Skip to content

Commit d0e98b3

Browse files
committed
Introduce a pan backend for touchscreen gestures
1 parent 4adf25f commit d0e98b3

File tree

6 files changed

+174
-1
lines changed

6 files changed

+174
-1
lines changed

lib/Gestures/GestureBackend.vala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
*/
77

88
private interface Gala.GestureBackend : Object {
9+
public signal float request_travel_distance ();
10+
911
public signal bool on_gesture_detected (Gesture gesture, uint32 timestamp);
1012
public signal void on_begin (double percentage, uint64 time);
1113
public signal void on_update (double percentage, uint64 time);

lib/Gestures/GestureController.vala

Lines changed: 14 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,15 @@ public class Gala.GestureController : Object {
134135
touchegg_backend.on_end.connect (gesture_end);
135136
}
136137

138+
public void enable_touchscreen (Clutter.Actor actor) {
139+
pan_backend = new PanBackend (wm, actor);
140+
pan_backend.request_travel_distance.connect (on_request_travel_distance);
141+
pan_backend.on_gesture_detected.connect (gesture_detected);
142+
pan_backend.on_begin.connect (gesture_begin);
143+
pan_backend.on_update.connect (gesture_update);
144+
pan_backend.on_end.connect (gesture_end);
145+
}
146+
137147
public void enable_scroll (Clutter.Actor actor, Clutter.Orientation orientation) {
138148
scroll_backend = new ScrollBackend (actor, orientation, new GestureSettings ());
139149
scroll_backend.on_gesture_detected.connect (gesture_detected);
@@ -142,6 +152,10 @@ public class Gala.GestureController : Object {
142152
scroll_backend.on_end.connect (gesture_end);
143153
}
144154

155+
private float on_request_travel_distance () {
156+
return target.get_travel_distance (action);
157+
}
158+
145159
private void prepare () {
146160
if (timeline != null) {
147161
timeline = null;

lib/Gestures/PanBackend.vala

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

lib/Gestures/RootTarget.vala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ public interface Gala.RootTarget : Object, GestureTarget {
1212
*/
1313
public abstract Clutter.Actor? actor { get; }
1414

15+
public virtual float get_travel_distance (GestureAction for_action) {
16+
return 0.0f;
17+
}
18+
1519
public void add_gesture_controller (GestureController controller) requires (controller.target == null) {
1620
controller.attached (this);
1721
weak_ref (controller.detached);

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

lib/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ 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',

0 commit comments

Comments
 (0)