Skip to content

Commit 7b35604

Browse files
committed
Introduce a pan backend for the gesture tracker
1 parent 4c11882 commit 7b35604

File tree

10 files changed

+236
-20
lines changed

10 files changed

+236
-20
lines changed

lib/Utils.vala

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,31 @@
1717

1818
namespace Gala {
1919
public class Utils {
20+
/**
21+
* Represents a size as an object in order for it to be updatable.
22+
* Additionally it has some utilities like {@link Gala.Utils.Size.actor_tracking}.
23+
*/
24+
public class Size {
25+
public float width;
26+
public float height;
27+
28+
public Size (float width, float height) {
29+
this.width = width;
30+
this.height = height;
31+
}
32+
33+
/**
34+
* Constructs a new size that will keep in sync with the width and height of an actor.
35+
*/
36+
public Size.actor_tracking (Clutter.Actor actor) {
37+
actor.notify["width"].connect ((obj, pspec) => width = ((Clutter.Actor) obj).width);
38+
actor.notify["height"].connect ((obj, pspec) => height = ((Clutter.Actor) obj).height);
39+
40+
width = actor.width;
41+
height = actor.height;
42+
}
43+
}
44+
2045
private struct CachedIcon {
2146
public Gdk.Pixbuf icon;
2247
public int icon_size;

src/Gestures/Gesture.vala

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,25 @@ namespace Gala {
3535
}
3636

3737
public class Gesture {
38+
public const float INVALID_COORD = float.MAX;
39+
3840
public Clutter.EventType type;
3941
public GestureDirection direction;
4042
public int fingers;
4143
public Clutter.InputDeviceType performed_on_device_type;
44+
45+
/**
46+
* The x coordinate of the initial contact point for the gesture.
47+
* Doesn't have to be set. In that case it is set to {@link INVALID_COORD}.
48+
* Currently the only backend not setting this is {@link GestureTracker.enable_touchpad}.
49+
*/
50+
public float origin_x = INVALID_COORD;
51+
52+
/**
53+
* The y coordinate of the initial contact point for the gesture.
54+
* Doesn't have to be set. In that case it is set to {@link INVALID_COORD}.
55+
* Currently the only backend not setting this is {@link GestureTracker.enable_touchpad}.
56+
*/
57+
public float origin_y = INVALID_COORD;
4258
}
4359
}

src/Gestures/GestureTracker.vala

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,10 @@ public class Gala.GestureTracker : Object {
7171
* If the receiving code needs to handle this gesture, it should call to connect_handlers to
7272
* start receiving updates.
7373
* @param gesture Information about the gesture.
74+
* @return true if the gesture will be handled false otherwise. If false is returned the other
75+
* signals may still be emitted but aren't guaranteed to be.
7476
*/
75-
public signal void on_gesture_detected (Gesture gesture);
77+
public signal bool on_gesture_detected (Gesture gesture);
7678

7779
/**
7880
* Emitted right after on_gesture_detected with the initial gesture information.
@@ -98,7 +100,12 @@ public class Gala.GestureTracker : Object {
98100
/**
99101
* Backend used if enable_touchpad is called.
100102
*/
101-
private ToucheggBackend touchpad_backend;
103+
private ToucheggBackend? touchpad_backend;
104+
105+
/**
106+
* Pan backend used if enable_pan is called.
107+
*/
108+
private PanBackend pan_backend;
102109

103110
/**
104111
* Scroll backend used if enable_scroll is called.
@@ -137,6 +144,34 @@ public class Gala.GestureTracker : Object {
137144
touchpad_backend.on_end.connect (gesture_end);
138145
}
139146

147+
/**
148+
* Allow to receive pan gestures.
149+
* @param actor Clutter actor that will receive the events.
150+
* @param travel_distances {@link Utils.Size} with width and height. The given size wil be
151+
* used to calculate the percentage. It should be set to the amount something will travel (e.g.
152+
* when moving an actor) based on the gesture to allow exact finger tracking. It can also be used
153+
* to calculate the raw pixels the finger travelled at a given time with percentage * corresponding distance
154+
* (height for {@link GestureDirection.UP} or DOWN, width for LEFT or RIGHT). If set to null the size of the
155+
* actor will be used. If the values change those changes will apply.
156+
*/
157+
public void enable_pan (Clutter.Actor actor, Utils.Size? travel_distances) {
158+
pan_backend = new PanBackend (actor, travel_distances);
159+
pan_backend.on_gesture_detected.connect (gesture_detected);
160+
pan_backend.on_begin.connect ((percentage, time) => {
161+
gesture_begin (percentage, time);
162+
if (touchpad_backend != null) {
163+
touchpad_backend.ignore_touchscreen = true;
164+
}
165+
});
166+
pan_backend.on_update.connect (gesture_update);
167+
pan_backend.on_end.connect ((percentage, time) => {
168+
gesture_end (percentage, time);
169+
if (touchpad_backend != null) {
170+
touchpad_backend.ignore_touchscreen = false;
171+
}
172+
});
173+
}
174+
140175
/**
141176
* Allow to receive scroll gestures.
142177
* @param actor Clutter actor that will receive the scroll events.
@@ -201,10 +236,12 @@ public class Gala.GestureTracker : Object {
201236
return value;
202237
}
203238

204-
private void gesture_detected (Gesture gesture) {
239+
private bool gesture_detected (Gesture gesture) {
205240
if (enabled) {
206-
on_gesture_detected (gesture);
241+
return on_gesture_detected (gesture);
207242
}
243+
244+
return false;
208245
}
209246

210247
private void gesture_begin (double percentage, uint64 elapsed_time) {

src/Gestures/PanBackend.vala

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
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+
public class Gala.PanBackend : Object {
9+
public signal bool on_gesture_detected (Gesture gesture);
10+
public signal void on_begin (double delta, uint64 time);
11+
public signal void on_update (double delta, uint64 time);
12+
public signal void on_end (double delta, uint64 time);
13+
14+
public Clutter.Actor actor { get; construct; }
15+
public Utils.Size? travel_distances { get; construct; }
16+
17+
private Clutter.PanAxis pan_axis;
18+
private Clutter.PanAction pan_action;
19+
20+
private GestureDirection direction;
21+
22+
private float origin_x;
23+
private float origin_y;
24+
25+
public PanBackend (Clutter.Actor actor, Utils.Size? travel_distances) {
26+
Object (actor: actor, travel_distances: travel_distances);
27+
}
28+
29+
construct {
30+
pan_action = new Clutter.PanAction () {
31+
n_touch_points = 1
32+
};
33+
34+
actor.add_action_full ("pan-gesture", CAPTURE, pan_action);
35+
36+
pan_action.gesture_begin.connect (on_gesture_begin);
37+
pan_action.pan.connect (on_pan);
38+
pan_action.gesture_end.connect (on_gesture_end);
39+
}
40+
41+
private bool on_gesture_begin () {
42+
float x_coord, y_coord;
43+
pan_action.get_press_coords (0, out x_coord, out y_coord);
44+
45+
origin_x = x_coord;
46+
origin_y = y_coord;
47+
48+
var handled = on_gesture_detected (build_gesture ());
49+
50+
if (!handled) {
51+
return false;
52+
}
53+
54+
on_begin (0, pan_action.get_last_event (0).get_time ());
55+
56+
return true;
57+
}
58+
59+
private void on_gesture_end () {
60+
float x_coord, y_coord;
61+
pan_action.get_motion_coords (0, out x_coord, out y_coord);
62+
on_end (calculate_percentage (x_coord, y_coord), pan_action.get_last_event (0).get_time ());
63+
64+
direction = GestureDirection.UNKNOWN;
65+
}
66+
67+
private bool on_pan (Clutter.PanAction pan_action, Clutter.Actor actor, bool interpolate) {
68+
uint64 time = pan_action.get_last_event (0).get_time ();
69+
70+
float x_coord, y_coord;
71+
pan_action.get_motion_coords (0, out x_coord, out y_coord);
72+
73+
on_update (calculate_percentage (x_coord, y_coord), time);
74+
75+
return true;
76+
}
77+
78+
private double calculate_percentage (float current_x, float current_y) {
79+
float current, origin, size;
80+
if (pan_axis == X_AXIS) {
81+
current = direction == RIGHT ? float.max (current_x, origin_x) : float.min (current_x, origin_x);
82+
origin = origin_x;
83+
size = travel_distances != null ? travel_distances.width : actor.width;
84+
} else {
85+
current = direction == DOWN ? float.max (current_y, origin_y) : float.min (current_y, origin_y);
86+
origin = origin_y;
87+
size = travel_distances != null ? travel_distances.height : actor.height;
88+
}
89+
90+
return (current - origin).abs () / size;
91+
}
92+
93+
private Gesture build_gesture () {
94+
float delta_x, delta_y;
95+
((Clutter.GestureAction) pan_action).get_motion_delta (0, out delta_x, out delta_y);
96+
97+
pan_axis = delta_x.abs () > delta_y.abs () ? Clutter.PanAxis.X_AXIS : Clutter.PanAxis.Y_AXIS;
98+
99+
if (pan_axis == X_AXIS) {
100+
direction = delta_x > 0 ? GestureDirection.RIGHT : GestureDirection.LEFT;
101+
} else {
102+
direction = delta_y > 0 ? GestureDirection.DOWN : GestureDirection.UP;
103+
}
104+
105+
return new Gesture () {
106+
type = Clutter.EventType.TOUCHPAD_SWIPE,
107+
direction = direction,
108+
fingers = (int) pan_action.get_n_current_points (),
109+
performed_on_device_type = Clutter.InputDeviceType.TOUCHSCREEN_DEVICE,
110+
origin_x = origin_x,
111+
origin_y = origin_y
112+
};
113+
}
114+
}

src/Gestures/ScrollBackend.vala

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public class Gala.ScrollBackend : Object {
2626
private const double FINISH_DELTA_HORIZONTAL = 40;
2727
private const double FINISH_DELTA_VERTICAL = 30;
2828

29-
public signal void on_gesture_detected (Gesture gesture);
29+
public signal bool on_gesture_detected (Gesture gesture);
3030
public signal void on_begin (double delta, uint64 time);
3131
public signal void on_update (double delta, uint64 time);
3232
public signal void on_end (double delta, uint64 time);
@@ -80,7 +80,9 @@ public class Gala.ScrollBackend : Object {
8080

8181
if (!started) {
8282
if (delta_x != 0 || delta_y != 0) {
83-
Gesture gesture = build_gesture (delta_x, delta_y, orientation);
83+
float origin_x, origin_y;
84+
event.get_coords (out origin_x, out origin_y);
85+
Gesture gesture = build_gesture (origin_x, origin_y, delta_x, delta_y, orientation);
8486
started = true;
8587
direction = gesture.direction;
8688
on_gesture_detected (gesture);
@@ -114,7 +116,7 @@ public class Gala.ScrollBackend : Object {
114116
&& event.get_scroll_direction () == Clutter.ScrollDirection.SMOOTH;
115117
}
116118

117-
private static Gesture build_gesture (double delta_x, double delta_y, Clutter.Orientation orientation) {
119+
private static Gesture build_gesture (float origin_x, float origin_y, double delta_x, double delta_y, Clutter.Orientation orientation) {
118120
GestureDirection direction;
119121
if (orientation == Clutter.Orientation.HORIZONTAL) {
120122
direction = delta_x > 0 ? GestureDirection.RIGHT : GestureDirection.LEFT;
@@ -126,7 +128,9 @@ public class Gala.ScrollBackend : Object {
126128
type = Clutter.EventType.SCROLL,
127129
direction = direction,
128130
fingers = 2,
129-
performed_on_device_type = Clutter.InputDeviceType.TOUCHPAD_DEVICE
131+
performed_on_device_type = Clutter.InputDeviceType.TOUCHPAD_DEVICE,
132+
origin_x = origin_x,
133+
origin_y = origin_y
130134
};
131135
}
132136

src/Gestures/ToucheggBackend.vala

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,13 @@
2121
* See: [[https://github.com/JoseExposito/touchegg]]
2222
*/
2323
public class Gala.ToucheggBackend : Object {
24-
public signal void on_gesture_detected (Gesture gesture);
24+
public signal bool on_gesture_detected (Gesture gesture);
2525
public signal void on_begin (double delta, uint64 time);
2626
public signal void on_update (double delta, uint64 time);
2727
public signal void on_end (double delta, uint64 time);
2828

29+
public bool ignore_touchscreen { get; set; default = false; }
30+
2931
/**
3032
* Gesture type as returned by the daemon.
3133
*/
@@ -197,6 +199,10 @@ public class Gala.ToucheggBackend : Object {
197199
signal_params.get ("(uudiut)", out type, out direction, out percentage, out fingers,
198200
out performed_on_device_type, out elapsed_time);
199201

202+
if (ignore_touchscreen && performed_on_device_type == TOUCHSCREEN) {
203+
return;
204+
}
205+
200206
var delta = percentage * DELTA_MULTIPLIER;
201207

202208
switch (signal_name) {

src/Widgets/MultitaskingView.vala

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -290,24 +290,28 @@ namespace Gala {
290290
workspaces.add_transition ("nudge", nudge);
291291
}
292292

293-
private void on_multitasking_gesture_detected (Gesture gesture) {
293+
private bool on_multitasking_gesture_detected (Gesture gesture) {
294294
if (gesture.type != Clutter.EventType.TOUCHPAD_SWIPE ||
295295
(gesture.fingers == 3 && GestureSettings.get_string ("three-finger-swipe-up") != "multitasking-view") ||
296296
(gesture.fingers == 4 && GestureSettings.get_string ("four-finger-swipe-up") != "multitasking-view")
297297
) {
298-
return;
298+
return false;
299299
}
300300

301301
if (gesture.direction == GestureDirection.UP && !opened) {
302302
toggle (true, false);
303+
return true;
303304
} else if (gesture.direction == GestureDirection.DOWN && opened) {
304305
toggle (true, false);
306+
return true;
305307
}
308+
309+
return false;
306310
}
307311

308-
private void on_workspace_gesture_detected (Gesture gesture) {
312+
private bool on_workspace_gesture_detected (Gesture gesture) {
309313
if (!opened) {
310-
return;
314+
return false;
311315
}
312316

313317
var can_handle_swipe = gesture.type == Clutter.EventType.TOUCHPAD_SWIPE &&
@@ -319,7 +323,10 @@ namespace Gala {
319323
if (gesture.type == Clutter.EventType.SCROLL || (can_handle_swipe && fingers)) {
320324
var direction = workspace_gesture_tracker.settings.get_natural_scroll_direction (gesture);
321325
switch_workspace_with_gesture (direction);
326+
return true;
322327
}
328+
329+
return false;
323330
}
324331

325332
private void switch_workspace_with_gesture (Meta.MotionDirection direction) {

0 commit comments

Comments
 (0)