1 /* GIMP - The GNU Image Manipulation Program
2  * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3  *
4  * gimptoolgyroscope.c
5  * Copyright (C) 2018 Ell
6  *
7  * This program is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
19  */
20 
21 #include "config.h"
22 
23 #include <gegl.h>
24 #include <gtk/gtk.h>
25 #include <gdk/gdkkeysyms.h>
26 
27 #include "libgimpmath/gimpmath.h"
28 
29 #include "display-types.h"
30 
31 #include "widgets/gimpwidgets-utils.h"
32 
33 #include "gimpdisplayshell.h"
34 #include "gimpdisplayshell-transform.h"
35 #include "gimptoolgyroscope.h"
36 
37 #include "gimp-intl.h"
38 
39 
40 #define EPSILON    1e-6
41 #define DEG_TO_RAD (G_PI / 180.0)
42 
43 
44 typedef enum
45 {
46   MODE_NONE,
47   MODE_PAN,
48   MODE_ROTATE,
49   MODE_ZOOM
50 } Mode;
51 
52 typedef enum
53 {
54   CONSTRAINT_NONE,
55   CONSTRAINT_UNKNOWN,
56   CONSTRAINT_HORIZONTAL,
57   CONSTRAINT_VERTICAL
58 } Constraint;
59 
60 enum
61 {
62   PROP_0,
63   PROP_YAW,
64   PROP_PITCH,
65   PROP_ROLL,
66   PROP_ZOOM,
67   PROP_INVERT,
68   PROP_SPEED,
69   PROP_PIVOT_X,
70   PROP_PIVOT_Y
71 };
72 
73 struct _GimpToolGyroscopePrivate
74 {
75   gdouble    yaw;
76   gdouble    pitch;
77   gdouble    roll;
78   gdouble    zoom;
79 
80   gdouble    orig_yaw;
81   gdouble    orig_pitch;
82   gdouble    orig_roll;
83   gdouble    orig_zoom;
84 
85   gboolean   invert;
86 
87   gdouble    speed;
88 
89   gdouble    pivot_x;
90   gdouble    pivot_y;
91 
92   Mode       mode;
93   Constraint constraint;
94 
95   gdouble    last_x;
96   gdouble    last_y;
97 
98   gdouble    last_angle;
99   gdouble    curr_angle;
100 
101   gdouble    last_zoom;
102 };
103 
104 
105 /*  local function prototypes  */
106 
107 static void       gimp_tool_gyroscope_set_property    (GObject               *object,
108                                                        guint                  property_id,
109                                                        const GValue          *value,
110                                                        GParamSpec            *pspec);
111 static void       gimp_tool_gyroscope_get_property    (GObject               *object,
112                                                        guint                  property_id,
113                                                        GValue                *value,
114                                                        GParamSpec            *pspec);
115 
116 static gint       gimp_tool_gyroscope_button_press    (GimpToolWidget        *widget,
117                                                        const GimpCoords      *coords,
118                                                        guint32                time,
119                                                        GdkModifierType        state,
120                                                        GimpButtonPressType    press_type);
121 static void       gimp_tool_gyroscope_button_release  (GimpToolWidget        *widget,
122                                                        const GimpCoords      *coords,
123                                                        guint32                time,
124                                                        GdkModifierType        state,
125                                                        GimpButtonReleaseType  release_type);
126 static void       gimp_tool_gyroscope_motion          (GimpToolWidget        *widget,
127                                                        const GimpCoords      *coords,
128                                                        guint32                time,
129                                                        GdkModifierType        state);
130 static GimpHit    gimp_tool_gyroscope_hit             (GimpToolWidget        *widget,
131                                                        const GimpCoords      *coords,
132                                                        GdkModifierType        state,
133                                                        gboolean               proximity);
134 static void       gimp_tool_gyroscope_hover           (GimpToolWidget        *widget,
135                                                        const GimpCoords      *coords,
136                                                        GdkModifierType        state,
137                                                        gboolean               proximity);
138 static gboolean   gimp_tool_gyroscope_key_press       (GimpToolWidget        *widget,
139                                                        GdkEventKey           *kevent);
140 static void       gimp_tool_gyroscope_motion_modifier (GimpToolWidget        *widget,
141                                                        GdkModifierType        key,
142                                                        gboolean               press,
143                                                        GdkModifierType        state);
144 static gboolean   gimp_tool_gyroscope_get_cursor      (GimpToolWidget        *widget,
145                                                        const GimpCoords      *coords,
146                                                        GdkModifierType        state,
147                                                        GimpCursorType        *cursor,
148                                                        GimpToolCursorType    *tool_cursor,
149                                                        GimpCursorModifier    *modifier);
150 
151 static void       gimp_tool_gyroscope_update_status   (GimpToolGyroscope      *gyroscope,
152                                                        GdkModifierType         state);
153 
154 static void       gimp_tool_gyroscope_save            (GimpToolGyroscope      *gyroscope);
155 static void       gimp_tool_gyroscope_restore         (GimpToolGyroscope      *gyroscope);
156 
157 static void       gimp_tool_gyroscope_rotate          (GimpToolGyroscope      *gyroscope,
158                                                        const GimpVector3      *axis);
159 static void       gimp_tool_gyroscope_rotate_vector   (GimpVector3            *vector,
160                                                        const GimpVector3      *axis);
161 
162 
G_DEFINE_TYPE_WITH_PRIVATE(GimpToolGyroscope,gimp_tool_gyroscope,GIMP_TYPE_TOOL_WIDGET)163 G_DEFINE_TYPE_WITH_PRIVATE (GimpToolGyroscope, gimp_tool_gyroscope,
164                             GIMP_TYPE_TOOL_WIDGET)
165 
166 #define parent_class gimp_tool_gyroscope_parent_class
167 
168 
169 static void
170 gimp_tool_gyroscope_class_init (GimpToolGyroscopeClass *klass)
171 {
172   GObjectClass        *object_class = G_OBJECT_CLASS (klass);
173   GimpToolWidgetClass *widget_class = GIMP_TOOL_WIDGET_CLASS (klass);
174 
175   object_class->set_property    = gimp_tool_gyroscope_set_property;
176   object_class->get_property    = gimp_tool_gyroscope_get_property;
177 
178   widget_class->button_press    = gimp_tool_gyroscope_button_press;
179   widget_class->button_release  = gimp_tool_gyroscope_button_release;
180   widget_class->motion          = gimp_tool_gyroscope_motion;
181   widget_class->hit             = gimp_tool_gyroscope_hit;
182   widget_class->hover           = gimp_tool_gyroscope_hover;
183   widget_class->key_press       = gimp_tool_gyroscope_key_press;
184   widget_class->motion_modifier = gimp_tool_gyroscope_motion_modifier;
185   widget_class->get_cursor      = gimp_tool_gyroscope_get_cursor;
186 
187   g_object_class_install_property (object_class, PROP_YAW,
188                                    g_param_spec_double ("yaw", NULL, NULL,
189                                                         -G_MAXDOUBLE,
190                                                         +G_MAXDOUBLE,
191                                                         0.0,
192                                                         GIMP_PARAM_READWRITE |
193                                                         G_PARAM_CONSTRUCT));
194 
195   g_object_class_install_property (object_class, PROP_PITCH,
196                                    g_param_spec_double ("pitch", NULL, NULL,
197                                                         -G_MAXDOUBLE,
198                                                         +G_MAXDOUBLE,
199                                                         0.0,
200                                                         GIMP_PARAM_READWRITE |
201                                                         G_PARAM_CONSTRUCT));
202 
203   g_object_class_install_property (object_class, PROP_ROLL,
204                                    g_param_spec_double ("roll", NULL, NULL,
205                                                         -G_MAXDOUBLE,
206                                                         +G_MAXDOUBLE,
207                                                         0.0,
208                                                         GIMP_PARAM_READWRITE |
209                                                         G_PARAM_CONSTRUCT));
210 
211   g_object_class_install_property (object_class, PROP_ZOOM,
212                                    g_param_spec_double ("zoom", NULL, NULL,
213                                                         0.0,
214                                                         +G_MAXDOUBLE,
215                                                         1.0,
216                                                         GIMP_PARAM_READWRITE |
217                                                         G_PARAM_CONSTRUCT));
218 
219   g_object_class_install_property (object_class, PROP_INVERT,
220                                    g_param_spec_boolean ("invert", NULL, NULL,
221                                                          FALSE,
222                                                          GIMP_PARAM_READWRITE |
223                                                          G_PARAM_CONSTRUCT));
224 
225   g_object_class_install_property (object_class, PROP_SPEED,
226                                    g_param_spec_double ("speed", NULL, NULL,
227                                                         -G_MAXDOUBLE,
228                                                         +G_MAXDOUBLE,
229                                                         1.0,
230                                                         GIMP_PARAM_READWRITE |
231                                                         G_PARAM_CONSTRUCT));
232 
233   g_object_class_install_property (object_class, PROP_PIVOT_X,
234                                    g_param_spec_double ("pivot-x", NULL, NULL,
235                                                         -G_MAXDOUBLE,
236                                                         +G_MAXDOUBLE,
237                                                         0.0,
238                                                         GIMP_PARAM_READWRITE |
239                                                         G_PARAM_CONSTRUCT));
240 
241   g_object_class_install_property (object_class, PROP_PIVOT_Y,
242                                    g_param_spec_double ("pivot-y", NULL, NULL,
243                                                         -G_MAXDOUBLE,
244                                                         +G_MAXDOUBLE,
245                                                         0.0,
246                                                         GIMP_PARAM_READWRITE |
247                                                         G_PARAM_CONSTRUCT));
248 }
249 
250 static void
gimp_tool_gyroscope_init(GimpToolGyroscope * gyroscope)251 gimp_tool_gyroscope_init (GimpToolGyroscope *gyroscope)
252 {
253   gyroscope->private = gimp_tool_gyroscope_get_instance_private (gyroscope);
254 }
255 
256 static void
gimp_tool_gyroscope_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)257 gimp_tool_gyroscope_set_property (GObject      *object,
258                                   guint         property_id,
259                                   const GValue *value,
260                                   GParamSpec   *pspec)
261 {
262   GimpToolGyroscope        *gyroscope = GIMP_TOOL_GYROSCOPE (object);
263   GimpToolGyroscopePrivate *private = gyroscope->private;
264 
265   switch (property_id)
266     {
267     case PROP_YAW:
268       private->yaw = g_value_get_double (value);
269       break;
270     case PROP_PITCH:
271       private->pitch = g_value_get_double (value);
272       break;
273     case PROP_ROLL:
274       private->roll = g_value_get_double (value);
275       break;
276     case PROP_ZOOM:
277       private->zoom = g_value_get_double (value);
278       break;
279     case PROP_INVERT:
280       private->invert = g_value_get_boolean (value);
281       break;
282     case PROP_SPEED:
283       private->speed = g_value_get_double (value);
284       break;
285     case PROP_PIVOT_X:
286       private->pivot_x = g_value_get_double (value);
287       break;
288     case PROP_PIVOT_Y:
289       private->pivot_y = g_value_get_double (value);
290       break;
291 
292     default:
293       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
294       break;
295     }
296 }
297 
298 static void
gimp_tool_gyroscope_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)299 gimp_tool_gyroscope_get_property (GObject    *object,
300                                   guint       property_id,
301                                   GValue     *value,
302                                   GParamSpec *pspec)
303 {
304   GimpToolGyroscope        *gyroscope = GIMP_TOOL_GYROSCOPE (object);
305   GimpToolGyroscopePrivate *private = gyroscope->private;
306 
307   switch (property_id)
308     {
309     case PROP_YAW:
310       g_value_set_double (value, private->yaw);
311       break;
312     case PROP_PITCH:
313       g_value_set_double (value, private->pitch);
314       break;
315     case PROP_ROLL:
316       g_value_set_double (value, private->roll);
317       break;
318     case PROP_ZOOM:
319       g_value_set_double (value, private->zoom);
320       break;
321     case PROP_INVERT:
322       g_value_set_boolean (value, private->invert);
323       break;
324     case PROP_SPEED:
325       g_value_set_double (value, private->speed);
326       break;
327     case PROP_PIVOT_X:
328       g_value_set_double (value, private->pivot_x);
329       break;
330     case PROP_PIVOT_Y:
331       g_value_set_double (value, private->pivot_y);
332       break;
333 
334     default:
335       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
336       break;
337     }
338 }
339 
340 static gint
gimp_tool_gyroscope_button_press(GimpToolWidget * widget,const GimpCoords * coords,guint32 time,GdkModifierType state,GimpButtonPressType press_type)341 gimp_tool_gyroscope_button_press (GimpToolWidget      *widget,
342                                   const GimpCoords    *coords,
343                                   guint32              time,
344                                   GdkModifierType      state,
345                                   GimpButtonPressType  press_type)
346 {
347   GimpToolGyroscope        *gyroscope = GIMP_TOOL_GYROSCOPE (widget);
348   GimpToolGyroscopePrivate *private   = gyroscope->private;
349 
350   gimp_tool_gyroscope_save (gyroscope);
351 
352   if (state & GDK_MOD1_MASK)
353     {
354       private->mode = MODE_ZOOM;
355 
356       private->last_zoom = private->zoom;
357     }
358   else if (state & gimp_get_extend_selection_mask ())
359     {
360       private->mode = MODE_ROTATE;
361 
362       private->last_angle = atan2 (coords->y - private->pivot_y,
363                                    coords->x - private->pivot_x);
364       private->curr_angle = private->last_angle;
365     }
366   else
367     {
368       private->mode = MODE_PAN;
369 
370       if (state & gimp_get_constrain_behavior_mask ())
371         private->constraint = CONSTRAINT_UNKNOWN;
372       else
373         private->constraint = CONSTRAINT_NONE;
374     }
375 
376   private->last_x = coords->x;
377   private->last_y = coords->y;
378 
379   gimp_tool_gyroscope_update_status (gyroscope, state);
380 
381   return 1;
382 }
383 
384 static void
gimp_tool_gyroscope_button_release(GimpToolWidget * widget,const GimpCoords * coords,guint32 time,GdkModifierType state,GimpButtonReleaseType release_type)385 gimp_tool_gyroscope_button_release (GimpToolWidget        *widget,
386                                     const GimpCoords      *coords,
387                                     guint32                time,
388                                     GdkModifierType        state,
389                                     GimpButtonReleaseType  release_type)
390 {
391   GimpToolGyroscope        *gyroscope = GIMP_TOOL_GYROSCOPE (widget);
392   GimpToolGyroscopePrivate *private   = gyroscope->private;
393 
394   if (release_type == GIMP_BUTTON_RELEASE_CANCEL)
395     gimp_tool_gyroscope_restore (gyroscope);
396 
397   private->mode = MODE_NONE;
398 
399   gimp_tool_gyroscope_update_status (gyroscope, state);
400 }
401 
402 static void
gimp_tool_gyroscope_motion(GimpToolWidget * widget,const GimpCoords * coords,guint32 time,GdkModifierType state)403 gimp_tool_gyroscope_motion (GimpToolWidget   *widget,
404                             const GimpCoords *coords,
405                             guint32           time,
406                             GdkModifierType   state)
407 {
408   GimpToolGyroscope        *gyroscope = GIMP_TOOL_GYROSCOPE (widget);
409   GimpToolGyroscopePrivate *private   = gyroscope->private;
410   GimpDisplayShell         *shell     = gimp_tool_widget_get_shell (widget);
411   GimpVector3               axis      = {};
412 
413   switch (private->mode)
414     {
415       case MODE_PAN:
416         {
417           gdouble x1     = private->last_x;
418           gdouble y1     = private->last_y;
419           gdouble x2     = coords->x;
420           gdouble y2     = coords->y;
421           gdouble factor = 1.0 / private->zoom;
422 
423           if (private->constraint != CONSTRAINT_NONE)
424             {
425               gimp_display_shell_rotate_xy_f (shell, x1, y1, &x1, &y1);
426               gimp_display_shell_rotate_xy_f (shell, x2, y2, &x2, &y2);
427 
428               if (private->constraint == CONSTRAINT_UNKNOWN)
429                 {
430                   if (fabs (x2 - x1) > fabs (y2 - y1))
431                     private->constraint = CONSTRAINT_HORIZONTAL;
432                   else if (fabs (y2 - y1) > fabs (x2 - x1))
433                     private->constraint = CONSTRAINT_VERTICAL;
434                 }
435 
436               if (private->constraint == CONSTRAINT_HORIZONTAL)
437                 y2 = y1;
438               else if (private->constraint == CONSTRAINT_VERTICAL)
439                 x2 = x1;
440 
441               gimp_display_shell_unrotate_xy_f (shell, x1, y1, &x1, &y1);
442               gimp_display_shell_unrotate_xy_f (shell, x2, y2, &x2, &y2);
443             }
444 
445           if (private->invert)
446             factor = 1.0 / factor;
447 
448           gimp_vector3_set (&axis, y2 - y1, x2 - x1, 0.0);
449           gimp_vector3_mul (&axis, factor * private->speed);
450         }
451       break;
452 
453     case MODE_ROTATE:
454       {
455         gdouble angle;
456 
457         angle = atan2 (coords->y - private->pivot_y,
458                        coords->x - private->pivot_x);
459 
460         private->curr_angle = angle;
461 
462         angle -= private->last_angle;
463 
464         if (state & gimp_get_constrain_behavior_mask ())
465           angle = RINT (angle / (G_PI / 6.0)) * (G_PI / 6.0);
466 
467         gimp_vector3_set (&axis, 0.0, 0.0, angle);
468 
469         private->last_angle += angle;
470       }
471     break;
472 
473     case MODE_ZOOM:
474       {
475         gdouble x1, y1;
476         gdouble x2, y2;
477         gdouble zoom;
478 
479         gimp_display_shell_transform_xy_f (shell,
480                                            private->last_x, private->last_y,
481                                            &x1,             &y1);
482         gimp_display_shell_transform_xy_f (shell,
483                                            coords->x,       coords->y,
484                                            &x2,             &y2);
485 
486         zoom = (y1 - y2) * shell->scale_y / 128.0;
487 
488         if (private->invert)
489           zoom = -zoom;
490 
491         private->last_zoom *= pow (2.0, zoom);
492 
493         zoom = log (private->last_zoom / private->zoom) / G_LN2;
494 
495         if (state & gimp_get_constrain_behavior_mask ())
496           zoom = RINT (zoom * 2.0) / 2.0;
497 
498         g_object_set (gyroscope,
499                       "zoom", private->zoom * pow (2.0, zoom),
500                       NULL);
501       }
502       break;
503 
504     case MODE_NONE:
505       g_return_if_reached ();
506   }
507 
508   private->last_x = coords->x;
509   private->last_y = coords->y;
510 
511   gimp_tool_gyroscope_rotate (gyroscope, &axis);
512 }
513 
514 static GimpHit
gimp_tool_gyroscope_hit(GimpToolWidget * widget,const GimpCoords * coords,GdkModifierType state,gboolean proximity)515 gimp_tool_gyroscope_hit (GimpToolWidget   *widget,
516                          const GimpCoords *coords,
517                          GdkModifierType   state,
518                          gboolean          proximity)
519 {
520   return GIMP_HIT_INDIRECT;
521 }
522 
523 static void
gimp_tool_gyroscope_hover(GimpToolWidget * widget,const GimpCoords * coords,GdkModifierType state,gboolean proximity)524 gimp_tool_gyroscope_hover (GimpToolWidget   *widget,
525                            const GimpCoords *coords,
526                            GdkModifierType   state,
527                            gboolean          proximity)
528 {
529   gimp_tool_gyroscope_update_status (GIMP_TOOL_GYROSCOPE (widget), state);
530 }
531 
532 static gboolean
gimp_tool_gyroscope_key_press(GimpToolWidget * widget,GdkEventKey * kevent)533 gimp_tool_gyroscope_key_press (GimpToolWidget *widget,
534                                GdkEventKey    *kevent)
535 {
536   GimpToolGyroscope        *gyroscope = GIMP_TOOL_GYROSCOPE (widget);
537   GimpToolGyroscopePrivate *private   = gyroscope->private;
538   GimpDisplayShell         *shell     = gimp_tool_widget_get_shell (widget);
539   GimpVector3               axis      = {};
540   gboolean                  fast;
541   gboolean                  result    = FALSE;
542 
543   fast = (kevent->state & gimp_get_constrain_behavior_mask ());
544 
545   if (kevent->state & GDK_MOD1_MASK)
546     {
547       /* zoom */
548       gdouble zoom = 0.0;
549 
550       switch (kevent->keyval)
551         {
552         case GDK_KEY_Up:
553           zoom   = fast ? +1.0 : +1.0 / 8.0;
554           result = TRUE;
555           break;
556 
557         case GDK_KEY_Down:
558           zoom   = fast ? -1.0 : -1.0 / 8.0;
559           result = TRUE;
560           break;
561         }
562 
563       if (private->invert)
564         zoom = -zoom;
565 
566       if (zoom)
567         {
568           g_object_set (gyroscope,
569                         "zoom", private->zoom * pow (2.0, zoom),
570                         NULL);
571         }
572     }
573   else if (kevent->state & gimp_get_extend_selection_mask ())
574     {
575       /* rotate */
576       gdouble angle = 0.0;
577 
578       switch (kevent->keyval)
579         {
580         case GDK_KEY_Left:
581           angle  = fast ? +15.0 : +1.0;
582           result = TRUE;
583           break;
584 
585         case GDK_KEY_Right:
586           angle  = fast ? -15.0 : -1.0;
587           result = TRUE;
588           break;
589         }
590 
591       if (shell->flip_horizontally ^ shell->flip_vertically)
592         angle = -angle;
593 
594       gimp_vector3_set (&axis, 0.0, 0.0, angle * DEG_TO_RAD);
595     }
596   else
597     {
598       /* pan */
599       gdouble x0     = 0.0;
600       gdouble y0     = 0.0;
601       gdouble x      = 0.0;
602       gdouble y      = 0.0;
603       gdouble factor = 1.0 / private->zoom;
604 
605       if (private->invert)
606         factor = 1.0 / factor;
607 
608       switch (kevent->keyval)
609         {
610         case GDK_KEY_Left:
611           x      = fast ? +15.0 : +1.0;
612           result = TRUE;
613           break;
614 
615         case GDK_KEY_Right:
616           x      = fast ? -15.0 : -1.0;
617           result = TRUE;
618           break;
619 
620         case GDK_KEY_Up:
621           y      = fast ? +15.0 : +1.0;
622           result = TRUE;
623           break;
624 
625         case GDK_KEY_Down:
626           y      = fast ? -15.0 : -1.0;
627           result = TRUE;
628           break;
629         }
630 
631       gimp_display_shell_unrotate_xy_f (shell, x0, y0, &x0, &y0);
632       gimp_display_shell_unrotate_xy_f (shell, x,  y,  &x,  &y);
633 
634       gimp_vector3_set (&axis,
635                         (y - y0) * DEG_TO_RAD,
636                         (x - x0) * DEG_TO_RAD,
637                         0.0);
638       gimp_vector3_mul (&axis, factor);
639     }
640 
641   gimp_tool_gyroscope_rotate (gyroscope, &axis);
642 
643   return result;
644 }
645 
646 static void
gimp_tool_gyroscope_motion_modifier(GimpToolWidget * widget,GdkModifierType key,gboolean press,GdkModifierType state)647 gimp_tool_gyroscope_motion_modifier (GimpToolWidget  *widget,
648                                      GdkModifierType  key,
649                                      gboolean         press,
650                                      GdkModifierType  state)
651 {
652   GimpToolGyroscope        *gyroscope = GIMP_TOOL_GYROSCOPE (widget);
653   GimpToolGyroscopePrivate *private   = gyroscope->private;
654 
655   gimp_tool_gyroscope_update_status (gyroscope, state);
656 
657   if (key == gimp_get_constrain_behavior_mask ())
658     {
659       switch (private->mode)
660       {
661         case MODE_PAN:
662           if (state & gimp_get_constrain_behavior_mask ())
663             private->constraint = CONSTRAINT_UNKNOWN;
664           else
665             private->constraint = CONSTRAINT_NONE;
666           break;
667 
668         case MODE_ROTATE:
669           if (! (state & gimp_get_constrain_behavior_mask ()))
670             private->last_angle = private->curr_angle;
671           break;
672 
673         case MODE_ZOOM:
674           if (! (state & gimp_get_constrain_behavior_mask ()))
675             private->last_zoom = private->zoom;
676           break;
677 
678         case MODE_NONE:
679           break;
680       }
681     }
682 }
683 
684 static gboolean
gimp_tool_gyroscope_get_cursor(GimpToolWidget * widget,const GimpCoords * coords,GdkModifierType state,GimpCursorType * cursor,GimpToolCursorType * tool_cursor,GimpCursorModifier * modifier)685 gimp_tool_gyroscope_get_cursor (GimpToolWidget     *widget,
686                                 const GimpCoords   *coords,
687                                 GdkModifierType     state,
688                                 GimpCursorType     *cursor,
689                                 GimpToolCursorType *tool_cursor,
690                                 GimpCursorModifier *modifier)
691 {
692   if (state & GDK_MOD1_MASK)
693     *modifier = GIMP_CURSOR_MODIFIER_ZOOM;
694   else if (state & gimp_get_extend_selection_mask ())
695     *modifier = GIMP_CURSOR_MODIFIER_ROTATE;
696   else
697     *modifier = GIMP_CURSOR_MODIFIER_MOVE;
698 
699   return TRUE;
700 }
701 
702 static void
gimp_tool_gyroscope_update_status(GimpToolGyroscope * gyroscope,GdkModifierType state)703 gimp_tool_gyroscope_update_status (GimpToolGyroscope *gyroscope,
704                                    GdkModifierType    state)
705 {
706   GimpToolGyroscopePrivate *private = gyroscope->private;
707   gchar                    *status;
708 
709   if (private->mode == MODE_ZOOM ||
710       (private->mode == MODE_NONE &&
711        state & GDK_MOD1_MASK))
712     {
713       status = gimp_suggest_modifiers (_("Click-Drag to zoom"),
714                                        gimp_get_toggle_behavior_mask () &
715                                        ~state,
716                                        NULL,
717                                        _("%s for constrained steps"),
718                                        NULL);
719     }
720   else if (private->mode == MODE_ROTATE ||
721            (private->mode == MODE_NONE &&
722             state & gimp_get_extend_selection_mask ()))
723     {
724       status = gimp_suggest_modifiers (_("Click-Drag to rotate"),
725                                        gimp_get_toggle_behavior_mask () &
726                                        ~state,
727                                        NULL,
728                                        _("%s for constrained angles"),
729                                        NULL);
730     }
731   else
732     {
733       status = gimp_suggest_modifiers (_("Click-Drag to pan"),
734                                        (((gimp_get_extend_selection_mask () |
735                                           GDK_MOD1_MASK)                    *
736                                          (private->mode == MODE_NONE))      |
737                                         gimp_get_toggle_behavior_mask  ())  &
738                                         ~state,
739                                        _("%s to rotate"),
740                                        _("%s for a constrained axis"),
741                                        _("%s to zoom"));
742     }
743 
744   gimp_tool_widget_set_status (GIMP_TOOL_WIDGET (gyroscope), status);
745 
746   g_free (status);
747 }
748 
749 static void
gimp_tool_gyroscope_save(GimpToolGyroscope * gyroscope)750 gimp_tool_gyroscope_save (GimpToolGyroscope *gyroscope)
751 {
752   GimpToolGyroscopePrivate *private = gyroscope->private;
753 
754   private->orig_yaw   = private->yaw;
755   private->orig_pitch = private->pitch;
756   private->orig_roll  = private->roll;
757   private->orig_zoom  = private->zoom;
758 }
759 
760 static void
gimp_tool_gyroscope_restore(GimpToolGyroscope * gyroscope)761 gimp_tool_gyroscope_restore (GimpToolGyroscope *gyroscope)
762 {
763   GimpToolGyroscopePrivate *private = gyroscope->private;
764 
765   g_object_set (gyroscope,
766                 "yaw",   private->orig_yaw,
767                 "pitch", private->orig_pitch,
768                 "roll",  private->orig_roll,
769                 "zoom",  private->orig_zoom,
770                 NULL);
771 }
772 
773 static void
gimp_tool_gyroscope_rotate(GimpToolGyroscope * gyroscope,const GimpVector3 * axis)774 gimp_tool_gyroscope_rotate (GimpToolGyroscope *gyroscope,
775                             const GimpVector3 *axis)
776 {
777   GimpToolGyroscopePrivate *private = gyroscope->private;
778   GimpVector3               real_axis;
779   GimpVector3               basis[2];
780   gdouble                   yaw;
781   gdouble                   pitch;
782   gdouble                   roll;
783   gint                      i;
784 
785   if (gimp_vector3_length (axis) < EPSILON)
786     return;
787 
788   real_axis = *axis;
789 
790   if (private->invert)
791     gimp_vector3_neg (&real_axis);
792 
793   for (i = 0; i < 2; i++)
794     {
795       gimp_vector3_set (&basis[i], i == 0, i == 1, 0.0);
796 
797       if (private->invert)
798         gimp_tool_gyroscope_rotate_vector (&basis[i], &real_axis);
799 
800       gimp_tool_gyroscope_rotate_vector (
801         &basis[i], &(GimpVector3) {0.0, private->yaw * DEG_TO_RAD, 0.0});
802       gimp_tool_gyroscope_rotate_vector (
803         &basis[i], &(GimpVector3) {private->pitch * DEG_TO_RAD, 0.0, 0.0});
804       gimp_tool_gyroscope_rotate_vector (
805         &basis[i], &(GimpVector3) {0.0, 0.0, private->roll * DEG_TO_RAD});
806 
807       if (! private->invert)
808         gimp_tool_gyroscope_rotate_vector (&basis[i], &real_axis);
809     }
810 
811   roll = atan2 (basis[1].x, basis[1].y);
812 
813   for (i = 0; i < 2; i++)
814     {
815       gimp_tool_gyroscope_rotate_vector (
816         &basis[i], &(GimpVector3) {0.0, 0.0, -roll});
817     }
818 
819   pitch = atan2 (-basis[1].z, basis[1].y);
820 
821   for (i = 0; i < 1; i++)
822     {
823       gimp_tool_gyroscope_rotate_vector (
824         &basis[i], &(GimpVector3) {-pitch, 0.0, 0.0});
825     }
826 
827   yaw = atan2 (basis[0].z, basis[0].x);
828 
829   g_object_set (gyroscope,
830                 "yaw",   yaw   / DEG_TO_RAD,
831                 "pitch", pitch / DEG_TO_RAD,
832                 "roll",  roll  / DEG_TO_RAD,
833                 NULL);
834 }
835 
836 static void
gimp_tool_gyroscope_rotate_vector(GimpVector3 * vector,const GimpVector3 * axis)837 gimp_tool_gyroscope_rotate_vector (GimpVector3       *vector,
838                                    const GimpVector3 *axis)
839 {
840   GimpVector3 normalized_axis;
841   GimpVector3 projection;
842   GimpVector3 u;
843   GimpVector3 v;
844   gdouble     angle;
845 
846   angle = gimp_vector3_length (axis);
847 
848   if (angle < EPSILON)
849     return;
850 
851   normalized_axis = gimp_vector3_mul_val (*axis, 1.0 / angle);
852 
853   projection = gimp_vector3_mul_val (
854     normalized_axis,
855     gimp_vector3_inner_product (vector, &normalized_axis));
856 
857   u = gimp_vector3_sub_val (*vector, projection);
858   v = gimp_vector3_cross_product (&u, &normalized_axis);
859 
860   gimp_vector3_mul (&u, cos (angle));
861   gimp_vector3_mul (&v, sin (angle));
862 
863   gimp_vector3_add (vector, &u, &v);
864   gimp_vector3_add (vector, vector, &projection);
865 }
866 
867 
868 /*  public functions  */
869 
870 
871 GimpToolWidget *
gimp_tool_gyroscope_new(GimpDisplayShell * shell)872 gimp_tool_gyroscope_new (GimpDisplayShell *shell)
873 {
874   g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
875 
876   return g_object_new (GIMP_TYPE_TOOL_GYROSCOPE,
877                        "shell", shell,
878                        NULL);
879 }
880