[gimp] app: Implement exclusiveness of zoom and rotate gestures



commit edcbf18fe682cf78f8589b4b66208886deb8b0ff
Author: Povilas Kanapickas <povilas radix lt>
Date:   Thu Mar 17 14:55:04 2022 +0200

    app: Implement exclusiveness of zoom and rotate gestures
    
    Performing zoom and rotation at the same time is inconvenient because
    most of the time the user will want either zoom or rotation. This can be
    solved by recognizing a "significant enough" zoom or rotation change
    initiated by the gesture recognizer and then ignoring the other gesture.

 app/display/gimpdisplayshell-tool-events.c | 90 +++++++++++++++++++++++++++---
 app/display/gimpdisplayshell-tool-events.h |  6 ++
 app/display/gimpdisplayshell.c             |  9 ++-
 app/display/gimpdisplayshell.h             |  2 +
 4 files changed, 97 insertions(+), 10 deletions(-)
---
diff --git a/app/display/gimpdisplayshell-tool-events.c b/app/display/gimpdisplayshell-tool-events.c
index 16df65ff58..35bc8b6424 100644
--- a/app/display/gimpdisplayshell-tool-events.c
+++ b/app/display/gimpdisplayshell-tool-events.c
@@ -1255,6 +1255,35 @@ gimp_display_shell_canvas_grab_notify (GtkWidget        *canvas,
     }
 }
 
+/* The ratio of the following defines what finger movement we interpret as
+ * a rotation versus zoom gesture. If finger movement is partially a zoom
+ * and partially a rotation, the detected gesture will be whichever gesture
+ * we detect first
+ *
+ * Let's define "finger movement angle" as the angle between the direction of
+ * finger movement and the line between fingers. If this angle is zero then
+ * the gesture is completely a zoom gesture. If this angle is 90 degrees
+ * then the gesture is completely a rotation gesture.
+ *
+ * The boundary finger movement angle (below which the gesture is zoom gesture
+ * and above which the gesture is rotate gesture) will be defined as follows:
+ *
+ *    boundary = arctan(deg2rad(ROTATE_GESTURE_ACTIVATION_DEG_DIFF) /
+ *                      (ZOOM_GESTURE_ACTIVATION_SCALE_DIFF / 2))
+ *
+ * Note that ZOOM_GESTURE_ACTIVATION_SCALE_DIFF needs to be divided by 2
+ * because both fingers are moving so the distance between them is increasing
+ * twice as fast.
+ *
+ * We probably want boundary angle to be around 60 degrees to prevent
+ * accidentally starting rotations.
+ *
+ * With ZOOM_GESTURE_ACTIVATION_SCALE_DIFF==0.02 and
+ * ROTATE_GESTURE_ACTIVATION_DEG_DIFF==1 boundary is 60.2 degrees.
+ */
+#define ZOOM_GESTURE_ACTIVATION_SCALE_DIFF 0.02
+#define ROTATE_GESTURE_ACTIVATION_DEG_DIFF 1
+
 void
 gimp_display_shell_zoom_gesture_begin (GtkGestureZoom   *gesture,
                                        GdkEventSequence *sequence,
@@ -1268,8 +1297,23 @@ gimp_display_shell_zoom_gesture_update (GtkGestureZoom   *gesture,
                                         GdkEventSequence *sequence,
                                         GimpDisplayShell *shell)
 {
-  gdouble current_scale = gtk_gesture_zoom_get_scale_delta (gesture);
-  gdouble delta = (current_scale - shell->last_zoom_scale) / shell->last_zoom_scale;
+  gdouble current_scale;
+  gdouble delta;
+
+  if (shell->rotate_gesture_active)
+    return;
+
+  /* we only activate zoom gesture handling if rotate gesture was inactive and
+   * the zoom difference is significant enough */
+  current_scale = gtk_gesture_zoom_get_scale_delta (gesture);
+  if (!shell->zoom_gesture_active                              &&
+      current_scale > (1 - ZOOM_GESTURE_ACTIVATION_SCALE_DIFF) &&
+      current_scale < (1 + ZOOM_GESTURE_ACTIVATION_SCALE_DIFF))
+    return;
+
+  shell->zoom_gesture_active = TRUE;
+
+  delta = (current_scale - shell->last_zoom_scale) / shell->last_zoom_scale;
   shell->last_zoom_scale = current_scale;
 
   gimp_display_shell_scale (shell,
@@ -1278,6 +1322,14 @@ gimp_display_shell_zoom_gesture_update (GtkGestureZoom   *gesture,
                             GIMP_ZOOM_FOCUS_POINTER);
 }
 
+void
+gimp_display_shell_zoom_gesture_end (GtkGestureZoom   *gesture,
+                                     GdkEventSequence *sequence,
+                                     GimpDisplayShell *shell)
+{
+  shell->zoom_gesture_active = FALSE;
+}
+
 void
 gimp_display_shell_rotate_gesture_begin (GtkGestureRotate *gesture,
                                          GdkEventSequence *sequence,
@@ -1295,19 +1347,39 @@ gimp_display_shell_rotate_gesture_update (GtkGestureRotate *gesture,
                                           GdkEventSequence *sequence,
                                           GimpDisplayShell *shell)
 {
-  gdouble         angle;
-  gboolean        constrain;
+  gdouble  angle;
+  gdouble  angle_delta_deg;
+  gboolean constrain;
+
+  /* we only activate rotate gesture handling if zoom gesture was inactive and
+   * the rotation is significant enough */
+  if (shell->zoom_gesture_active)
+    return;
+
+  angle_delta_deg = 180.0 * gtk_gesture_rotate_get_angle_delta (gesture) / G_PI;
+  if (!shell->rotate_gesture_active                         &&
+      angle_delta_deg > -ROTATE_GESTURE_ACTIVATION_DEG_DIFF &&
+      angle_delta_deg < ROTATE_GESTURE_ACTIVATION_DEG_DIFF)
+    return;
+
+  shell->rotate_gesture_active = TRUE;
+
+  angle = shell->initial_gesture_rotate_angle + angle_delta_deg;
 
   gimp_display_shell_rotate_gesture_maybe_get_state (gesture, sequence,
                                                      &shell->last_gesture_rotate_state);
 
-  angle = shell->initial_gesture_rotate_angle +
-          180.0 * gtk_gesture_rotate_get_angle_delta (gesture) / G_PI;
-
   constrain = (shell->last_gesture_rotate_state & GDK_CONTROL_MASK) ? TRUE : FALSE;
 
-  gimp_display_shell_rotate_to (shell,
-                                constrain ? RINT (angle / 15.0) * 15.0 : angle);
+  gimp_display_shell_rotate_to (shell, constrain ? RINT (angle / 15.0) * 15.0 : angle);
+}
+
+void
+gimp_display_shell_rotate_gesture_end (GtkGestureRotate *gesture,
+                                       GdkEventSequence *sequence,
+                                       GimpDisplayShell *shell)
+{
+  shell->rotate_gesture_active = FALSE;
 }
 
 void
diff --git a/app/display/gimpdisplayshell-tool-events.h b/app/display/gimpdisplayshell-tool-events.h
index a491999d04..0e3cf93e02 100644
--- a/app/display/gimpdisplayshell-tool-events.h
+++ b/app/display/gimpdisplayshell-tool-events.h
@@ -36,6 +36,9 @@ void       gimp_display_shell_zoom_gesture_begin      (GtkGestureZoom   *gesture
 void       gimp_display_shell_zoom_gesture_update     (GtkGestureZoom   *gesture,
                                                        GdkEventSequence *sequence,
                                                        GimpDisplayShell *shell);
+void       gimp_display_shell_zoom_gesture_end        (GtkGestureZoom   *gesture,
+                                                       GdkEventSequence *sequence,
+                                                       GimpDisplayShell *shell);
 
 void       gimp_display_shell_rotate_gesture_begin    (GtkGestureRotate *gesture,
                                                        GdkEventSequence *sequence,
@@ -43,6 +46,9 @@ void       gimp_display_shell_rotate_gesture_begin    (GtkGestureRotate *gesture
 void       gimp_display_shell_rotate_gesture_update   (GtkGestureRotate *gesture,
                                                        GdkEventSequence *sequence,
                                                        GimpDisplayShell *shell);
+void       gimp_display_shell_rotate_gesture_end      (GtkGestureRotate *gesture,
+                                                       GdkEventSequence *sequence,
+                                                       GimpDisplayShell *shell);
 
 void       gimp_display_shell_buffer_stroke           (GimpMotionBuffer *buffer,
                                                        const GimpCoords *coords,
diff --git a/app/display/gimpdisplayshell.c b/app/display/gimpdisplayshell.c
index 8ca3d1efa4..066d935c25 100644
--- a/app/display/gimpdisplayshell.c
+++ b/app/display/gimpdisplayshell.c
@@ -515,10 +515,12 @@ gimp_display_shell_constructed (GObject *object)
   shell->zoom_gesture = gtk_gesture_zoom_new (GTK_WIDGET (shell->canvas));
   gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (shell->zoom_gesture),
                                               GTK_PHASE_CAPTURE);
+  shell->zoom_gesture_active = FALSE;
 
   shell->rotate_gesture = gtk_gesture_rotate_new (GTK_WIDGET (shell->canvas));
   gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (shell->rotate_gesture),
                                               GTK_PHASE_CAPTURE);
+  shell->rotate_gesture_active = FALSE;
 
   /*  the horizontal ruler  */
   shell->hrule = gimp_ruler_new (GTK_ORIENTATION_HORIZONTAL);
@@ -612,13 +614,18 @@ gimp_display_shell_constructed (GObject *object)
   g_signal_connect (shell->zoom_gesture, "update",
                     G_CALLBACK (gimp_display_shell_zoom_gesture_update),
                     shell);
+  g_signal_connect (shell->zoom_gesture, "end",
+                    G_CALLBACK (gimp_display_shell_zoom_gesture_end),
+                    shell);
   g_signal_connect (shell->rotate_gesture, "begin",
                     G_CALLBACK (gimp_display_shell_rotate_gesture_begin),
                     shell);
   g_signal_connect (shell->rotate_gesture, "update",
                     G_CALLBACK (gimp_display_shell_rotate_gesture_update),
                     shell);
-
+  g_signal_connect (shell->rotate_gesture, "end",
+                    G_CALLBACK (gimp_display_shell_rotate_gesture_end),
+                    shell);
 
   /*  the zoom button  */
   shell->zoom_button = g_object_new (GTK_TYPE_CHECK_BUTTON,
diff --git a/app/display/gimpdisplayshell.h b/app/display/gimpdisplayshell.h
index 7a4a0a41f3..9b81c639e4 100644
--- a/app/display/gimpdisplayshell.h
+++ b/app/display/gimpdisplayshell.h
@@ -203,10 +203,12 @@ struct _GimpDisplayShell
 
   /*  the state of gimp_display_shell_zoom_gesture_*() */
   gdouble            last_zoom_scale;
+  gboolean           zoom_gesture_active;
 
   /*  the state of gimp_display_shell_rotate_gesture_*() */
   guint              last_gesture_rotate_state;
   gdouble            initial_gesture_rotate_angle;
+  gboolean           rotate_gesture_active;
 
   /* Two states are possible when the shell is grabbed: it can be
    * grabbed with space (or space+button1 which is the same state),


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]