Skip to content

Commit 38db478

Browse files
committed
Introduce a pan backend for touchscreen gestures
1 parent 91e65ff commit 38db478

File tree

5 files changed

+172
-1
lines changed

5 files changed

+172
-1
lines changed

lib/Gestures/GestureController.vala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ public class Gala.GestureController : Object {
8989

9090
private ToucheggBackend? touchegg_backend;
9191
private TouchpadBackend? touchpad_backend;
92+
private PanBackend? pan_backend;
9293
private ScrollBackend? scroll_backend;
9394

9495
private GestureBackend? recognizing_backend;
@@ -134,6 +135,14 @@ public class Gala.GestureController : Object {
134135
touchegg_backend.on_end.connect (gesture_end);
135136
}
136137

138+
public void enable_touchscreen (Clutter.Actor actor, TouchscreenTarget target) {
139+
pan_backend = new PanBackend (wm, actor, target);
140+
pan_backend.on_gesture_detected.connect (gesture_detected);
141+
pan_backend.on_begin.connect (gesture_begin);
142+
pan_backend.on_update.connect (gesture_update);
143+
pan_backend.on_end.connect (gesture_end);
144+
}
145+
137146
public void enable_scroll (Clutter.Actor actor, Clutter.Orientation orientation) {
138147
scroll_backend = new ScrollBackend (actor, orientation, new GestureSettings ());
139148
scroll_backend.on_gesture_detected.connect (gesture_detected);

lib/Gestures/PanBackend.vala

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
/*
2+
* Copyright 2024 elementary, Inc. (https://elementary.io)
3+
* SPDX-License-Identifier: GPL-3.0-or-later
4+
*
5+
* Authored by: Leonhard Kargl <[email protected]>
6+
*/
7+
8+
internal class Gala.PanBackend : Object, GestureBackend {
9+
public WindowManager wm { get; construct; }
10+
public Clutter.Actor actor { get; construct; }
11+
public TouchscreenTarget target { get; construct; }
12+
13+
private ModalProxy? modal_proxy;
14+
15+
private Clutter.PanAxis pan_axis;
16+
private Clutter.PanAction pan_action;
17+
18+
private GestureDirection direction;
19+
20+
private float origin_x;
21+
private float origin_y;
22+
23+
private float current_x;
24+
private float current_y;
25+
26+
private float last_x_coord;
27+
private float last_y_coord;
28+
private uint last_n_points;
29+
30+
public PanBackend (WindowManager wm, Clutter.Actor actor, TouchscreenTarget target) {
31+
Object (wm: wm, actor: actor, target: target);
32+
}
33+
34+
construct {
35+
pan_action = new Clutter.PanAction () {
36+
n_touch_points = 1
37+
};
38+
39+
actor.add_action_full ("pan-gesture", CAPTURE, pan_action);
40+
41+
pan_action.gesture_begin.connect (on_gesture_begin);
42+
pan_action.pan.connect (on_pan);
43+
pan_action.gesture_end.connect (on_gesture_end);
44+
pan_action.gesture_cancel.connect (on_gesture_end);
45+
}
46+
47+
~PanBackend () {
48+
actor.remove_action (pan_action);
49+
}
50+
51+
private bool on_gesture_begin () {
52+
if (pan_action.get_last_event (0).get_source_device ().get_device_type () != TOUCHSCREEN_DEVICE) {
53+
return false;
54+
}
55+
56+
float x_coord, y_coord;
57+
pan_action.get_press_coords (0, out x_coord, out y_coord);
58+
59+
origin_x = current_x = x_coord;
60+
origin_y = current_y = y_coord;
61+
62+
var time = pan_action.get_last_event (0).get_time ();
63+
64+
var handled = on_gesture_detected (build_gesture (), time);
65+
66+
if (!handled) {
67+
reset ();
68+
return false;
69+
}
70+
71+
modal_proxy = wm.push_modal (actor, true);
72+
73+
on_begin (0, time);
74+
75+
return true;
76+
}
77+
78+
private void on_gesture_end () {
79+
on_end (calculate_percentage (), Meta.CURRENT_TIME);
80+
81+
reset ();
82+
}
83+
84+
private void reset () {
85+
if (modal_proxy != null) {
86+
wm.pop_modal (modal_proxy);
87+
modal_proxy = null;
88+
}
89+
90+
direction = GestureDirection.UNKNOWN;
91+
last_n_points = 0;
92+
last_x_coord = 0;
93+
last_y_coord = 0;
94+
}
95+
96+
private bool on_pan (Clutter.PanAction pan_action, Clutter.Actor actor, bool interpolate) {
97+
var time = pan_action.get_last_event (0).get_time ();
98+
99+
float x, y;
100+
pan_action.get_motion_coords (0, out x, out y);
101+
102+
if (pan_action.get_n_current_points () == last_n_points) {
103+
current_x += x - last_x_coord;
104+
current_y += y - last_y_coord;
105+
}
106+
107+
last_x_coord = x;
108+
last_y_coord = y;
109+
last_n_points = pan_action.get_n_current_points ();
110+
111+
on_update (calculate_percentage (), time);
112+
113+
return true;
114+
}
115+
116+
private double calculate_percentage () {
117+
float current, origin;
118+
if (pan_axis == X_AXIS) {
119+
current = direction == RIGHT ? float.max (current_x, origin_x) : float.min (current_x, origin_x);
120+
origin = origin_x;
121+
} else {
122+
current = direction == DOWN ? float.max (current_y, origin_y) : float.min (current_y, origin_y);
123+
origin = origin_y;
124+
}
125+
126+
return (current - origin).abs () / target.travel_distance;
127+
}
128+
129+
private Gesture build_gesture () {
130+
float delta_x, delta_y;
131+
((Clutter.GestureAction) pan_action).get_motion_delta (0, out delta_x, out delta_y);
132+
133+
pan_axis = delta_x.abs () > delta_y.abs () ? Clutter.PanAxis.X_AXIS : Clutter.PanAxis.Y_AXIS;
134+
135+
if (pan_axis == X_AXIS) {
136+
direction = delta_x > 0 ? GestureDirection.RIGHT : GestureDirection.LEFT;
137+
} else {
138+
direction = delta_y > 0 ? GestureDirection.DOWN : GestureDirection.UP;
139+
}
140+
141+
return new Gesture () {
142+
type = Clutter.EventType.TOUCHPAD_SWIPE,
143+
direction = direction,
144+
fingers = (int) pan_action.get_n_current_points (),
145+
performed_on_device_type = Clutter.InputDeviceType.TOUCHSCREEN_DEVICE,
146+
origin_x = origin_x,
147+
origin_y = origin_y
148+
};
149+
}
150+
}

lib/Gestures/ToucheggBackend.vala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ private class Gala.ToucheggBackend : Object, GestureBackend {
180180
signal_params.get ("(uudiut)", out type, out direction, out percentage, out fingers,
181181
out performed_on_device_type, out elapsed_time);
182182

183-
if (Meta.Util.is_wayland_compositor () && performed_on_device_type != DeviceType.TOUCHSCREEN && type != PINCH) {
183+
if (Meta.Util.is_wayland_compositor () && type != PINCH) {
184184
return;
185185
}
186186

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/*
2+
* Copyright 2025 elementary, Inc. (https://elementary.io)
3+
* SPDX-License-Identifier: GPL-3.0-or-later
4+
*
5+
* Authored by: Leonhard Kargl <[email protected]>
6+
*/
7+
8+
public interface Gala.TouchscreenTarget : Object {
9+
public abstract float travel_distance { get; }
10+
}

lib/meson.build

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,14 @@ gala_lib_sources = files(
3131
'Gestures/GestureController.vala',
3232
'Gestures/GestureSettings.vala',
3333
'Gestures/GestureTarget.vala',
34+
'Gestures/PanBackend.vala',
3435
'Gestures/PropertyTarget.vala',
3536
'Gestures/RootTarget.vala',
3637
'Gestures/ScrollBackend.vala',
3738
'Gestures/SpringTimeline.vala',
3839
'Gestures/ToucheggBackend.vala',
3940
'Gestures/TouchpadBackend.vala',
41+
'Gestures/TouchscreenTarget.vala',
4042
'Gestures/WorkspaceHideTracker.vala'
4143
) + gala_common_enums
4244

0 commit comments

Comments
 (0)