/* PIKA - Photo and Image Kooker Application * a rebranding of The GNU Image Manipulation Program (created with heckimp) * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio * * Original copyright, applying to most contents (license remains unchanged): * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * pikatoolgyroscope.c * Copyright (C) 2018 Ell * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "config.h" #include #include #include #include "libpikabase/pikabase.h" #include "libpikamath/pikamath.h" #include "display-types.h" #include "widgets/pikawidgets-utils.h" #include "pikadisplayshell.h" #include "pikadisplayshell-transform.h" #include "pikatoolgyroscope.h" #include "pika-intl.h" #define EPSILON 1e-6 #define DEG_TO_RAD (G_PI / 180.0) typedef enum { MODE_NONE, MODE_PAN, MODE_ROTATE, MODE_ZOOM } Mode; typedef enum { CONSTRAINT_NONE, CONSTRAINT_UNKNOWN, CONSTRAINT_HORIZONTAL, CONSTRAINT_VERTICAL } Constraint; enum { PROP_0, PROP_YAW, PROP_PITCH, PROP_ROLL, PROP_ZOOM, PROP_INVERT, PROP_SPEED, PROP_PIVOT_X, PROP_PIVOT_Y }; struct _PikaToolGyroscopePrivate { gdouble yaw; gdouble pitch; gdouble roll; gdouble zoom; gdouble orig_yaw; gdouble orig_pitch; gdouble orig_roll; gdouble orig_zoom; gboolean invert; gdouble speed; gdouble pivot_x; gdouble pivot_y; Mode mode; Constraint constraint; gdouble last_x; gdouble last_y; gdouble last_angle; gdouble curr_angle; gdouble last_zoom; }; /* local function prototypes */ static void pika_tool_gyroscope_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); static void pika_tool_gyroscope_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec); static gint pika_tool_gyroscope_button_press (PikaToolWidget *widget, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaButtonPressType press_type); static void pika_tool_gyroscope_button_release (PikaToolWidget *widget, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaButtonReleaseType release_type); static void pika_tool_gyroscope_motion (PikaToolWidget *widget, const PikaCoords *coords, guint32 time, GdkModifierType state); static PikaHit pika_tool_gyroscope_hit (PikaToolWidget *widget, const PikaCoords *coords, GdkModifierType state, gboolean proximity); static void pika_tool_gyroscope_hover (PikaToolWidget *widget, const PikaCoords *coords, GdkModifierType state, gboolean proximity); static gboolean pika_tool_gyroscope_key_press (PikaToolWidget *widget, GdkEventKey *kevent); static void pika_tool_gyroscope_motion_modifier (PikaToolWidget *widget, GdkModifierType key, gboolean press, GdkModifierType state); static gboolean pika_tool_gyroscope_get_cursor (PikaToolWidget *widget, const PikaCoords *coords, GdkModifierType state, PikaCursorType *cursor, PikaToolCursorType *tool_cursor, PikaCursorModifier *modifier); static void pika_tool_gyroscope_update_status (PikaToolGyroscope *gyroscope, GdkModifierType state); static void pika_tool_gyroscope_save (PikaToolGyroscope *gyroscope); static void pika_tool_gyroscope_restore (PikaToolGyroscope *gyroscope); static void pika_tool_gyroscope_rotate (PikaToolGyroscope *gyroscope, const PikaVector3 *axis); static void pika_tool_gyroscope_rotate_vector (PikaVector3 *vector, const PikaVector3 *axis); G_DEFINE_TYPE_WITH_PRIVATE (PikaToolGyroscope, pika_tool_gyroscope, PIKA_TYPE_TOOL_WIDGET) #define parent_class pika_tool_gyroscope_parent_class static void pika_tool_gyroscope_class_init (PikaToolGyroscopeClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); PikaToolWidgetClass *widget_class = PIKA_TOOL_WIDGET_CLASS (klass); object_class->set_property = pika_tool_gyroscope_set_property; object_class->get_property = pika_tool_gyroscope_get_property; widget_class->button_press = pika_tool_gyroscope_button_press; widget_class->button_release = pika_tool_gyroscope_button_release; widget_class->motion = pika_tool_gyroscope_motion; widget_class->hit = pika_tool_gyroscope_hit; widget_class->hover = pika_tool_gyroscope_hover; widget_class->key_press = pika_tool_gyroscope_key_press; widget_class->motion_modifier = pika_tool_gyroscope_motion_modifier; widget_class->get_cursor = pika_tool_gyroscope_get_cursor; g_object_class_install_property (object_class, PROP_YAW, g_param_spec_double ("yaw", NULL, NULL, -G_MAXDOUBLE, +G_MAXDOUBLE, 0.0, PIKA_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_object_class_install_property (object_class, PROP_PITCH, g_param_spec_double ("pitch", NULL, NULL, -G_MAXDOUBLE, +G_MAXDOUBLE, 0.0, PIKA_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_object_class_install_property (object_class, PROP_ROLL, g_param_spec_double ("roll", NULL, NULL, -G_MAXDOUBLE, +G_MAXDOUBLE, 0.0, PIKA_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_object_class_install_property (object_class, PROP_ZOOM, g_param_spec_double ("zoom", NULL, NULL, 0.0, +G_MAXDOUBLE, 1.0, PIKA_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_object_class_install_property (object_class, PROP_INVERT, g_param_spec_boolean ("invert", NULL, NULL, FALSE, PIKA_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_object_class_install_property (object_class, PROP_SPEED, g_param_spec_double ("speed", NULL, NULL, -G_MAXDOUBLE, +G_MAXDOUBLE, 1.0, PIKA_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_object_class_install_property (object_class, PROP_PIVOT_X, g_param_spec_double ("pivot-x", NULL, NULL, -G_MAXDOUBLE, +G_MAXDOUBLE, 0.0, PIKA_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_object_class_install_property (object_class, PROP_PIVOT_Y, g_param_spec_double ("pivot-y", NULL, NULL, -G_MAXDOUBLE, +G_MAXDOUBLE, 0.0, PIKA_PARAM_READWRITE | G_PARAM_CONSTRUCT)); } static void pika_tool_gyroscope_init (PikaToolGyroscope *gyroscope) { gyroscope->private = pika_tool_gyroscope_get_instance_private (gyroscope); } static void pika_tool_gyroscope_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { PikaToolGyroscope *gyroscope = PIKA_TOOL_GYROSCOPE (object); PikaToolGyroscopePrivate *private = gyroscope->private; switch (property_id) { case PROP_YAW: private->yaw = g_value_get_double (value); break; case PROP_PITCH: private->pitch = g_value_get_double (value); break; case PROP_ROLL: private->roll = g_value_get_double (value); break; case PROP_ZOOM: private->zoom = g_value_get_double (value); break; case PROP_INVERT: private->invert = g_value_get_boolean (value); break; case PROP_SPEED: private->speed = g_value_get_double (value); break; case PROP_PIVOT_X: private->pivot_x = g_value_get_double (value); break; case PROP_PIVOT_Y: private->pivot_y = g_value_get_double (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void pika_tool_gyroscope_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { PikaToolGyroscope *gyroscope = PIKA_TOOL_GYROSCOPE (object); PikaToolGyroscopePrivate *private = gyroscope->private; switch (property_id) { case PROP_YAW: g_value_set_double (value, private->yaw); break; case PROP_PITCH: g_value_set_double (value, private->pitch); break; case PROP_ROLL: g_value_set_double (value, private->roll); break; case PROP_ZOOM: g_value_set_double (value, private->zoom); break; case PROP_INVERT: g_value_set_boolean (value, private->invert); break; case PROP_SPEED: g_value_set_double (value, private->speed); break; case PROP_PIVOT_X: g_value_set_double (value, private->pivot_x); break; case PROP_PIVOT_Y: g_value_set_double (value, private->pivot_y); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static gint pika_tool_gyroscope_button_press (PikaToolWidget *widget, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaButtonPressType press_type) { PikaToolGyroscope *gyroscope = PIKA_TOOL_GYROSCOPE (widget); PikaToolGyroscopePrivate *private = gyroscope->private; pika_tool_gyroscope_save (gyroscope); if (state & GDK_MOD1_MASK) { private->mode = MODE_ZOOM; private->last_zoom = private->zoom; } else if (state & pika_get_extend_selection_mask ()) { private->mode = MODE_ROTATE; private->last_angle = atan2 (coords->y - private->pivot_y, coords->x - private->pivot_x); private->curr_angle = private->last_angle; } else { private->mode = MODE_PAN; if (state & pika_get_constrain_behavior_mask ()) private->constraint = CONSTRAINT_UNKNOWN; else private->constraint = CONSTRAINT_NONE; } private->last_x = coords->x; private->last_y = coords->y; pika_tool_gyroscope_update_status (gyroscope, state); return 1; } static void pika_tool_gyroscope_button_release (PikaToolWidget *widget, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaButtonReleaseType release_type) { PikaToolGyroscope *gyroscope = PIKA_TOOL_GYROSCOPE (widget); PikaToolGyroscopePrivate *private = gyroscope->private; if (release_type == PIKA_BUTTON_RELEASE_CANCEL) pika_tool_gyroscope_restore (gyroscope); private->mode = MODE_NONE; pika_tool_gyroscope_update_status (gyroscope, state); } static void pika_tool_gyroscope_motion (PikaToolWidget *widget, const PikaCoords *coords, guint32 time, GdkModifierType state) { PikaToolGyroscope *gyroscope = PIKA_TOOL_GYROSCOPE (widget); PikaToolGyroscopePrivate *private = gyroscope->private; PikaDisplayShell *shell = pika_tool_widget_get_shell (widget); PikaVector3 axis = {}; switch (private->mode) { case MODE_PAN: { gdouble x1 = private->last_x; gdouble y1 = private->last_y; gdouble x2 = coords->x; gdouble y2 = coords->y; gdouble factor = 1.0 / private->zoom; if (private->constraint != CONSTRAINT_NONE) { pika_display_shell_rotate_xy_f (shell, x1, y1, &x1, &y1); pika_display_shell_rotate_xy_f (shell, x2, y2, &x2, &y2); if (private->constraint == CONSTRAINT_UNKNOWN) { if (fabs (x2 - x1) > fabs (y2 - y1)) private->constraint = CONSTRAINT_HORIZONTAL; else if (fabs (y2 - y1) > fabs (x2 - x1)) private->constraint = CONSTRAINT_VERTICAL; } if (private->constraint == CONSTRAINT_HORIZONTAL) y2 = y1; else if (private->constraint == CONSTRAINT_VERTICAL) x2 = x1; pika_display_shell_unrotate_xy_f (shell, x1, y1, &x1, &y1); pika_display_shell_unrotate_xy_f (shell, x2, y2, &x2, &y2); } if (private->invert) factor = 1.0 / factor; pika_vector3_set (&axis, y2 - y1, x2 - x1, 0.0); pika_vector3_mul (&axis, factor * private->speed); } break; case MODE_ROTATE: { gdouble angle; angle = atan2 (coords->y - private->pivot_y, coords->x - private->pivot_x); private->curr_angle = angle; angle -= private->last_angle; if (state & pika_get_constrain_behavior_mask ()) angle = RINT (angle / (G_PI / 6.0)) * (G_PI / 6.0); pika_vector3_set (&axis, 0.0, 0.0, angle); private->last_angle += angle; } break; case MODE_ZOOM: { gdouble x1, y1; gdouble x2, y2; gdouble zoom; pika_display_shell_transform_xy_f (shell, private->last_x, private->last_y, &x1, &y1); pika_display_shell_transform_xy_f (shell, coords->x, coords->y, &x2, &y2); zoom = (y1 - y2) * shell->scale_y / 128.0; if (private->invert) zoom = -zoom; private->last_zoom *= pow (2.0, zoom); zoom = log (private->last_zoom / private->zoom) / G_LN2; if (state & pika_get_constrain_behavior_mask ()) zoom = RINT (zoom * 2.0) / 2.0; g_object_set (gyroscope, "zoom", private->zoom * pow (2.0, zoom), NULL); } break; case MODE_NONE: g_return_if_reached (); } private->last_x = coords->x; private->last_y = coords->y; pika_tool_gyroscope_rotate (gyroscope, &axis); } static PikaHit pika_tool_gyroscope_hit (PikaToolWidget *widget, const PikaCoords *coords, GdkModifierType state, gboolean proximity) { return PIKA_HIT_INDIRECT; } static void pika_tool_gyroscope_hover (PikaToolWidget *widget, const PikaCoords *coords, GdkModifierType state, gboolean proximity) { pika_tool_gyroscope_update_status (PIKA_TOOL_GYROSCOPE (widget), state); } static gboolean pika_tool_gyroscope_key_press (PikaToolWidget *widget, GdkEventKey *kevent) { PikaToolGyroscope *gyroscope = PIKA_TOOL_GYROSCOPE (widget); PikaToolGyroscopePrivate *private = gyroscope->private; PikaDisplayShell *shell = pika_tool_widget_get_shell (widget); PikaVector3 axis = {}; gboolean fast; gboolean result = FALSE; fast = (kevent->state & pika_get_constrain_behavior_mask ()); if (kevent->state & GDK_MOD1_MASK) { /* zoom */ gdouble zoom = 0.0; switch (kevent->keyval) { case GDK_KEY_Up: zoom = fast ? +1.0 : +1.0 / 8.0; result = TRUE; break; case GDK_KEY_Down: zoom = fast ? -1.0 : -1.0 / 8.0; result = TRUE; break; } if (private->invert) zoom = -zoom; if (zoom) { g_object_set (gyroscope, "zoom", private->zoom * pow (2.0, zoom), NULL); } } else if (kevent->state & pika_get_extend_selection_mask ()) { /* rotate */ gdouble angle = 0.0; switch (kevent->keyval) { case GDK_KEY_Left: angle = fast ? +15.0 : +1.0; result = TRUE; break; case GDK_KEY_Right: angle = fast ? -15.0 : -1.0; result = TRUE; break; } if (shell->flip_horizontally ^ shell->flip_vertically) angle = -angle; pika_vector3_set (&axis, 0.0, 0.0, angle * DEG_TO_RAD); } else { /* pan */ gdouble x0 = 0.0; gdouble y0 = 0.0; gdouble x = 0.0; gdouble y = 0.0; gdouble factor = 1.0 / private->zoom; if (private->invert) factor = 1.0 / factor; switch (kevent->keyval) { case GDK_KEY_Left: x = fast ? +15.0 : +1.0; result = TRUE; break; case GDK_KEY_Right: x = fast ? -15.0 : -1.0; result = TRUE; break; case GDK_KEY_Up: y = fast ? +15.0 : +1.0; result = TRUE; break; case GDK_KEY_Down: y = fast ? -15.0 : -1.0; result = TRUE; break; } pika_display_shell_unrotate_xy_f (shell, x0, y0, &x0, &y0); pika_display_shell_unrotate_xy_f (shell, x, y, &x, &y); pika_vector3_set (&axis, (y - y0) * DEG_TO_RAD, (x - x0) * DEG_TO_RAD, 0.0); pika_vector3_mul (&axis, factor); } pika_tool_gyroscope_rotate (gyroscope, &axis); return result; } static void pika_tool_gyroscope_motion_modifier (PikaToolWidget *widget, GdkModifierType key, gboolean press, GdkModifierType state) { PikaToolGyroscope *gyroscope = PIKA_TOOL_GYROSCOPE (widget); PikaToolGyroscopePrivate *private = gyroscope->private; pika_tool_gyroscope_update_status (gyroscope, state); if (key == pika_get_constrain_behavior_mask ()) { switch (private->mode) { case MODE_PAN: if (state & pika_get_constrain_behavior_mask ()) private->constraint = CONSTRAINT_UNKNOWN; else private->constraint = CONSTRAINT_NONE; break; case MODE_ROTATE: if (! (state & pika_get_constrain_behavior_mask ())) private->last_angle = private->curr_angle; break; case MODE_ZOOM: if (! (state & pika_get_constrain_behavior_mask ())) private->last_zoom = private->zoom; break; case MODE_NONE: break; } } } static gboolean pika_tool_gyroscope_get_cursor (PikaToolWidget *widget, const PikaCoords *coords, GdkModifierType state, PikaCursorType *cursor, PikaToolCursorType *tool_cursor, PikaCursorModifier *modifier) { if (state & GDK_MOD1_MASK) *modifier = PIKA_CURSOR_MODIFIER_ZOOM; else if (state & pika_get_extend_selection_mask ()) *modifier = PIKA_CURSOR_MODIFIER_ROTATE; else *modifier = PIKA_CURSOR_MODIFIER_MOVE; return TRUE; } static void pika_tool_gyroscope_update_status (PikaToolGyroscope *gyroscope, GdkModifierType state) { PikaToolGyroscopePrivate *private = gyroscope->private; gchar *status; if (private->mode == MODE_ZOOM || (private->mode == MODE_NONE && state & GDK_MOD1_MASK)) { status = pika_suggest_modifiers (_("Click-Drag to zoom"), pika_get_toggle_behavior_mask () & ~state, NULL, _("%s for constrained steps"), NULL); } else if (private->mode == MODE_ROTATE || (private->mode == MODE_NONE && state & pika_get_extend_selection_mask ())) { status = pika_suggest_modifiers (_("Click-Drag to rotate"), pika_get_toggle_behavior_mask () & ~state, NULL, _("%s for constrained angles"), NULL); } else { status = pika_suggest_modifiers (_("Click-Drag to pan"), (((pika_get_extend_selection_mask () | GDK_MOD1_MASK) * (private->mode == MODE_NONE)) | pika_get_toggle_behavior_mask ()) & ~state, _("%s to rotate"), _("%s for a constrained axis"), _("%s to zoom")); } pika_tool_widget_set_status (PIKA_TOOL_WIDGET (gyroscope), status); g_free (status); } static void pika_tool_gyroscope_save (PikaToolGyroscope *gyroscope) { PikaToolGyroscopePrivate *private = gyroscope->private; private->orig_yaw = private->yaw; private->orig_pitch = private->pitch; private->orig_roll = private->roll; private->orig_zoom = private->zoom; } static void pika_tool_gyroscope_restore (PikaToolGyroscope *gyroscope) { PikaToolGyroscopePrivate *private = gyroscope->private; g_object_set (gyroscope, "yaw", private->orig_yaw, "pitch", private->orig_pitch, "roll", private->orig_roll, "zoom", private->orig_zoom, NULL); } static void pika_tool_gyroscope_rotate (PikaToolGyroscope *gyroscope, const PikaVector3 *axis) { PikaToolGyroscopePrivate *private = gyroscope->private; PikaVector3 real_axis; PikaVector3 basis[2]; gdouble yaw; gdouble pitch; gdouble roll; gint i; if (pika_vector3_length (axis) < EPSILON) return; real_axis = *axis; if (private->invert) pika_vector3_neg (&real_axis); for (i = 0; i < 2; i++) { pika_vector3_set (&basis[i], i == 0, i == 1, 0.0); if (private->invert) pika_tool_gyroscope_rotate_vector (&basis[i], &real_axis); pika_tool_gyroscope_rotate_vector ( &basis[i], &(PikaVector3) {0.0, private->yaw * DEG_TO_RAD, 0.0}); pika_tool_gyroscope_rotate_vector ( &basis[i], &(PikaVector3) {private->pitch * DEG_TO_RAD, 0.0, 0.0}); pika_tool_gyroscope_rotate_vector ( &basis[i], &(PikaVector3) {0.0, 0.0, private->roll * DEG_TO_RAD}); if (! private->invert) pika_tool_gyroscope_rotate_vector (&basis[i], &real_axis); } roll = atan2 (basis[1].x, basis[1].y); for (i = 0; i < 2; i++) { pika_tool_gyroscope_rotate_vector ( &basis[i], &(PikaVector3) {0.0, 0.0, -roll}); } pitch = atan2 (-basis[1].z, basis[1].y); for (i = 0; i < 1; i++) { pika_tool_gyroscope_rotate_vector ( &basis[i], &(PikaVector3) {-pitch, 0.0, 0.0}); } yaw = atan2 (basis[0].z, basis[0].x); g_object_set (gyroscope, "yaw", yaw / DEG_TO_RAD, "pitch", pitch / DEG_TO_RAD, "roll", roll / DEG_TO_RAD, NULL); } static void pika_tool_gyroscope_rotate_vector (PikaVector3 *vector, const PikaVector3 *axis) { PikaVector3 normalized_axis; PikaVector3 projection; PikaVector3 u; PikaVector3 v; gdouble angle; angle = pika_vector3_length (axis); if (angle < EPSILON) return; normalized_axis = pika_vector3_mul_val (*axis, 1.0 / angle); projection = pika_vector3_mul_val ( normalized_axis, pika_vector3_inner_product (vector, &normalized_axis)); u = pika_vector3_sub_val (*vector, projection); v = pika_vector3_cross_product (&u, &normalized_axis); pika_vector3_mul (&u, cos (angle)); pika_vector3_mul (&v, sin (angle)); pika_vector3_add (vector, &u, &v); pika_vector3_add (vector, vector, &projection); } /* public functions */ PikaToolWidget * pika_tool_gyroscope_new (PikaDisplayShell *shell) { g_return_val_if_fail (PIKA_IS_DISPLAY_SHELL (shell), NULL); return g_object_new (PIKA_TYPE_TOOL_GYROSCOPE, "shell", shell, NULL); }