Skip to content

Commit

Permalink
Overshoot gestures (#2164)
Browse files Browse the repository at this point in the history
Co-authored-by: Leo <[email protected]>
  • Loading branch information
leolost2605 and lenemter authored Dec 19, 2024
1 parent 6a90680 commit 6f0093a
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 83 deletions.
64 changes: 48 additions & 16 deletions src/Gestures/GesturePropertyTransition.vala
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,28 @@ public class Gala.GesturePropertyTransition : Object {
public Value to_value { get; construct set; }

/**
* If not null this can be used to have an intermediate step before animating back to the origin.
* Therefore using this makes mostly sense if {@link to_value} equals {@link from_value}.
* This is mostly used for the nudge animations when trying to switch workspaces where there isn't one anymore.
* The lower max overshoot. The gesture percentage by which #this animates the property is bounded
* by this property on the lower end. If it is in the form X.YY with Y not 0 the animation will be linear
* until X and then take another 100% to animate until X.YY (instead of YY%).
* Default is 0.
*/
public Value? intermediate_value { get; construct; }
public double overshoot_lower_clamp { get; set; default = 0; }
/**
* Same as {@link overshoot_lower_clamp} but for the upper limit.
* If this is less than 1 and the transition is started without a gesture it will animate to
* the {@link to_value} by this percent and then back to the {@link from_value}.
* Default is 1.
*/
public double overshoot_upper_clamp { get; set; default = 1; }

/**
* This is the from value that's actually used when calculating the animation movement.
* If {@link from_value} isn't null this will be the same, otherwise it will be set to the current
* value of the target property, when calling {@link start}.
*/
private Value actual_from_value;
private float from_value_float; // Only valid in the time between start () and finish ()
private float to_value_float; // Only valid in the time between start () and finish ()

private DoneCallback? done_callback;

Expand All @@ -57,16 +67,14 @@ public class Gala.GesturePropertyTransition : Object {
GestureTracker gesture_tracker,
string property,
Value? from_value,
Value to_value,
Value? intermediate_value = null
Value to_value
) {
Object (
actor: actor,
gesture_tracker: gesture_tracker,
property: property,
from_value: from_value,
to_value: to_value,
intermediate_value: intermediate_value
to_value: to_value
);
}

Expand Down Expand Up @@ -100,20 +108,44 @@ public class Gala.GesturePropertyTransition : Object {
return;
}

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

GestureTracker.OnUpdate on_animation_update = (percentage) => {
var animation_value = GestureTracker.animation_value (value_to_float (actual_from_value), value_to_float (intermediate_value ?? to_value), percentage);
var lower_clamp_int = (int) overshoot_lower_clamp;
var upper_clamp_int = (int) overshoot_upper_clamp;

double stretched_percentage = 0;
if (percentage < lower_clamp_int) {
stretched_percentage = (percentage - lower_clamp_int) * - (overshoot_lower_clamp - lower_clamp_int);
} else if (percentage > upper_clamp_int) {
stretched_percentage = (percentage - upper_clamp_int) * (overshoot_upper_clamp - upper_clamp_int);
}

percentage = percentage.clamp (lower_clamp_int, upper_clamp_int);

var animation_value = GestureTracker.animation_value (from_value_float, to_value_float, percentage, false);

if (stretched_percentage != 0) {
animation_value += (float) stretched_percentage * (to_value_float - from_value_float);
}

actor.set_property (property, value_from_float (animation_value));
};

GestureTracker.OnEnd on_animation_end = (percentage, cancel_action, calculated_duration) => {
GestureTracker.OnEnd on_animation_end = (percentage, completions, calculated_duration) => {
completions = completions.clamp ((int) overshoot_lower_clamp, (int) overshoot_upper_clamp);
var target_value = from_value_float + completions * (to_value_float - from_value_float);

actor.save_easing_state ();
actor.set_easing_mode (EASE_OUT_QUAD);
actor.set_easing_duration (AnimationsSettings.get_animation_duration (calculated_duration));
actor.set_property (property, cancel_action ? actual_from_value : to_value);
actor.set_property (property, value_from_float (target_value));
actor.restore_easing_state ();

unowned var transition = actor.get_transition (property);
Expand All @@ -128,21 +160,21 @@ public class Gala.GesturePropertyTransition : Object {
gesture_tracker.connect_handlers (on_animation_begin, on_animation_update, on_animation_end);
} else {
on_animation_begin (0);
if (intermediate_value != null) {
if (overshoot_upper_clamp < 1) {
actor.save_easing_state ();
actor.set_easing_mode (EASE_OUT_QUAD);
actor.set_easing_duration (AnimationsSettings.get_animation_duration (gesture_tracker.min_animation_duration));
actor.set_property (property, intermediate_value);
actor.set_property (property, value_from_float ((float) overshoot_upper_clamp * (to_value_float - from_value_float) + from_value_float));
actor.restore_easing_state ();

unowned var transition = actor.get_transition (property);
if (transition == null) {
on_animation_end (1, false, gesture_tracker.min_animation_duration);
on_animation_end (1, 1, gesture_tracker.min_animation_duration);
} else {
transition.stopped.connect (() => on_animation_end (1, false, gesture_tracker.min_animation_duration));
transition.stopped.connect (() => on_animation_end (1, 1, gesture_tracker.min_animation_duration));
}
} else {
on_animation_end (1, false, gesture_tracker.min_animation_duration);
on_animation_end (1, 1, gesture_tracker.min_animation_duration);
}
}
}
Expand Down
23 changes: 16 additions & 7 deletions src/Gestures/GestureTracker.vala
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,16 @@ public class Gala.GestureTracker : Object {

/**
* @param percentage Value between 0 and 1.
* @param completions The number of times a full cycle of the gesture was completed in this go. Can be
* negative if the gesture was started in one direction but ended in the other. This is used to update
* the UI to the according state. 0 for example means that the UI should go back to the same state
* it was in before the gesture started.
*/
public signal void on_end (double percentage, bool cancel_action, int calculated_duration);
public signal void on_end (double percentage, int completions, int calculated_duration);

public delegate void OnBegin (double percentage);
public delegate void OnUpdate (double percentage);
public delegate void OnEnd (double percentage, bool cancel_action, int calculated_duration);
public delegate void OnEnd (double percentage, int completions, int calculated_duration);

/**
* Backend used if enable_touchpad is called.
Expand Down Expand Up @@ -239,12 +243,17 @@ public class Gala.GestureTracker : Object {

private void gesture_end (double percentage, uint64 elapsed_time) {
double end_percentage = applied_percentage (percentage, percentage_delta);
bool cancel_action = (end_percentage < SUCCESS_PERCENTAGE_THRESHOLD)
&& ((end_percentage <= previous_percentage) && (velocity < SUCCESS_VELOCITY_THRESHOLD));
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);

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

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

disconnect_all_handlers ();
Expand All @@ -254,8 +263,8 @@ public class Gala.GestureTracker : Object {
velocity = 0;
}

private static double applied_percentage (double percentage, double percentage_delta) {
return (percentage - percentage_delta).clamp (0, 1);
private static inline double applied_percentage (double percentage, double percentage_delta) {
return percentage - percentage_delta;
}

/**
Expand Down
5 changes: 1 addition & 4 deletions src/Gestures/ScrollBackend.vala
Original file line number Diff line number Diff line change
Expand Up @@ -136,10 +136,7 @@ public class Gala.ScrollBackend : Object {
double finish_delta = is_horizontal ? FINISH_DELTA_HORIZONTAL : FINISH_DELTA_VERTICAL;

bool is_positive = (direction == GestureDirection.RIGHT || direction == GestureDirection.DOWN);
double clamp_low = is_positive ? 0 : -1;
double clamp_high = is_positive ? 1 : 0;

double normalized_delta = (used_delta / finish_delta).clamp (clamp_low, clamp_high).abs ();
return normalized_delta;
return (used_delta / finish_delta) * (is_positive ? 1 : -1);
}
}
64 changes: 21 additions & 43 deletions src/Widgets/MultitaskingView.vala
Original file line number Diff line number Diff line change
Expand Up @@ -340,11 +340,6 @@ namespace Gala {
float initial_x = workspaces.x;
float target_x = 0;
bool is_nudge_animation = !target_workspace_exists;
var scale = display.get_monitor_scale (display.get_primary_monitor ());
var nudge_gap = InternalUtils.scale_to_int (WindowManagerGala.NUDGE_GAP, scale);

unowned IconGroup active_icon_group = null;
unowned IconGroup? target_icon_group = null;

if (is_nudge_animation) {
var workspaces_geometry = InternalUtils.get_workspaces_geometry (display);
Expand All @@ -355,21 +350,11 @@ namespace Gala {
var workspace = workspace_clone.workspace;

if (workspace == target_workspace) {
target_icon_group = workspace_clone.icon_group;
target_x = -workspace_clone.multitasking_view_x ();
} else if (workspace == active_workspace) {
active_icon_group = workspace_clone.icon_group;
}
}
}

if (!is_nudge_animation && active_icon_group.get_transition ("backdrop-opacity") != null) {
active_icon_group.remove_transition ("backdrop-opacity");
}
if (!is_nudge_animation && target_icon_group.get_transition ("backdrop-opacity") != null) {
target_icon_group.remove_transition ("backdrop-opacity");
}

debug ("Starting MultitaskingView switch workspace animation:");
debug ("Active workspace index: %d", active_workspace.index ());
debug ("Target workspace index: %d", target_workspace_index);
Expand All @@ -379,28 +364,24 @@ namespace Gala {
debug ("Target X: %f", target_x);

switching_workspace_with_gesture = true;
if (target_workspace != null) {
target_workspace.activate (display.get_current_time ());
}

if (is_nudge_animation) {
new GesturePropertyTransition (workspaces, workspace_gesture_tracker, "x", null, initial_x, initial_x + nudge_gap * -relative_dir).start (true);
} else {
new GesturePropertyTransition (workspaces, workspace_gesture_tracker, "x", null, target_x).start (true);
new GesturePropertyTransition (active_icon_group, workspace_gesture_tracker, "backdrop-opacity", 1f, 0f).start (true);
new GesturePropertyTransition (target_icon_group, workspace_gesture_tracker, "backdrop-opacity", 0f, 1f).start (true);
}
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);

GestureTracker.OnEnd on_animation_end = (percentage, cancel_action, calculated_duration) => {
new GesturePropertyTransition (workspaces, workspace_gesture_tracker, "x", null, target_x) {
overshoot_lower_clamp = lower_clamp,
overshoot_upper_clamp = upper_clamp
}.start (true);

GestureTracker.OnEnd on_animation_end = (percentage, completions, calculated_duration) => {
switching_workspace_with_gesture = false;

if (is_nudge_animation || cancel_action) {
active_workspace.activate (display.get_current_time ());
}
completions = completions.clamp ((int) lower_clamp, (int) upper_clamp);
manager.get_workspace_by_index (active_workspace.index () + completions * relative_dir).activate (display.get_current_time ());
};

if (!AnimationsSettings.get_enable_animations ()) {
on_animation_end (1, false, 0);
on_animation_end (1, 1, 0);
} else {
workspace_gesture_tracker.connect_handlers (null, null, (owned) on_animation_end);
}
Expand Down Expand Up @@ -429,9 +410,6 @@ namespace Gala {

if (workspace == active_workspace) {
active_x = dest_x;
workspace_clone.icon_group.backdrop_opacity = 1.0f;
} else {
workspace_clone.icon_group.backdrop_opacity = 0.0f;
}

workspace_clone.save_easing_state ();
Expand Down Expand Up @@ -706,8 +684,8 @@ namespace Gala {
hide_docks (with_gesture, is_cancel_animation);
}

GestureTracker.OnEnd on_animation_end = (percentage, cancel_action) => {
var animation_duration = cancel_action ? 0 : ANIMATION_DURATION;
GestureTracker.OnEnd on_animation_end = (percentage, completions) => {
var animation_duration = completions == 0 ? 0 : ANIMATION_DURATION;
Timeout.add (animation_duration, () => {
if (!opening) {
foreach (var container in window_containers_monitors) {
Expand All @@ -727,7 +705,7 @@ namespace Gala {

animating = false;

if (cancel_action) {
if (completions == 0) {
toggle (false, true);
}

Expand All @@ -736,7 +714,7 @@ namespace Gala {
};

if (!with_gesture) {
on_animation_end (1, false, 0);
on_animation_end (1, 1, 0);
} else {
multitasking_gesture_tracker.connect_handlers (null, null, (owned) on_animation_end);
}
Expand Down Expand Up @@ -792,8 +770,8 @@ namespace Gala {
clone.y = y;
};

GestureTracker.OnEnd on_animation_end = (percentage, cancel_action) => {
if (cancel_action) {
GestureTracker.OnEnd on_animation_end = (percentage, completions) => {
if (completions == 0) {
return;
}

Expand All @@ -805,7 +783,7 @@ namespace Gala {
};

if (!with_gesture || !AnimationsSettings.get_enable_animations ()) {
on_animation_end (1, false, 0);
on_animation_end (1, 1, 0);
} else {
multitasking_gesture_tracker.connect_handlers (null, (owned) on_animation_update, (owned) on_animation_end);
}
Expand All @@ -823,8 +801,8 @@ namespace Gala {
dock.y = y;
};

GestureTracker.OnEnd on_animation_end = (percentage, cancel_action) => {
if (cancel_action) {
GestureTracker.OnEnd on_animation_end = (percentage, completions) => {
if (completions == 0) {
return;
}

Expand All @@ -836,7 +814,7 @@ namespace Gala {
};

if (!with_gesture || !AnimationsSettings.get_enable_animations ()) {
on_animation_end (1, false, 0);
on_animation_end (1, 1, 0);
} else {
multitasking_gesture_tracker.connect_handlers (null, (owned) on_animation_update, (owned) on_animation_end);
}
Expand Down
16 changes: 16 additions & 0 deletions src/Widgets/WorkspaceClone.vala
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,14 @@ namespace Gala {

var listener = WindowListener.get_default ();
listener.window_no_longer_on_all_workspaces.connect (add_window);

parent_set.connect ((old_parent) => {
if (old_parent != null) {
old_parent.notify["x"].disconnect (update_icon_group_opacity);
}

get_parent ().notify["x"].connect (update_icon_group_opacity);
});
}

~WorkspaceClone () {
Expand All @@ -250,6 +258,14 @@ namespace Gala {
icon_group.destroy ();
}

private void update_icon_group_opacity () {
var offset = (multitasking_view_x () + get_parent ().x).abs ();

var adjusted_width = width - InternalUtils.scale_to_int (X_OFFSET, scale_factor);

icon_group.backdrop_opacity = (1 - (offset / adjusted_width)).clamp (0, 1);
}

private void reallocate () {
icon_group.scale_factor = scale_factor;
window_container.monitor_scale = scale_factor;
Expand Down
Loading

0 comments on commit 6f0093a

Please sign in to comment.