Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce a new PanBackend for the gesture tracker #2095

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions lib/Utils.vala
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,31 @@

namespace Gala {
public class Utils {
/**
* Represents a size as an object in order for it to be updatable.
* Additionally it has some utilities like {@link Gala.Utils.Size.actor_tracking}.
*/
public class Size {
public float width;
public float height;

public Size (float width, float height) {
this.width = width;
this.height = height;
}

/**
* Constructs a new size that will keep in sync with the width and height of an actor.
*/
public Size.actor_tracking (Clutter.Actor actor) {
actor.notify["width"].connect ((obj, pspec) => width = ((Clutter.Actor) obj).width);
actor.notify["height"].connect ((obj, pspec) => height = ((Clutter.Actor) obj).height);

width = actor.width;
height = actor.height;
}
}

private struct CachedIcon {
public Gdk.Pixbuf icon;
public int icon_size;
Expand Down
16 changes: 16 additions & 0 deletions src/Gestures/Gesture.vala
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,25 @@ namespace Gala {
}

public class Gesture {
public const float INVALID_COORD = float.MAX;

public Clutter.EventType type;
public GestureDirection direction;
public int fingers;
public Clutter.InputDeviceType performed_on_device_type;

/**
* The x coordinate of the initial contact point for the gesture.
* Doesn't have to be set. In that case it is set to {@link INVALID_COORD}.
* Currently the only backend not setting this is {@link GestureTracker.enable_touchpad}.
*/
public float origin_x = INVALID_COORD;

/**
* The y coordinate of the initial contact point for the gesture.
* Doesn't have to be set. In that case it is set to {@link INVALID_COORD}.
* Currently the only backend not setting this is {@link GestureTracker.enable_touchpad}.
*/
public float origin_y = INVALID_COORD;
}
}
45 changes: 41 additions & 4 deletions src/Gestures/GestureTracker.vala
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,10 @@ public class Gala.GestureTracker : Object {
* If the receiving code needs to handle this gesture, it should call to connect_handlers to
* start receiving updates.
* @param gesture Information about the gesture.
* @return true if the gesture will be handled false otherwise. If false is returned the other
* signals may still be emitted but aren't guaranteed to be.
*/
public signal void on_gesture_detected (Gesture gesture);
public signal bool on_gesture_detected (Gesture gesture);

/**
* Emitted right after on_gesture_detected with the initial gesture information.
Expand All @@ -98,7 +100,12 @@ public class Gala.GestureTracker : Object {
/**
* Backend used if enable_touchpad is called.
*/
private ToucheggBackend touchpad_backend;
private ToucheggBackend? touchpad_backend;

/**
* Pan backend used if enable_pan is called.
*/
private PanBackend pan_backend;

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

/**
* Allow to receive pan gestures.
* @param actor Clutter actor that will receive the events.
* @param travel_distances {@link Utils.Size} with width and height. The given size wil be
* used to calculate the percentage. It should be set to the amount something will travel (e.g.
* when moving an actor) based on the gesture to allow exact finger tracking. It can also be used
* to calculate the raw pixels the finger travelled at a given time with percentage * corresponding distance
* (height for {@link GestureDirection.UP} or DOWN, width for LEFT or RIGHT). If set to null the size of the
* actor will be used. If the values change those changes will apply.
*/
public void enable_pan (Clutter.Actor actor, Utils.Size? travel_distances) {
pan_backend = new PanBackend (actor, travel_distances);
pan_backend.on_gesture_detected.connect (gesture_detected);
pan_backend.on_begin.connect ((percentage, time) => {
gesture_begin (percentage, time);
if (touchpad_backend != null) {
touchpad_backend.ignore_touchscreen = true;
}
});
pan_backend.on_update.connect (gesture_update);
pan_backend.on_end.connect ((percentage, time) => {
gesture_end (percentage, time);
if (touchpad_backend != null) {
touchpad_backend.ignore_touchscreen = false;
}
});
}

/**
* Allow to receive scroll gestures.
* @param actor Clutter actor that will receive the scroll events.
Expand Down Expand Up @@ -201,10 +236,12 @@ public class Gala.GestureTracker : Object {
return value;
}

private void gesture_detected (Gesture gesture) {
private bool gesture_detected (Gesture gesture) {
if (enabled) {
on_gesture_detected (gesture);
return on_gesture_detected (gesture);
}

return false;
}

private void gesture_begin (double percentage, uint64 elapsed_time) {
Expand Down
114 changes: 114 additions & 0 deletions src/Gestures/PanBackend.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* Copyright 2024 elementary, Inc. (https://elementary.io)
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Authored by: Leonhard Kargl <[email protected]>
*/

public class Gala.PanBackend : Object {
public signal bool on_gesture_detected (Gesture gesture);
public signal void on_begin (double delta, uint64 time);
public signal void on_update (double delta, uint64 time);
public signal void on_end (double delta, uint64 time);

public Clutter.Actor actor { get; construct; }
public Utils.Size? travel_distances { get; construct; }

private Clutter.PanAxis pan_axis;
private Clutter.PanAction pan_action;

private GestureDirection direction;

private float origin_x;
private float origin_y;

public PanBackend (Clutter.Actor actor, Utils.Size? travel_distances) {
Object (actor: actor, travel_distances: travel_distances);
}

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);
}

private bool on_gesture_begin () {
float x_coord, y_coord;
pan_action.get_press_coords (0, out x_coord, out y_coord);

origin_x = x_coord;
origin_y = y_coord;

var handled = on_gesture_detected (build_gesture ());

if (!handled) {
return false;
}

on_begin (0, pan_action.get_last_event (0).get_time ());

return true;
}

private void on_gesture_end () {
float x_coord, y_coord;
pan_action.get_motion_coords (0, out x_coord, out y_coord);
on_end (calculate_percentage (x_coord, y_coord), pan_action.get_last_event (0).get_time ());

direction = GestureDirection.UNKNOWN;
}

private bool on_pan (Clutter.PanAction pan_action, Clutter.Actor actor, bool interpolate) {
uint64 time = pan_action.get_last_event (0).get_time ();

float x_coord, y_coord;
pan_action.get_motion_coords (0, out x_coord, out y_coord);

on_update (calculate_percentage (x_coord, y_coord), time);

return true;
}

private double calculate_percentage (float current_x, float current_y) {
float current, origin, size;
if (pan_axis == X_AXIS) {
current = direction == RIGHT ? float.max (current_x, origin_x) : float.min (current_x, origin_x);
origin = origin_x;
size = travel_distances != null ? travel_distances.width : actor.width;
} else {
current = direction == DOWN ? float.max (current_y, origin_y) : float.min (current_y, origin_y);
origin = origin_y;
size = travel_distances != null ? travel_distances.height : actor.height;
}

return (current - origin).abs () / size;
}

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
};
}
}
12 changes: 8 additions & 4 deletions src/Gestures/ScrollBackend.vala
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public class Gala.ScrollBackend : Object {
private const double FINISH_DELTA_HORIZONTAL = 40;
private const double FINISH_DELTA_VERTICAL = 30;

public signal void on_gesture_detected (Gesture gesture);
public signal bool on_gesture_detected (Gesture gesture);
public signal void on_begin (double delta, uint64 time);
public signal void on_update (double delta, uint64 time);
public signal void on_end (double delta, uint64 time);
Expand Down Expand Up @@ -80,7 +80,9 @@ public class Gala.ScrollBackend : Object {

if (!started) {
if (delta_x != 0 || delta_y != 0) {
Gesture gesture = build_gesture (delta_x, delta_y, orientation);
float origin_x, origin_y;
event.get_coords (out origin_x, out origin_y);
Gesture gesture = build_gesture (origin_x, origin_y, delta_x, delta_y, orientation);
started = true;
direction = gesture.direction;
on_gesture_detected (gesture);
Expand Down Expand Up @@ -114,7 +116,7 @@ public class Gala.ScrollBackend : Object {
&& event.get_scroll_direction () == Clutter.ScrollDirection.SMOOTH;
}

private static Gesture build_gesture (double delta_x, double delta_y, Clutter.Orientation orientation) {
private static Gesture build_gesture (float origin_x, float origin_y, double delta_x, double delta_y, Clutter.Orientation orientation) {
GestureDirection direction;
if (orientation == Clutter.Orientation.HORIZONTAL) {
direction = delta_x > 0 ? GestureDirection.RIGHT : GestureDirection.LEFT;
Expand All @@ -126,7 +128,9 @@ public class Gala.ScrollBackend : Object {
type = Clutter.EventType.SCROLL,
direction = direction,
fingers = 2,
performed_on_device_type = Clutter.InputDeviceType.TOUCHPAD_DEVICE
performed_on_device_type = Clutter.InputDeviceType.TOUCHPAD_DEVICE,
origin_x = origin_x,
origin_y = origin_y
};
}

Expand Down
8 changes: 7 additions & 1 deletion src/Gestures/ToucheggBackend.vala
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@
* See: [[https://github.com/JoseExposito/touchegg]]
*/
public class Gala.ToucheggBackend : Object {
public signal void on_gesture_detected (Gesture gesture);
public signal bool on_gesture_detected (Gesture gesture);
public signal void on_begin (double delta, uint64 time);
public signal void on_update (double delta, uint64 time);
public signal void on_end (double delta, uint64 time);

public bool ignore_touchscreen { get; set; default = false; }

/**
* Gesture type as returned by the daemon.
*/
Expand Down Expand Up @@ -197,6 +199,10 @@ public class Gala.ToucheggBackend : Object {
signal_params.get ("(uudiut)", out type, out direction, out percentage, out fingers,
out performed_on_device_type, out elapsed_time);

if (ignore_touchscreen && performed_on_device_type == TOUCHSCREEN) {
return;
}

var delta = percentage * DELTA_MULTIPLIER;

switch (signal_name) {
Expand Down
15 changes: 11 additions & 4 deletions src/Widgets/MultitaskingView.vala
Original file line number Diff line number Diff line change
Expand Up @@ -290,24 +290,28 @@ namespace Gala {
workspaces.add_transition ("nudge", nudge);
}

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

if (gesture.direction == GestureDirection.UP && !opened) {
toggle (true, false);
return true;
} else if (gesture.direction == GestureDirection.DOWN && opened) {
toggle (true, false);
return true;
}

return false;
}

private void on_workspace_gesture_detected (Gesture gesture) {
private bool on_workspace_gesture_detected (Gesture gesture) {
if (!opened) {
return;
return false;
}

var can_handle_swipe = gesture.type == Clutter.EventType.TOUCHPAD_SWIPE &&
Expand All @@ -319,7 +323,10 @@ namespace Gala {
if (gesture.type == Clutter.EventType.SCROLL || (can_handle_swipe && fingers)) {
var direction = workspace_gesture_tracker.settings.get_natural_scroll_direction (gesture);
switch_workspace_with_gesture (direction);
return true;
}

return false;
}

private void switch_workspace_with_gesture (Meta.MotionDirection direction) {
Expand Down
Loading