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.
This commit is contained in:
Povilas Kanapickas 2022-03-17 14:55:04 +02:00 committed by Jehan
parent e8cf57f157
commit edcbf18fe6
4 changed files with 97 additions and 10 deletions

View File

@ -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

View File

@ -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,

View File

@ -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,

View File

@ -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),