Skip to content

Commit 51fbd75

Browse files
committed
Gestures: Introduce a PanBackend
1 parent cfbd905 commit 51fbd75

File tree

4 files changed

+184
-1
lines changed

4 files changed

+184
-1
lines changed

src/Gestures/GestureTracker.vala

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,12 @@ public class Gala.GestureTracker : Object {
120120
/**
121121
* Backend used if enable_touchpad is called.
122122
*/
123-
private ToucheggBackend touchpad_backend;
123+
private ToucheggBackend? touchpad_backend;
124+
125+
/**
126+
* Pan backend used if enable_pan is called.
127+
*/
128+
private PanBackend pan_backend;
124129

125130
/**
126131
* Scroll backend used if enable_scroll is called.
@@ -159,6 +164,22 @@ public class Gala.GestureTracker : Object {
159164
touchpad_backend.on_end.connect (gesture_end);
160165
}
161166

167+
/**
168+
* Allow to receive pan gestures.
169+
* @param actor Clutter actor that will receive the events.
170+
* @param travel_distance_func this will be called if a gesture is detected and true is returned from {@link on_gesture_detected}.
171+
* The returned distance wil be used to calculate the percentage. It should be set to the amount something will travel (e.g.
172+
* when moving an actor) based on the gesture to allow exact finger tracking. It can also be used
173+
* to calculate the raw pixels the finger travelled at a given time with percentage * distance.
174+
*/
175+
public void enable_pan (WindowManager wm, Clutter.Actor actor, owned PanBackend.GetTravelDistance travel_distance_func) {
176+
pan_backend = new PanBackend (wm, actor, (owned) travel_distance_func);
177+
pan_backend.on_gesture_detected.connect (gesture_detected);
178+
pan_backend.on_begin.connect (gesture_begin);
179+
pan_backend.on_update.connect (gesture_update);
180+
pan_backend.on_end.connect (gesture_end);
181+
}
182+
162183
/**
163184
* Allow to receive scroll gestures.
164185
* @param actor Clutter actor that will receive the scroll events.

src/Gestures/PanBackend.vala

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

src/Gestures/ToucheggBackend.vala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,10 @@ public class Gala.ToucheggBackend : Object, GestureBackend {
192192
signal_params.get ("(uudiut)", out type, out direction, out percentage, out fingers,
193193
out performed_on_device_type, out elapsed_time);
194194

195+
if (performed_on_device_type == TOUCHSCREEN) {
196+
return;
197+
}
198+
195199
var delta = percentage * DELTA_MULTIPLIER;
196200

197201
switch (signal_name) {

src/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ gala_bin_sources = files(
3636
'Gestures/Gesture.vala',
3737
'Gestures/GestureSettings.vala',
3838
'Gestures/GestureTracker.vala',
39+
'Gestures/PanBackend.vala',
3940
'Gestures/ScrollBackend.vala',
4041
'Gestures/ToucheggBackend.vala',
4142
'HotCorners/Barrier.vala',

0 commit comments

Comments
 (0)