diff --git a/lib/Gestures/GestureBackend.vala b/lib/Gestures/GestureBackend.vala index b7b0e1261..81cabc9a1 100644 --- a/lib/Gestures/GestureBackend.vala +++ b/lib/Gestures/GestureBackend.vala @@ -6,6 +6,8 @@ */ private interface Gala.GestureBackend : Object { + public signal float request_travel_distance (); + public signal bool on_gesture_detected (Gesture gesture, uint32 timestamp); public signal void on_begin (double percentage, uint64 time); public signal void on_update (double percentage, uint64 time); diff --git a/lib/Gestures/GestureController.vala b/lib/Gestures/GestureController.vala index aec8241c1..50146a2af 100644 --- a/lib/Gestures/GestureController.vala +++ b/lib/Gestures/GestureController.vala @@ -89,6 +89,7 @@ public class Gala.GestureController : Object { private ToucheggBackend? touchegg_backend; private TouchpadBackend? touchpad_backend; + private PanBackend? pan_backend; private ScrollBackend? scroll_backend; private GestureBackend? recognizing_backend; @@ -134,6 +135,15 @@ public class Gala.GestureController : Object { touchegg_backend.on_end.connect (gesture_end); } + public void enable_touchscreen (Clutter.Actor actor) { + pan_backend = new PanBackend (wm, actor); + pan_backend.request_travel_distance.connect (on_request_travel_distance); + pan_backend.on_gesture_detected.connect (gesture_detected); + pan_backend.on_begin.connect (gesture_begin); + pan_backend.on_update.connect (gesture_update); + pan_backend.on_end.connect (gesture_end); + } + public void enable_scroll (Clutter.Actor actor, Clutter.Orientation orientation) { scroll_backend = new ScrollBackend (actor, orientation, new GestureSettings ()); scroll_backend.on_gesture_detected.connect (gesture_detected); @@ -142,6 +152,10 @@ public class Gala.GestureController : Object { scroll_backend.on_end.connect (gesture_end); } + private float on_request_travel_distance () { + return target.get_travel_distance (action); + } + private void prepare () { if (timeline != null) { timeline = null; @@ -178,8 +192,8 @@ public class Gala.GestureController : Object { return recognizing; } - private void gesture_begin (double percentage, uint64 elapsed_time) { - if (!recognizing) { + private void gesture_begin (GestureBackend backend, double percentage, uint64 elapsed_time) { + if (!recognizing || backend != recognizing_backend) { return; } @@ -190,8 +204,8 @@ public class Gala.GestureController : Object { previous_time = elapsed_time; } - private void gesture_update (double percentage, uint64 elapsed_time) { - if (!recognizing) { + private void gesture_update (GestureBackend backend, double percentage, uint64 elapsed_time) { + if (!recognizing || backend != recognizing_backend) { return; } @@ -215,8 +229,8 @@ public class Gala.GestureController : Object { previous_delta = updated_delta; } - private void gesture_end (double percentage, uint64 elapsed_time) { - if (!recognizing) { + private void gesture_end (GestureBackend backend, double percentage, uint64 elapsed_time) { + if (!recognizing || backend != recognizing_backend) { return; } @@ -311,8 +325,8 @@ public class Gala.GestureController : Object { public void cancel_gesture () { if (recognizing) { + gesture_end (recognizing_backend, previous_percentage, previous_time); recognizing_backend.cancel_gesture (); - gesture_end (previous_percentage, previous_time); } } } diff --git a/lib/Gestures/PanBackend.vala b/lib/Gestures/PanBackend.vala new file mode 100644 index 000000000..6c16a666a --- /dev/null +++ b/lib/Gestures/PanBackend.vala @@ -0,0 +1,152 @@ +/* + * Copyright 2024 elementary, Inc. (https://elementary.io) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Authored by: Leonhard Kargl + */ + +internal class Gala.PanBackend : Object, GestureBackend { + public WindowManager wm { get; construct; } + public Clutter.Actor actor { get; construct; } + + private ModalProxy? modal_proxy; + + private Clutter.PanAxis pan_axis; + private Clutter.PanAction pan_action; + + private GestureDirection direction; + + private float origin_x; + private float origin_y; + + private float current_x; + private float current_y; + + private float last_x_coord; + private float last_y_coord; + private uint last_n_points; + + public PanBackend (WindowManager wm, Clutter.Actor actor) { + Object (wm: wm, actor: actor); + } + + construct { + pan_action = new Clutter.PanAction () { + n_touch_points = 1 + }; + + actor.add_action_full ("pan-gesture", CAPTURE, pan_action); + + pan_action.gesture_begin.connect (on_gesture_begin); + pan_action.pan.connect (on_pan); + pan_action.gesture_end.connect (on_gesture_end); + pan_action.gesture_cancel.connect (on_gesture_end); + } + + ~PanBackend () { + actor.remove_action (pan_action); + } + + private bool on_gesture_begin () { + if (pan_action.get_last_event (0).get_source_device ().get_device_type () != TOUCHSCREEN_DEVICE) { + return false; + } + + float x_coord, y_coord; + pan_action.get_press_coords (0, out x_coord, out y_coord); + + origin_x = current_x = x_coord; + origin_y = current_y = y_coord; + + var time = pan_action.get_last_event (0).get_time (); + + var handled = on_gesture_detected (build_gesture (), time); + + if (!handled) { + reset (); + return false; + } + + modal_proxy = wm.push_modal (actor, true); + + on_begin (0, time); + + return true; + } + + private void on_gesture_end () { + if (modal_proxy != null) { + // Only emit on end if we actually began the gesture + on_end (calculate_percentage (), Meta.CURRENT_TIME); + } + + reset (); + } + + private void reset () { + if (modal_proxy != null) { + wm.pop_modal (modal_proxy); + modal_proxy = null; + } + + direction = GestureDirection.UNKNOWN; + last_n_points = 0; + last_x_coord = 0; + last_y_coord = 0; + } + + private bool on_pan (Clutter.PanAction pan_action, Clutter.Actor actor, bool interpolate) { + var time = pan_action.get_last_event (0).get_time (); + + float x, y; + pan_action.get_motion_coords (0, out x, out y); + + if (pan_action.get_n_current_points () == last_n_points) { + current_x += x - last_x_coord; + current_y += y - last_y_coord; + } + + last_x_coord = x; + last_y_coord = y; + last_n_points = pan_action.get_n_current_points (); + + on_update (calculate_percentage (), time); + + return true; + } + + private double calculate_percentage () { + float current, origin; + if (pan_axis == X_AXIS) { + current = direction == RIGHT ? float.max (current_x, origin_x) : float.min (current_x, origin_x); + origin = origin_x; + } else { + current = direction == DOWN ? float.max (current_y, origin_y) : float.min (current_y, origin_y); + origin = origin_y; + } + + return (current - origin).abs () / request_travel_distance (); + } + + private Gesture build_gesture () { + float delta_x, delta_y; + ((Clutter.GestureAction) pan_action).get_motion_delta (0, out delta_x, out delta_y); + + pan_axis = delta_x.abs () > delta_y.abs () ? Clutter.PanAxis.X_AXIS : Clutter.PanAxis.Y_AXIS; + + if (pan_axis == X_AXIS) { + direction = delta_x > 0 ? GestureDirection.RIGHT : GestureDirection.LEFT; + } else { + direction = delta_y > 0 ? GestureDirection.DOWN : GestureDirection.UP; + } + + return new Gesture () { + type = Clutter.EventType.TOUCHPAD_SWIPE, + direction = direction, + fingers = (int) pan_action.get_n_current_points (), + performed_on_device_type = Clutter.InputDeviceType.TOUCHSCREEN_DEVICE, + origin_x = origin_x, + origin_y = origin_y + }; + } +} diff --git a/lib/Gestures/RootTarget.vala b/lib/Gestures/RootTarget.vala index fac6afcaa..77956f4dd 100644 --- a/lib/Gestures/RootTarget.vala +++ b/lib/Gestures/RootTarget.vala @@ -12,6 +12,10 @@ public interface Gala.RootTarget : Object, GestureTarget { */ public abstract Clutter.Actor? actor { get; } + public virtual float get_travel_distance (GestureAction for_action) { + return 0.0f; + } + public void add_gesture_controller (GestureController controller) requires (controller.target == null) { controller.attached (this); weak_ref (controller.detached); diff --git a/lib/Gestures/ToucheggBackend.vala b/lib/Gestures/ToucheggBackend.vala index c280efc09..1dbb88fe9 100644 --- a/lib/Gestures/ToucheggBackend.vala +++ b/lib/Gestures/ToucheggBackend.vala @@ -180,7 +180,7 @@ private class Gala.ToucheggBackend : Object, GestureBackend { signal_params.get ("(uudiut)", out type, out direction, out percentage, out fingers, out performed_on_device_type, out elapsed_time); - if (Meta.Util.is_wayland_compositor () && performed_on_device_type != DeviceType.TOUCHSCREEN && type != PINCH) { + if (Meta.Util.is_wayland_compositor () && type != PINCH) { return; } diff --git a/lib/meson.build b/lib/meson.build index 94885b458..e3a70dcbd 100644 --- a/lib/meson.build +++ b/lib/meson.build @@ -31,6 +31,7 @@ gala_lib_sources = files( 'Gestures/GestureController.vala', 'Gestures/GestureSettings.vala', 'Gestures/GestureTarget.vala', + 'Gestures/PanBackend.vala', 'Gestures/PropertyTarget.vala', 'Gestures/RootTarget.vala', 'Gestures/ScrollBackend.vala', diff --git a/src/Widgets/MultitaskingView/MultitaskingView.vala b/src/Widgets/MultitaskingView/MultitaskingView.vala index 4ac4ba68f..c654d48fe 100644 --- a/src/Widgets/MultitaskingView/MultitaskingView.vala +++ b/src/Widgets/MultitaskingView/MultitaskingView.vala @@ -63,6 +63,7 @@ public class Gala.MultitaskingView : ActorTarget, RootTarget, ActivatableCompone multitasking_gesture_controller = new GestureController (MULTITASKING_VIEW, wm, MULTITASKING_VIEW); multitasking_gesture_controller.enable_touchpad (wm.stage); + multitasking_gesture_controller.enable_touchscreen (wm.stage); add_gesture_controller (multitasking_gesture_controller); add_target (ShellClientsManager.get_instance ()); // For hiding the panels @@ -74,6 +75,7 @@ public class Gala.MultitaskingView : ActorTarget, RootTarget, ActivatableCompone follow_natural_scroll = true, }; workspaces_gesture_controller.enable_touchpad (wm.stage); + workspaces_gesture_controller.enable_touchscreen (wm.stage); workspaces_gesture_controller.enable_scroll (this, HORIZONTAL); add_gesture_controller (workspaces_gesture_controller); @@ -254,6 +256,14 @@ public class Gala.MultitaskingView : ActorTarget, RootTarget, ActivatableCompone workspaces_gesture_controller.cancel_gesture (); } + public override float get_travel_distance (GestureAction for_action) { + switch (for_action) { + case MULTITASKING_VIEW: return primary_monitor_container.get_height () * 0.5f; + case SWITCH_WORKSPACE: return workspaces.get_first_child ().get_width (); + default: return 0; + } + } + public override void start_progress (GestureAction action) { if (!visible) { opened = true;