-
-
Notifications
You must be signed in to change notification settings - Fork 76
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
741f2ac
commit f61800f
Showing
4 changed files
with
423 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,202 @@ | ||
/* | ||
* Copyright 2025 elementary, Inc. (https://elementary.io) | ||
* SPDX-License-Identifier: GPL-3.0-or-later | ||
* | ||
* Authored by: Leonhard Kargl <leo.kargl@proton.me> | ||
*/ | ||
|
||
public abstract class Gala.GestureTarget : Object { | ||
/** | ||
* The actor manipulated by the gesture. The associated frame clock | ||
* will be used for animation timelines. | ||
*/ | ||
public Clutter.Actor actor { get; construct; } | ||
|
||
public abstract void update (double progress); | ||
} | ||
|
||
public class Gala.GestureController : Object { | ||
/** | ||
* When a gesture ends with a velocity greater than this constant, the action is not cancelled, | ||
* even if the animation threshold has not been reached. | ||
*/ | ||
private const double SUCCESS_VELOCITY_THRESHOLD = 0.003; | ||
|
||
/** | ||
* Maximum velocity allowed on gesture update. | ||
*/ | ||
private const double MAX_VELOCITY = 0.01; | ||
|
||
// These are for calculations that only have to be done once | ||
public signal void commit (double progress); | ||
|
||
public GestureSettings.GestureAction action { get; construct set; } | ||
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; } | ||
|
||
private double _progress = 0; | ||
public double progress { | ||
get { return _progress; } | ||
set { | ||
_progress = value; | ||
|
||
var lower_clamp_int = (int) overshoot_lower_clamp; | ||
var upper_clamp_int = (int) overshoot_upper_clamp; | ||
|
||
double stretched_percentage = 0; | ||
if (progress < lower_clamp_int) { | ||
stretched_percentage = (progress - lower_clamp_int) * - (overshoot_lower_clamp - lower_clamp_int); | ||
} else if (progress > upper_clamp_int) { | ||
stretched_percentage = (progress - upper_clamp_int) * (overshoot_upper_clamp - upper_clamp_int); | ||
} | ||
|
||
var clamped = progress.clamp (lower_clamp_int, upper_clamp_int); | ||
|
||
target.update (clamped + stretched_percentage); | ||
} | ||
} | ||
|
||
public GestureTarget target { get; construct set; } | ||
|
||
private ToucheggBackend touchpad_backend; | ||
private ScrollBackend scroll_backend; | ||
|
||
private bool recognizing = false; | ||
private double previous_percentage; | ||
private uint64 previous_time; | ||
private double previous_delta; | ||
private double velocity; | ||
// Used to check whether to cancel. Necessary because on_end is often called | ||
// with the same percentage as the last update so this is the one before the last update. | ||
private double old_previous; | ||
private int direction_multiplier; | ||
|
||
private Clutter.Timeline? timeline; | ||
|
||
public GestureController (GestureSettings.GestureAction action) { | ||
Object (action: action); | ||
} | ||
|
||
public void enable_touchpad () { | ||
touchpad_backend = ToucheggBackend.get_default (); | ||
touchpad_backend.on_gesture_detected.connect (gesture_detected); | ||
touchpad_backend.on_begin.connect (gesture_begin); | ||
touchpad_backend.on_update.connect (gesture_update); | ||
touchpad_backend.on_end.connect (gesture_end); | ||
} | ||
|
||
public void enable_scroll (Clutter.Actor actor, Clutter.Orientation orientation) { | ||
scroll_backend = new ScrollBackend (actor, orientation, new GestureSettings ()); | ||
scroll_backend.on_gesture_detected.connect (gesture_detected); | ||
scroll_backend.on_begin.connect (gesture_begin); | ||
scroll_backend.on_update.connect (gesture_update); | ||
scroll_backend.on_end.connect (gesture_end); | ||
} | ||
|
||
private void prepare () { | ||
if (timeline != null) { | ||
timeline.stop (); | ||
timeline = null; | ||
} | ||
} | ||
|
||
private bool gesture_detected (GestureBackend backend, Gesture gesture, uint32 timestamp) { | ||
recognizing = GestureSettings.get_action (gesture) == action || GestureSettings.get_action (gesture) == NONE; | ||
|
||
if (recognizing) { | ||
if (gesture.direction == UP || gesture.direction == RIGHT) { | ||
direction_multiplier = 1; | ||
} else { | ||
direction_multiplier = -1; | ||
} | ||
} | ||
|
||
return recognizing; | ||
} | ||
|
||
private void gesture_begin (double percentage, uint64 elapsed_time) { | ||
if (!recognizing) { | ||
return; | ||
} | ||
|
||
prepare (); | ||
|
||
previous_percentage = percentage; | ||
previous_time = elapsed_time; | ||
} | ||
|
||
private void gesture_update (double percentage, uint64 elapsed_time) { | ||
if (!recognizing) { | ||
return; | ||
} | ||
|
||
var updated_delta = previous_delta; | ||
if (elapsed_time != previous_time) { | ||
double distance = percentage - previous_percentage; | ||
double time = (double)(elapsed_time - previous_time); | ||
velocity = (distance / time); | ||
|
||
if (velocity > MAX_VELOCITY) { | ||
velocity = MAX_VELOCITY; | ||
var used_percentage = MAX_VELOCITY * time + previous_percentage; | ||
updated_delta += percentage - used_percentage; | ||
} | ||
} | ||
|
||
progress += calculate_applied_delta (percentage, updated_delta); | ||
|
||
old_previous = previous_percentage; | ||
previous_percentage = percentage; | ||
previous_time = elapsed_time; | ||
previous_delta = updated_delta; | ||
} | ||
|
||
private void gesture_end (double percentage, uint64 elapsed_time) { | ||
if (!recognizing) { | ||
return; | ||
} | ||
|
||
progress += calculate_applied_delta (percentage, previous_delta); | ||
|
||
int completions = (int) Math.round (progress); | ||
|
||
if (velocity.abs () > SUCCESS_VELOCITY_THRESHOLD) { | ||
completions += velocity > 0 ? direction_multiplier : -direction_multiplier; | ||
} | ||
|
||
var lower_clamp_int = (int) overshoot_lower_clamp; | ||
var upper_clamp_int = (int) overshoot_upper_clamp; | ||
|
||
completions = completions.clamp (lower_clamp_int, upper_clamp_int); | ||
|
||
recognizing = false; | ||
|
||
finish (velocity, (double) completions); | ||
|
||
previous_percentage = 0; | ||
previous_time = 0; | ||
previous_delta = 0; | ||
velocity = 0; | ||
old_previous = 0; | ||
direction_multiplier = 0; | ||
} | ||
|
||
private inline double calculate_applied_delta (double percentage, double percentage_delta) { | ||
return ((percentage - percentage_delta) - (previous_percentage - previous_delta)) * direction_multiplier; | ||
} | ||
|
||
private void finish (double velocity, double to) { | ||
var transition = new SpringTimeline (target.actor, progress, to, velocity, 1, 0.5, 500); | ||
transition.progress.connect ((value) => progress = value); | ||
|
||
timeline = transition; | ||
|
||
commit (to); | ||
} | ||
|
||
public void goto (double to) { | ||
prepare (); | ||
finish (0.005, to); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
/* | ||
* Copyright 2025 elementary, Inc. (https://elementary.io) | ||
* SPDX-License-Identifier: GPL-3.0-or-later | ||
* | ||
* Authored by: Leonhard Kargl <leo.kargl@proton.me> | ||
*/ | ||
|
||
public class Gala.PropertyTarget : GestureTarget { | ||
/** | ||
* The property that will be animated. To be properly animated it has to be marked as | ||
* animatable in the Clutter documentation and should be numeric. | ||
*/ | ||
public string property { get; construct; } | ||
|
||
public Clutter.Interval interval { get; construct; } | ||
|
||
public PropertyTarget (Clutter.Actor actor, string property, Type value_type, Value from_value, Value to_value) { | ||
Object (actor: actor, property: property, interval: new Clutter.Interval.with_values (value_type, from_value, to_value)); | ||
} | ||
|
||
public override void update (double progress) { | ||
actor.set_property (property, interval.compute (progress)); | ||
} | ||
} |
Oops, something went wrong.