Skip to content
65 changes: 65 additions & 0 deletions lib/PropertyAnimator.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* SPDX-License-Identifier: LGPL-3.0-or-later
* SPDX-FileCopyrightText: 2025 elementary, Inc. (https://elementary.io)
*/

public class Gala.PropertyAnimator : Object {
public struct AnimatableProperty {
string property;
Type value_type;
Value? from_value;
Value target_value;
}

public delegate void OnStopped (Clutter.Actor actor);

private Clutter.Actor actor;
private OnStopped on_stopped;

public PropertyAnimator (
Clutter.Actor actor,
uint duration,
Clutter.AnimationMode animation_mode,
AnimatableProperty[] properties,
OnStopped on_stopped
) {
this.actor = actor;
this.on_stopped = on_stopped;

ref ();

if (!Meta.Prefs.get_gnome_animations ()) {
call_on_stopped ();
return;
}

for (var i = 0; i < properties.length; i++) {
var property = properties[i];

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

var transition = new Clutter.PropertyTransition (property.property) {
progress_mode = animation_mode,
remove_on_complete = true,
duration = duration,
interval = new Clutter.Interval.with_values (
property.value_type,
property.from_value ?? actor_current_property,
property.target_value
)
};

if (i == 0) {
transition.stopped.connect (call_on_stopped);
}

actor.add_transition (property.property, transition);
}
}

private void call_on_stopped () {
on_stopped (actor);
unref ();
}
}
1 change: 1 addition & 0 deletions lib/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ gala_lib_sources = files(
'Drawing/Utilities.vala',
'Image.vala',
'Plugin.vala',
'PropertyAnimator.vala',
'RoundedCornersEffect.vala',
'ShadowEffect.vala',
'Text.vala',
Expand Down
148 changes: 72 additions & 76 deletions src/WindowManager.vala
Original file line number Diff line number Diff line change
Expand Up @@ -976,24 +976,24 @@ namespace Gala {
unowned Meta.WindowActor window_actor = window.get_compositor_private () as Meta.WindowActor;
window_group.set_child_below_sibling (tile_preview, window_actor);

var duration = AnimationDuration.SNAP / 2U;

var rect = window.get_frame_rect ();
tile_preview.set_position (rect.x, rect.y);
tile_preview.set_size (rect.width, rect.height);
tile_preview.show ();

if (Meta.Prefs.get_gnome_animations ()) {
tile_preview.save_easing_state ();
tile_preview.set_easing_mode (Clutter.AnimationMode.EASE_IN_OUT_QUAD);
tile_preview.set_easing_duration (duration);
tile_preview.opacity = 255U;
tile_preview.set_position (tile_rect.x, tile_rect.y);
tile_preview.set_size (tile_rect.width, tile_rect.height);
tile_preview.restore_easing_state ();
} else {
tile_preview.opacity = 255U;
}
new PropertyAnimator (
tile_preview,
AnimationDuration.SNAP / 2,
Clutter.AnimationMode.EASE_IN_OUT_QUAD,
{
{ "opacity", typeof (uint), null, 255u },
{ "x", typeof (float), null, (float) tile_rect.x },
{ "y", typeof (float), null, (float) tile_rect.y },
{ "width", typeof (float), null, (float) tile_rect.width },
{ "height", typeof (float), null, (float) tile_rect.height }
},
() => {}
);
}

public override void hide_tile_preview () {
Expand Down Expand Up @@ -1073,15 +1073,15 @@ namespace Gala {
}

public override void minimize (Meta.WindowActor actor) {
if (!Meta.Prefs.get_gnome_animations () ||
actor.get_meta_window ().window_type != Meta.WindowType.NORMAL) {
if (!Utils.get_window_is_normal (actor.meta_window)) {
minimize_completed (actor);
return;
}

kill_window_effects (actor);
minimizing.add (actor);

var scale_x = 0.0, scale_y = 0.0;
Mtk.Rectangle icon = {};
if (actor.get_meta_window ().get_icon_geometry (out icon)) {
// Fix icon position and size according to ui scaling factor.
Expand All @@ -1096,37 +1096,26 @@ namespace Gala {
(actor.y - icon.y) / (icon.height - actor.height)
);

actor.save_easing_state ();
actor.set_easing_mode (Clutter.AnimationMode.EASE_IN_EXPO);
actor.set_easing_duration (AnimationDuration.HIDE);
actor.set_scale (icon.width / actor.width, icon.height / actor.height);
actor.opacity = 0;
actor.restore_easing_state ();

ulong minimize_handler_id = 0;
minimize_handler_id = actor.transitions_completed.connect (() => {
actor.disconnect (minimize_handler_id);
minimize_completed (actor);
minimizing.remove (actor);
});
scale_x = (double) icon.width / actor.width;
scale_y = (double) icon.height / actor.height;
} else {
actor.set_pivot_point (0.5f, 1.0f);

actor.save_easing_state ();
actor.set_easing_mode (Clutter.AnimationMode.EASE_IN_EXPO);
actor.set_easing_duration (AnimationDuration.HIDE);
actor.set_scale (0.0, 0.0);
actor.opacity = 0;
actor.restore_easing_state ();

ulong minimize_handler_id = 0;
minimize_handler_id = actor.transitions_completed.connect (() => {
actor.disconnect (minimize_handler_id);
actor.set_pivot_point (0.0f, 0.0f);
minimize_completed (actor);
minimizing.remove (actor);
});
}

new PropertyAnimator (
actor,
AnimationDuration.HIDE,
Clutter.AnimationMode.EASE_IN_EXPO,
{
{ "scale-x", typeof (double), null, scale_x },
{ "scale-y", typeof (double), null, scale_y },
{ "opacity", typeof (uint), null, 0u }
},
(actor) => {
minimize_completed ((Meta.WindowActor) actor);
minimizing.remove ((Meta.WindowActor) actor);
}
);
}

private void maximize (Meta.WindowActor actor, int ex, int ey, int ew, int eh) {
Expand Down Expand Up @@ -1492,26 +1481,27 @@ namespace Gala {

ui_group.add_child (latest_window_snapshot);

var scale_x = (float) ew / old_rect_size_change.width;
var scale_y = (float) eh / old_rect_size_change.height;

latest_window_snapshot.save_easing_state ();
latest_window_snapshot.set_easing_mode (Clutter.AnimationMode.EASE_IN_OUT_QUAD);
latest_window_snapshot.set_easing_duration (duration);
latest_window_snapshot.set_position (ex, ey);
latest_window_snapshot.set_scale (scale_x, scale_y);
latest_window_snapshot.opacity = 0U;
latest_window_snapshot.restore_easing_state ();

ulong unmaximize_old_handler_id = 0;
unmaximize_old_handler_id = latest_window_snapshot.transition_stopped.connect ((snapshot, name, is_finished) => {
snapshot.disconnect (unmaximize_old_handler_id);
var scale_x = (double) ew / old_rect_size_change.width;
var scale_y = (double) eh / old_rect_size_change.height;

unowned var parent = snapshot.get_parent ();
if (parent != null) {
parent.remove_child (snapshot);
new PropertyAnimator (
latest_window_snapshot,
duration,
Clutter.AnimationMode.EASE_IN_OUT_QUAD,
{
{ "x", typeof (float), null, (float) ex },
{ "y", typeof (float), null, (float) ey },
{ "scale-x", typeof (double), null, scale_x },
{ "scale-y", typeof (double), null, scale_y },
{ "opacity", typeof (uint), null, 0u }
},
(actor) => {
unowned var parent = actor.get_parent ();
if (parent != null) {
parent.remove_child (actor);
}
}
});
);

latest_window_snapshot = null;

Expand All @@ -1522,21 +1512,27 @@ namespace Gala {

actor.set_pivot_point (0.0f, 0.0f);
actor.set_position (ex - real_actor_offset_x, ey - real_actor_offset_y);
actor.set_translation (-ex + offset_x * (1.0f / scale_x - 1.0f) + old_rect_size_change.x, -ey + offset_y * (1.0f / scale_y - 1.0f) + old_rect_size_change.y, 0.0f);
actor.set_scale (1.0f / scale_x, 1.0f / scale_y);

actor.save_easing_state ();
actor.set_easing_mode (Clutter.AnimationMode.EASE_IN_OUT_QUAD);
actor.set_easing_duration (duration);
actor.set_scale (1.0f, 1.0f);
actor.set_translation (0.0f, 0.0f, 0.0f);
actor.restore_easing_state ();

ulong handler_id = 0UL;
handler_id = actor.transitions_completed.connect (() => {
actor.disconnect (handler_id);
unmaximizing.remove (actor);
});
actor.set_translation (
(float) (-ex + offset_x * (1.0f / scale_x - 1.0f) + old_rect_size_change.x),
(float) (-ey + offset_y * (1.0f / scale_y - 1.0f) + old_rect_size_change.y),
0.0f
);
actor.set_scale (1.0 / scale_x, 1.0 / scale_y);

new PropertyAnimator (
actor,
duration,
Clutter.AnimationMode.EASE_IN_OUT_QUAD,
{
{ "scale-x", typeof (double), null, 1.0 },
{ "scale-y", typeof (double), null, 1.0 },
{ "translation-x", typeof (float), null, 0.0f },
{ "translation-y", typeof (float), null, 0.0f }
},
(actor) => {
unmaximizing.remove ((Meta.WindowActor) actor);
}
);
}

private void move_window_to_next_ws (Meta.Window window) {
Expand Down