Skip to content
Open
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
11 changes: 8 additions & 3 deletions lib/Gestures/Backends/GestureBackend.vala
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,15 @@
*/

private interface Gala.GestureBackend : Object {
public enum Unit {
PIXELS,
PERCENTAGE
}

public signal bool on_gesture_detected (Gesture gesture, uint32 timestamp);
public signal void on_begin (double percentage, uint64 time);
public signal void on_update (double percentage, uint64 time);
public signal void on_end (double percentage, uint64 time);
public signal void on_begin (Unit unit, double value, uint64 time);
public signal void on_update (Unit unit, double value, uint64 time);
public signal void on_end (Unit unit, double value, uint64 time);

/**
* The gesture should be cancelled. The implementation should stop emitting
Expand Down
8 changes: 4 additions & 4 deletions lib/Gestures/Backends/ScrollBackend.vala
Original file line number Diff line number Diff line change
Expand Up @@ -98,15 +98,15 @@ private class Gala.ScrollBackend : Object, GestureBackend {
on_gesture_detected (gesture, time);

double delta = calculate_delta (delta_x, delta_y, direction);
on_begin (delta, time);
on_begin (PERCENTAGE, delta, time);
}
} else {
double delta = calculate_delta (delta_x, delta_y, direction);
if (x == 0 && y == 0) {
on_end (delta, time);
on_end (PERCENTAGE, delta, time);
reset ();
} else {
on_update (delta, time);
on_update (PERCENTAGE, delta, time);
}
}

Expand All @@ -119,7 +119,7 @@ private class Gala.ScrollBackend : Object, GestureBackend {
}

double delta = calculate_delta (delta_x, delta_y, direction);
on_end (delta, event.get_time ());
on_end (PERCENTAGE, delta, event.get_time ());
reset ();

return Clutter.EVENT_PROPAGATE;
Expand Down
88 changes: 88 additions & 0 deletions lib/Gestures/Backends/TouchScreenSwipeBackend.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* Copyright 2025 elementary, Inc. (https://elementary.io)
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Authored by: Leonhard Kargl <leo.kargl@proton.me>
*/

#if HAS_MUTTER49
private class Gala.TouchScreenSwipeBackend : Object, GestureBackend {
private const int DRAG_THRESHOLD_DISTANCE = 16;

public Clutter.Actor actor { get; construct; }

private Clutter.PanGesture gesture;
private Clutter.Orientation current_orientation;
private bool cancelled = false;

public TouchScreenSwipeBackend (Clutter.Actor actor) {
Object (actor: actor);
}

construct {
gesture = new Clutter.PanGesture ();
gesture.may_recognize.connect (on_may_recognize);
gesture.recognize.connect (on_pan_recognize);
gesture.pan_update.connect (on_pan_update);
gesture.end.connect (on_pan_end);
actor.add_action (gesture);
}

~TouchScreenSwipeBackend () {
actor.remove_action (gesture);
}

public override void cancel_gesture () {
cancelled = true;
gesture.cancel ();
}

private bool on_may_recognize () {
var detected_gesture = new Gesture () {
type = TOUCHPAD_SWIPE,
direction = get_direction (),
fingers = (int) gesture.get_n_points (),
performed_on_device_type = TOUCHSCREEN_DEVICE
};

return on_gesture_detected (detected_gesture, Clutter.get_current_event_time ());
}

private GestureDirection get_direction () {
var delta = gesture.get_delta ();
current_orientation = delta.get_x ().abs () > delta.get_y ().abs () ? Clutter.Orientation.HORIZONTAL : Clutter.Orientation.VERTICAL;
switch (current_orientation) {
case HORIZONTAL: return RIGHT;
case VERTICAL: return DOWN;
default: return RIGHT;
}
}

private void on_pan_recognize () {
cancelled = false;

on_begin (PIXELS, get_used_distance (), Clutter.get_current_event_time ());
}

private void on_pan_update () {
if (cancelled) {
return;
}

on_update (PIXELS, get_used_distance (), Clutter.get_current_event_time ());
}

private void on_pan_end () {
if (cancelled) {
return;
}

on_end (PIXELS, get_used_distance (), Clutter.get_current_event_time ());
}

private float get_used_distance () {
var distance = gesture.get_accumulated_delta ();
return current_orientation == HORIZONTAL ? distance.get_x () : distance.get_y ();
}
}
#endif
8 changes: 4 additions & 4 deletions lib/Gestures/Backends/ToucheggBackend.vala
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ private class Gala.ToucheggBackend : Object, GestureBackend {
out performed_on_device_type, out elapsed_time);

#if HAS_MUTTER49
if (performed_on_device_type != DeviceType.TOUCHSCREEN) {
if (!(performed_on_device_type == DeviceType.TOUCHSCREEN && type == PINCH)) {
#else
if (performed_on_device_type != DeviceType.TOUCHSCREEN && type != PINCH) {
#endif
Expand All @@ -194,19 +194,19 @@ private class Gala.ToucheggBackend : Object, GestureBackend {
case DBUS_ON_GESTURE_BEGIN:
Idle.add (() => {
on_gesture_detected (make_gesture (type, direction, fingers, performed_on_device_type), Meta.CURRENT_TIME);
on_begin (delta, elapsed_time);
on_begin (PERCENTAGE, delta, elapsed_time);
return false;
}, Priority.DEFAULT);
break;
case DBUS_ON_GESTURE_UPDATE:
Idle.add (() => {
on_update (delta, elapsed_time);
on_update (PERCENTAGE, delta, elapsed_time);
return false;
}, Priority.DEFAULT);
break;
case DBUS_ON_GESTURE_END:
Idle.add (() => {
on_end (delta, elapsed_time);
on_end (PERCENTAGE, delta, elapsed_time);
return false;
}, Priority.DEFAULT);
break;
Expand Down
6 changes: 3 additions & 3 deletions lib/Gestures/Backends/TouchpadPinchBackend.vala
Original file line number Diff line number Diff line change
Expand Up @@ -71,16 +71,16 @@ private class Gala.TouchpadPinchBackend : Object, GestureBackend {

switch (event.get_gesture_phase ()) {
case BEGIN:
on_begin (0, event.get_time ());
on_begin (PERCENTAGE, 0, event.get_time ());
break;

case UPDATE:
on_update (percentage, event.get_time ());
on_update (PERCENTAGE, percentage, event.get_time ());
break;

case END:
case CANCEL:
on_end (percentage, event.get_time ());
on_end (PERCENTAGE, percentage, event.get_time ());
reset ();
break;
}
Expand Down
6 changes: 3 additions & 3 deletions lib/Gestures/Backends/TouchpadSwipeBackend.vala
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ private class Gala.TouchpadSwipeBackend : Object, GestureBackend {
}

state = ONGOING;
on_begin (0, event.get_time ());
on_begin (PERCENTAGE, 0, event.get_time ());
} else if (main_handler) {
foreach (var other_backend in grouped_with) {
other_backend.handle_event (event, false);
Expand All @@ -126,12 +126,12 @@ private class Gala.TouchpadSwipeBackend : Object, GestureBackend {
break;

case UPDATE:
on_update (percentage, event.get_time ());
on_update (PERCENTAGE, percentage, event.get_time ());
break;

case END:
case CANCEL:
on_end (percentage, event.get_time ());
on_end (PERCENTAGE, percentage, event.get_time ());
reset ();
break;
}
Expand Down
49 changes: 29 additions & 20 deletions lib/Gestures/GestureController.vala
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ public class Gala.GestureController : Object {
}
}

public double distance { get; construct set; }
public double overshoot_lower_clamp { get; construct set; default = 0d; }
public double overshoot_upper_clamp { get; construct set; default = 1d; }
public bool follow_natural_scroll { get; set; default = false; }
Expand Down Expand Up @@ -79,8 +78,7 @@ public class Gala.GestureController : Object {

private bool running = false;

private Gee.List<GestureBackend> backends;
private Gee.List<GestureTrigger> triggers;
private Gee.HashMap<GestureBackend, GestureTrigger> backends;

private GestureBackend? recognizing_backend;
private double gesture_progress;
Expand All @@ -97,8 +95,7 @@ public class Gala.GestureController : Object {
}

construct {
backends = new Gee.ArrayList<GestureBackend> ();
triggers = new Gee.ArrayList<GestureTrigger> ();
backends = new Gee.HashMap<GestureBackend, GestureTrigger> ();
}

/**
Expand All @@ -115,25 +112,26 @@ public class Gala.GestureController : Object {
}

public void add_trigger (GestureTrigger trigger) {
triggers.add (trigger);
trigger.enable_backends (this);
}

internal void enable_backend (GestureBackend backend) {
internal void enable_backend (GestureBackend backend, GestureTrigger trigger) requires (
!backends.has_key (backend)
) {
backend.on_gesture_detected.connect (gesture_detected);
backend.on_begin.connect (gesture_begin);
backend.on_update.connect (gesture_update);
backend.on_end.connect (gesture_end);
backends.add (backend);
backends[backend] = trigger;
}

/**
* Allows both GestureControllers to recognize at the same time.
* Make sure to call this after you've added all necessary backends.
*/
public void group_with (GestureController other) {
foreach (var backend in backends) {
foreach (var other_backend in other.backends) {
foreach (var backend in backends.keys) {
foreach (var other_backend in other.backends.keys) {
backend.group_with (other_backend);
other_backend.group_with (backend);
}
Expand All @@ -154,11 +152,8 @@ public class Gala.GestureController : Object {
return false;
}

foreach (var trigger in triggers) {
if (trigger.triggers (gesture)) {
recognizing = true;
break;
}
if (backends[backend].triggers (gesture)) {
recognizing = true;
}

if (recognizing) {
Expand All @@ -180,23 +175,25 @@ public class Gala.GestureController : Object {
return recognizing;
}

private void gesture_begin (double percentage, uint64 elapsed_time) {
private void gesture_begin (GestureBackend.Unit unit, double value, uint64 elapsed_time) {
if (!recognizing) {
return;
}

prepare ();

gesture_progress = progress;
previous_percentage = percentage;
previous_percentage = get_percentage (unit, value);
previous_time = elapsed_time;
}

private void gesture_update (double percentage, uint64 elapsed_time) {
private void gesture_update (GestureBackend.Unit unit, double value, uint64 elapsed_time) {
if (!recognizing) {
return;
}

var percentage = get_percentage (unit, value);

var updated_delta = previous_delta;
if (elapsed_time != previous_time) {
double distance = percentage - previous_percentage;
Expand All @@ -217,13 +214,14 @@ public class Gala.GestureController : Object {
previous_delta = updated_delta;
}

private void gesture_end (double percentage, uint64 elapsed_time) {
private void gesture_end (GestureBackend.Unit unit, double value, uint64 elapsed_time) {
if (!recognizing) {
return;
}

recognizing = false;

var percentage = get_percentage (unit, value);
update_gesture_progress (percentage, previous_delta);

var to = progress;
Expand All @@ -246,6 +244,17 @@ public class Gala.GestureController : Object {
direction_multiplier = 0;
}

private double get_percentage (GestureBackend.Unit unit, double value) {
switch (unit) {
case PIXELS:
return value / target.get_distance (action);
case PERCENTAGE:
return value;
Comment thread
leolost2605 marked this conversation as resolved.
}

return value;
}

private void update_gesture_progress (double percentage, double percentage_delta) {
gesture_progress += ((percentage - percentage_delta) - (previous_percentage - previous_delta)) * direction_multiplier;

Expand Down Expand Up @@ -347,7 +356,7 @@ public class Gala.GestureController : Object {
public void cancel_gesture () {
if (recognizing) {
recognizing_backend.cancel_gesture ();
gesture_end (previous_percentage, previous_time);
gesture_end (PERCENTAGE, previous_percentage, previous_time);
}
}
}
10 changes: 10 additions & 0 deletions lib/Gestures/Targets/RootTarget.vala
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@ public interface Gala.RootTarget : Object, GestureTarget {
*/
public abstract Clutter.Actor? actor { get; }

/**
* Returns the distance the manipulated actor is expected to travel for
* a 100% gesture. This is used so that the actor follows touch screen gestures 1:1.
* This only applies to targets that expect to receive swipe gestures. Pinch gestures
* are not affected.
*/
public virtual double get_distance (GestureAction action) {
return -1;
}

public void add_gesture_controller (GestureController controller) requires (controller.target == null) {
controller.attached (this);
weak_ref (controller.detached);
Expand Down
7 changes: 4 additions & 3 deletions lib/Gestures/Triggers/GlobalTrigger.vala
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,12 @@ public class Gala.GlobalTrigger : Object, GestureTrigger {
}

internal void enable_backends (GestureController controller) {
controller.enable_backend (ToucheggBackend.get_default ());
controller.enable_backend (new TouchpadSwipeBackend (wm.stage));
controller.enable_backend (ToucheggBackend.get_default (), this);
controller.enable_backend (new TouchpadSwipeBackend (wm.stage), this);
#if HAS_MUTTER49
controller.enable_backend (new TouchScreenSwipeBackend (wm.stage), this);
// On mutter < 49 there is a bug that pinch gestures aren't delivered when over a window so rely on touch egg there
controller.enable_backend (new TouchpadPinchBackend (wm.stage));
controller.enable_backend (new TouchpadPinchBackend (wm.stage), this);
#endif
}
}
Loading
Loading