Skip to content

Commit

Permalink
MultitaskingView: Allow workspace switch animation to be interrupted (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
leolost2605 authored Jan 25, 2025
1 parent 6b3e685 commit de8752f
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 39 deletions.
34 changes: 28 additions & 6 deletions src/Gestures/GesturePropertyTransition.vala
Original file line number Diff line number Diff line change
Expand Up @@ -88,35 +88,55 @@ public class Gala.GesturePropertyTransition : Object {
* be set immediately on {@link GestureTracker.OnEnd} not only once the animation ends to allow for interrupting the animation by starting a new gesture.
* done_callback will only be called if the animation finishes, not if it is interrupted e.g. by starting a new animation for the same property,
* destroying the actor or removing the transition.
*
* @return If a transition is currently in progress for the actor and the property the percentage how far the current value
* is towards the to_value given the final value of the ongoing transition is returned. This is usally the case if a gesture ended but was
* started again before the animation finished so this should be used to set {@link GestureTracker.initial_percentage}. If no transition
* is in progress 0 is returned.
*/
public void start (bool with_gesture, owned DoneCallback? done_callback = null) {
public double start (bool with_gesture, owned DoneCallback? done_callback = null) {
ref ();

this.done_callback = (owned) done_callback;

Value current_value = {};
actor.get_property (property, ref current_value);

actual_from_value = from_value ?? current_value;
Value initial_value;

unowned var old_transition = actor.get_transition (property);
if (old_transition != null) {
initial_value = old_transition.interval.final;
} else {
initial_value = current_value;
}

actual_from_value = from_value ?? initial_value;

if (actual_from_value.type () != current_value.type ()) {
warning ("from_value of type %s is not of the same type as the property %s which is %s. Can't animate.", from_value.type_name (), property, current_value.type_name ());
finish ();
return;
return 0;
}

if (current_value.type () != to_value.type ()) {
warning ("to_value of type %s is not of the same type as the property %s which is %s. Can't animate.", to_value.type_name (), property, current_value.type_name ());
finish ();
return;
return 0;
}

// Pre calculate some things, so we don't have to do it on every update
from_value_float = value_to_float (actual_from_value);
to_value_float = value_to_float (to_value);

GestureTracker.OnBegin on_animation_begin = () => {
actor.set_property (property, actual_from_value);
var current_value_double = (double) value_to_float (current_value);
var initial_value_double = (double) value_to_float (initial_value);

var initial_percentage = ((to_value_float - initial_value_double) - (to_value_float - current_value_double)) / (to_value_float - initial_value_double);

GestureTracker.OnBegin on_animation_begin = (percentage) => {
var animation_value = GestureTracker.animation_value (from_value_float, to_value_float, percentage, false);
actor.set_property (property, value_from_float (animation_value));
};

GestureTracker.OnUpdate on_animation_update = (percentage) => {
Expand Down Expand Up @@ -180,6 +200,8 @@ public class Gala.GesturePropertyTransition : Object {
on_animation_end (1, 1, gesture_tracker.min_animation_duration);
}
}

return initial_percentage;
}

private void finish (bool callback = true) {
Expand Down
59 changes: 42 additions & 17 deletions src/Gestures/GestureTracker.vala
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,10 @@ public class Gala.GestureTracker : Object {
* start receiving updates.
* @param gesture the same gesture as in {@link on_gesture_detected}
* @param timestamp the timestamp of the event that initiated the gesture or {@link Meta.CURRENT_TIME}.
* @return the initial percentage that should already be preapplied. This is useful
* if an animation was still ongoing when the gesture was started.
*/
public signal void on_gesture_handled (Gesture gesture, uint32 timestamp);
public signal double on_gesture_handled (Gesture gesture, uint32 timestamp);

/**
* Emitted right after on_gesture_detected with the initial gesture information.
Expand Down Expand Up @@ -135,19 +137,25 @@ public class Gala.GestureTracker : Object {

private Gee.ArrayList<ulong> handlers;

private double applied_percentage;
private double previous_percentage;
private uint64 previous_time;
private double percentage_delta;
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;

construct {
settings = new GestureSettings ();

handlers = new Gee.ArrayList<ulong> ();
applied_percentage = 0;
previous_percentage = 0;
previous_time = 0;
percentage_delta = 0;
previous_delta = 0;
velocity = 0;
old_previous = 0;
}

public GestureTracker (int min_animation_duration, int max_animation_duration) {
Expand Down Expand Up @@ -249,7 +257,7 @@ public class Gala.GestureTracker : Object {
private bool gesture_detected (GestureBackend backend, Gesture gesture, uint32 timestamp) {
if (enabled && on_gesture_detected (gesture)) {
backend.prepare_gesture_handling ();
on_gesture_handled (gesture, timestamp);
applied_percentage = on_gesture_handled (gesture, timestamp);
return true;
}

Expand All @@ -258,7 +266,7 @@ public class Gala.GestureTracker : Object {

private void gesture_begin (double percentage, uint64 elapsed_time) {
if (enabled) {
on_begin (percentage);
on_begin (applied_percentage);
}

recognizing = true;
Expand All @@ -267,6 +275,7 @@ public class Gala.GestureTracker : Object {
}

private void gesture_update (double percentage, uint64 elapsed_time) {
var updated_delta = previous_delta;
if (elapsed_time != previous_time) {
double distance = percentage - previous_percentage;
double time = (double)(elapsed_time - previous_time);
Expand All @@ -275,43 +284,59 @@ public class Gala.GestureTracker : Object {
if (velocity > MAX_VELOCITY) {
velocity = MAX_VELOCITY;
var used_percentage = MAX_VELOCITY * time + previous_percentage;
percentage_delta += percentage - used_percentage;
updated_delta += percentage - used_percentage;
}
}

applied_percentage += calculate_applied_delta (percentage, updated_delta);

if (enabled) {
on_update (applied_percentage (percentage, percentage_delta));
on_update (applied_percentage);
}

old_previous = previous_percentage;
previous_percentage = percentage;
previous_time = elapsed_time;
previous_delta = updated_delta;
}

private void gesture_end (double percentage, uint64 elapsed_time) {
double end_percentage = applied_percentage (percentage, percentage_delta);
int completions = (int) end_percentage;
bool cancel_action = (end_percentage.abs () < SUCCESS_PERCENTAGE_THRESHOLD)
&& ((end_percentage.abs () <= previous_percentage.abs ()) && (velocity < SUCCESS_VELOCITY_THRESHOLD));
int calculated_duration = calculate_end_animation_duration (end_percentage, cancel_action);
applied_percentage += calculate_applied_delta (percentage, previous_delta);

int completions = (int) applied_percentage;

var remaining_percentage = applied_percentage - completions;

bool cancel_action = ((percentage - completions).abs () < SUCCESS_PERCENTAGE_THRESHOLD) && (velocity.abs () < SUCCESS_VELOCITY_THRESHOLD)
|| ((percentage.abs () < old_previous.abs ()) && (velocity.abs () > SUCCESS_VELOCITY_THRESHOLD));

int calculated_duration = calculate_end_animation_duration (remaining_percentage, cancel_action);

if (!cancel_action) {
completions += end_percentage < 0 ? -1 : 1;
completions += applied_percentage < 0 ? -1 : 1;
}

if (enabled) {
on_end (end_percentage, completions, calculated_duration);
on_end (applied_percentage, completions, calculated_duration);
}

disconnect_all_handlers ();
recognizing = false;
applied_percentage = 0;
previous_percentage = 0;
previous_time = 0;
percentage_delta = 0;
previous_delta = 0;
velocity = 0;
old_previous = 0;
}

private static inline double applied_percentage (double percentage, double percentage_delta) {
return percentage - percentage_delta;
/**
* Calculate the delta between the new percentage and the previous one while taking into account
* the velocity delta which makes sure we don't go over the MAX_VELOCITY. The velocity delta we use
* for the calculation shouldn't be confused with this delta.
*/
private inline double calculate_applied_delta (double percentage, double percentage_delta) {
return (percentage - percentage_delta) - (previous_percentage - previous_delta);
}

/**
Expand Down
22 changes: 10 additions & 12 deletions src/Widgets/MultitaskingView.vala
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,6 @@ namespace Gala {
private Drawing.StyleManager style_manager;

private bool switching_workspace_with_gesture = false;
private bool switching_workspace_in_progress {
get {
return switching_workspace_with_gesture || workspaces.get_transition ("x") != null;
}
}

public MultitaskingView (WindowManagerGala wm) {
Object (wm: wm);
Expand All @@ -71,7 +66,7 @@ namespace Gala {
multitasking_gesture_tracker = new GestureTracker (ANIMATION_DURATION, ANIMATION_DURATION);
multitasking_gesture_tracker.enable_touchpad ();
multitasking_gesture_tracker.on_gesture_detected.connect (on_multitasking_gesture_detected);
multitasking_gesture_tracker.on_gesture_handled.connect (() => toggle (true, false));
multitasking_gesture_tracker.on_gesture_handled.connect (on_multitasking_gesture_handled);

workspace_gesture_tracker = new GestureTracker (AnimationDuration.WORKSPACE_SWITCH_MIN, AnimationDuration.WORKSPACE_SWITCH);
workspace_gesture_tracker.enable_touchpad ();
Expand Down Expand Up @@ -304,6 +299,11 @@ namespace Gala {
return false;
}

private double on_multitasking_gesture_handled (Gesture gesture, uint32 timestamp) {
toggle (true, false);
return 0;
}

private bool on_workspace_gesture_detected (Gesture gesture) {
if (!opened) {
return false;
Expand All @@ -316,11 +316,7 @@ namespace Gala {
return false;
}

private void switch_workspace_with_gesture (Gesture gesture, uint32 timestamp) {
if (switching_workspace_in_progress) {
return;
}

private double switch_workspace_with_gesture (Gesture gesture, uint32 timestamp) {
var direction = workspace_gesture_tracker.settings.get_natural_scroll_direction (gesture);

unowned var manager = display.get_workspace_manager ();
Expand Down Expand Up @@ -364,7 +360,7 @@ namespace Gala {
var upper_clamp = (direction == LEFT) ? (active_workspace.index () + 0.1) : (num_workspaces - active_workspace.index () - 0.9);
var lower_clamp = (direction == RIGHT) ? - (active_workspace.index () + 0.1) : - (num_workspaces - active_workspace.index () - 0.9);

new GesturePropertyTransition (workspaces, workspace_gesture_tracker, "x", null, target_x) {
var initial_percentage = new GesturePropertyTransition (workspaces, workspace_gesture_tracker, "x", null, target_x) {
overshoot_lower_clamp = lower_clamp,
overshoot_upper_clamp = upper_clamp
}.start (true);
Expand All @@ -381,6 +377,8 @@ namespace Gala {
} else {
workspace_gesture_tracker.connect_handlers (null, null, (owned) on_animation_end);
}

return initial_percentage;
}

/**
Expand Down
4 changes: 3 additions & 1 deletion src/WindowManager.vala
Original file line number Diff line number Diff line change
Expand Up @@ -596,7 +596,7 @@ namespace Gala {
return switch_workspace_with_gesture || (action == SWITCH_WINDOWS && !window_switcher.opened);
}

private void on_gesture_handled (Gesture gesture, uint32 timestamp) {
private double on_gesture_handled (Gesture gesture, uint32 timestamp) {
var direction = gesture_tracker.settings.get_natural_scroll_direction (gesture);

switch (GestureSettings.get_action (gesture)) {
Expand All @@ -620,6 +620,8 @@ namespace Gala {
default:
break;
}

return 0;
}

/**
Expand Down
8 changes: 5 additions & 3 deletions src/Zoom.vala
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public class Gala.Zoom : Object {
gesture_tracker = new GestureTracker (ANIMATION_DURATION, ANIMATION_DURATION);
gesture_tracker.enable_touchpad ();
gesture_tracker.on_gesture_detected.connect (on_gesture_detected);
gesture_tracker.on_gesture_handled.connect ((gesture) => zoom_with_gesture (gesture.direction));
gesture_tracker.on_gesture_handled.connect (on_gesture_handled);

behavior_settings = new GLib.Settings ("io.elementary.desktop.wm.behavior");

Expand Down Expand Up @@ -101,9 +101,9 @@ public class Gala.Zoom : Object {
return Clutter.EVENT_STOP;
}

private void zoom_with_gesture (GestureDirection direction) {
private double on_gesture_handled (Gesture gesture, uint32 timestamp) {
var initial_zoom = current_zoom;
var target_zoom = (direction == GestureDirection.IN)
var target_zoom = (gesture.direction == GestureDirection.IN)
? initial_zoom - MAX_ZOOM
: initial_zoom + MAX_ZOOM;

Expand All @@ -123,6 +123,8 @@ public class Gala.Zoom : Object {
};

gesture_tracker.connect_handlers (null, (owned) on_animation_update, null);

return 0;
}

private inline Graphene.Point compute_new_pivot_point () {
Expand Down

0 comments on commit de8752f

Please sign in to comment.