Skip to content

Commit 58e5b42

Browse files
Introduce a GesturePropertyTransition (#2074)
Co-authored-by: Leo <[email protected]>
1 parent 4850f1b commit 58e5b42

File tree

7 files changed

+254
-292
lines changed

7 files changed

+254
-292
lines changed
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
/*
2+
* Copyright 2024 elementary, Inc. (https://elementary.io)
3+
* SPDX-License-Identifier: GPL-3.0-or-later
4+
*
5+
* Authored by: Leonhard Kargl <[email protected]>
6+
*/
7+
8+
/**
9+
* A class that will animate a property of a {@link Clutter.Actor} one to one with a gesture or
10+
* with easing without a gesture. Respects the enable animation setting.
11+
*/
12+
public class Gala.GesturePropertyTransition : Object {
13+
public delegate void DoneCallback ();
14+
15+
/**
16+
* The actor whose property will be animated.
17+
*/
18+
public Clutter.Actor actor { get; construct; }
19+
20+
public GestureTracker gesture_tracker { get; construct; }
21+
22+
/**
23+
* The property that will be animated. To be properly animated it has to be marked as
24+
* animatable in the Clutter documentation and should be numeric.
25+
*/
26+
public string property { get; construct; }
27+
28+
/**
29+
* The starting value of the animation or null to use the current value. The value
30+
* has to be of the same type as the property.
31+
*/
32+
public Value? from_value { get; construct set; }
33+
34+
/**
35+
* The value to animate to. It has to be of the same type as the property.
36+
*/
37+
public Value to_value { get; construct set; }
38+
39+
/**
40+
* If not null this can be used to have an intermediate step before animating back to the origin.
41+
* Therefore using this makes mostly sense if {@link to_value} equals {@link from_value}.
42+
* This is mostly used for the nudge animations when trying to switch workspaces where there isn't one anymore.
43+
*/
44+
public Value? intermediate_value { get; construct; }
45+
46+
/**
47+
* This is the from value that's actually used when calculating the animation movement.
48+
* If {@link from_value} isn't null this will be the same, otherwise it will be set to the current
49+
* value of the target property, when calling {@link start}.
50+
*/
51+
private Value actual_from_value;
52+
53+
private DoneCallback? done_callback;
54+
55+
public GesturePropertyTransition (
56+
Clutter.Actor actor,
57+
GestureTracker gesture_tracker,
58+
string property,
59+
Value? from_value,
60+
Value to_value,
61+
Value? intermediate_value = null
62+
) {
63+
Object (
64+
actor: actor,
65+
gesture_tracker: gesture_tracker,
66+
property: property,
67+
from_value: from_value,
68+
to_value: to_value,
69+
intermediate_value: intermediate_value
70+
);
71+
}
72+
73+
/**
74+
* Starts animating the property from {@link from_value} to {@link to_value}. If with_gesture is true
75+
* it will connect to the gesture trackers signals and animate according to the input finishing with an easing
76+
* to the final position. If with_gesture is false it will just ease to the {@link to_value}.
77+
* #this will keep itself alive until the animation finishes so it is safe to immediatly unref it after creation and calling start.
78+
*
79+
* @param done_callback a callback for when the transition finishes. It is guaranteed to be called exactly once.
80+
*/
81+
public void start (bool with_gesture, owned DoneCallback? done_callback = null) {
82+
ref ();
83+
84+
this.done_callback = (owned) done_callback;
85+
86+
Value current_value = {};
87+
actor.get_property (property, ref current_value);
88+
89+
actual_from_value = from_value ?? current_value;
90+
91+
if (actual_from_value.type () != current_value.type ()) {
92+
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 ());
93+
finish ();
94+
return;
95+
}
96+
97+
if (current_value.type () != to_value.type ()) {
98+
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 ());
99+
finish ();
100+
return;
101+
}
102+
103+
GestureTracker.OnBegin on_animation_begin = () => {
104+
actor.set_property (property, actual_from_value);
105+
};
106+
107+
GestureTracker.OnUpdate on_animation_update = (percentage) => {
108+
var animation_value = GestureTracker.animation_value (value_to_float (actual_from_value), value_to_float (intermediate_value ?? to_value), percentage);
109+
actor.set_property (property, value_from_float (animation_value));
110+
};
111+
112+
GestureTracker.OnEnd on_animation_end = (percentage, cancel_action, calculated_duration) => {
113+
actor.save_easing_state ();
114+
actor.set_easing_mode (EASE_OUT_QUAD);
115+
actor.set_easing_duration (AnimationsSettings.get_animation_duration (calculated_duration));
116+
actor.set_property (property, cancel_action ? actual_from_value : to_value);
117+
actor.restore_easing_state ();
118+
119+
unowned var transition = actor.get_transition (property);
120+
if (transition == null) {
121+
finish ();
122+
} else {
123+
transition.stopped.connect (finish);
124+
}
125+
};
126+
127+
if (with_gesture && AnimationsSettings.get_enable_animations ()) {
128+
gesture_tracker.connect_handlers (on_animation_begin, on_animation_update, on_animation_end);
129+
} else {
130+
on_animation_begin (0);
131+
if (intermediate_value != null) {
132+
actor.save_easing_state ();
133+
actor.set_easing_mode (EASE_OUT_QUAD);
134+
actor.set_easing_duration (AnimationsSettings.get_animation_duration (gesture_tracker.min_animation_duration));
135+
actor.set_property (property, intermediate_value);
136+
actor.restore_easing_state ();
137+
138+
unowned var transition = actor.get_transition (property);
139+
if (transition == null) {
140+
on_animation_end (1, false, gesture_tracker.min_animation_duration);
141+
} else {
142+
transition.stopped.connect (() => on_animation_end (1, false, gesture_tracker.min_animation_duration));
143+
}
144+
} else {
145+
on_animation_end (1, false, gesture_tracker.min_animation_duration);
146+
}
147+
}
148+
}
149+
150+
private void finish () {
151+
if (done_callback != null) {
152+
done_callback ();
153+
}
154+
155+
unref ();
156+
}
157+
158+
private float value_to_float (Value val) {
159+
Value float_val = Value (typeof (float));
160+
if (val.transform (ref float_val)) {
161+
return float_val.get_float ();
162+
}
163+
164+
critical ("Non numeric property specified");
165+
return 0;
166+
}
167+
168+
private Value value_from_float (float f) {
169+
var float_val = Value (typeof (float));
170+
float_val.set_float (f);
171+
172+
var val = Value (actual_from_value.type ());
173+
174+
if (!float_val.transform (ref val)) {
175+
warning ("Failed to transform float to give type");
176+
}
177+
178+
return val;
179+
}
180+
}

src/Widgets/MultitaskingView.vala

Lines changed: 8 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -383,58 +383,17 @@ namespace Gala {
383383
target_workspace.activate (display.get_current_time ());
384384
}
385385

386-
GestureTracker.OnUpdate on_animation_update = (percentage) => {
387-
var x = GestureTracker.animation_value (initial_x, target_x, percentage, true);
388-
var icon_group_opacity = GestureTracker.animation_value (0.0f, 1.0f, percentage, false);
389-
390-
if (is_nudge_animation) {
391-
x = x.clamp (initial_x - nudge_gap, initial_x + nudge_gap);
392-
}
393-
394-
workspaces.x = x;
395-
396-
if (!is_nudge_animation) {
397-
active_icon_group.backdrop_opacity = 1.0f - icon_group_opacity;
398-
target_icon_group.backdrop_opacity = icon_group_opacity;
399-
}
400-
};
386+
if (is_nudge_animation) {
387+
new GesturePropertyTransition (workspaces, workspace_gesture_tracker, "x", null, initial_x, initial_x + nudge_gap * -relative_dir).start (true);
388+
} else {
389+
new GesturePropertyTransition (workspaces, workspace_gesture_tracker, "x", null, target_x).start (true);
390+
new GesturePropertyTransition (active_icon_group, workspace_gesture_tracker, "backdrop-opacity", 1f, 0f).start (true);
391+
new GesturePropertyTransition (target_icon_group, workspace_gesture_tracker, "backdrop-opacity", 0f, 1f).start (true);
392+
}
401393

402394
GestureTracker.OnEnd on_animation_end = (percentage, cancel_action, calculated_duration) => {
403395
switching_workspace_with_gesture = false;
404396

405-
var duration = is_nudge_animation ?
406-
(uint) (AnimationDuration.NUDGE / 2) :
407-
(uint) calculated_duration;
408-
409-
workspaces.save_easing_state ();
410-
workspaces.set_easing_mode (Clutter.AnimationMode.EASE_OUT_QUAD);
411-
workspaces.set_easing_duration (duration);
412-
workspaces.x = (is_nudge_animation || cancel_action) ? initial_x : target_x;
413-
workspaces.restore_easing_state ();
414-
415-
if (!is_nudge_animation) {
416-
if (AnimationsSettings.get_enable_animations ()) {
417-
var active_transition = new Clutter.PropertyTransition ("backdrop-opacity") {
418-
duration = duration,
419-
remove_on_complete = true
420-
};
421-
active_transition.set_from_value (active_icon_group.backdrop_opacity);
422-
active_transition.set_to_value (cancel_action ? 1.0f : 0.0f);
423-
active_icon_group.add_transition ("backdrop-opacity", active_transition);
424-
425-
var target_transition = new Clutter.PropertyTransition ("backdrop-opacity") {
426-
duration = duration,
427-
remove_on_complete = true
428-
};
429-
target_transition.set_from_value (target_icon_group.backdrop_opacity);
430-
target_transition.set_to_value (cancel_action ? 0.0f : 1.0f);
431-
target_icon_group.add_transition ("backdrop-opacity", target_transition);
432-
} else {
433-
active_icon_group.backdrop_opacity = cancel_action ? 1.0f : 0.0f;
434-
target_icon_group.backdrop_opacity = cancel_action ? 0.0f : 1.0f;
435-
}
436-
}
437-
438397
if (is_nudge_animation || cancel_action) {
439398
active_workspace.activate (display.get_current_time ());
440399
}
@@ -443,7 +402,7 @@ namespace Gala {
443402
if (!AnimationsSettings.get_enable_animations ()) {
444403
on_animation_end (1, false, 0);
445404
} else {
446-
workspace_gesture_tracker.connect_handlers (null, (owned) on_animation_update, (owned) on_animation_end);
405+
workspace_gesture_tracker.connect_handlers (null, null, (owned) on_animation_end);
447406
}
448407
}
449408

0 commit comments

Comments
 (0)