diff --git a/src/Misc/NotificationStack.vala b/src/Misc/NotificationStack.vala index 23ba6593e..393fcf8dd 100644 --- a/src/Misc/NotificationStack.vala +++ b/src/Misc/NotificationStack.vala @@ -163,17 +163,15 @@ public class Gala.NotificationStack : Object { } } - public void destroy_notification (Meta.WindowActor notification) { - notification.save_easing_state (); - notification.set_easing_duration (Utils.get_animation_duration (AnimationDuration.CLOSE)); - notification.set_easing_mode (Clutter.AnimationMode.EASE_IN_QUAD); - notification.opacity = 0; - - notification.x += stack_width; - notification.restore_easing_state (); + public async void destroy_notification (Meta.WindowActor notification) { + var builder = new TransitionBuilder (notification, AnimationDuration.CLOSE, EASE_IN_QUAD); + builder.add_property ("opacity", 0u); + builder.add_property ("x", (float) (notification.x + stack_width)); notifications.remove (notification); update_positions (); + + yield builder.run (); } /** diff --git a/src/Misc/WindowEffects.vala b/src/Misc/WindowEffects.vala new file mode 100644 index 000000000..28796b2a3 --- /dev/null +++ b/src/Misc/WindowEffects.vala @@ -0,0 +1,253 @@ +/* + * Copyright 2026 elementary, Inc. (https://elementary.io) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Authored by: Leonhard Kargl + */ + +public class Gala.WindowEffects : Object { + public Clutter.Actor ui_group { get; construct; } + public NotificationStack notification_stack { get; construct; } + + private Gee.HashSet minimizing = new Gee.HashSet (); + private Gee.HashSet mapping = new Gee.HashSet (); + private Gee.HashSet destroying = new Gee.HashSet (); + private Gee.HashSet unminimizing = new Gee.HashSet (); + private Gee.HashSet changing_size = new Gee.HashSet (); + + public WindowEffects (Clutter.Actor ui_group, NotificationStack notification_stack) { + Object (ui_group: ui_group, notification_stack: notification_stack); + } + + public async void animate_map (Meta.WindowActor actor) { + var window = actor.meta_window; + + mapping.add (actor); + + switch (window.window_type) { + case Meta.WindowType.NORMAL: + if (window.maximized_vertically || window.maximized_horizontally) { + var outer_rect = window.get_frame_rect (); + actor.set_position (outer_rect.x, outer_rect.y); + } + + actor.set_pivot_point (0.5f, 1.0f); + + var builder = new TransitionBuilder (actor, AnimationDuration.HIDE, EASE_OUT_EXPO); + builder.add_property_with_from ("scale-x", 0.01, 1.0); + builder.add_property_with_from ("scale-y", 0.1, 1.0); + builder.add_property_with_from ("opacity", 0U, 255U); + yield builder.run (); + break; + + case Meta.WindowType.MODAL_DIALOG: + case Meta.WindowType.DIALOG: + dim_parent_window (window); + actor.set_pivot_point (0.5f, 0.5f); + + var builder = new TransitionBuilder (actor, 200, EASE_OUT_QUAD); + builder.add_property_with_from ("scale-x", 1.05, 1.0); + builder.add_property_with_from ("scale-y", 1.05, 1.0); + builder.add_property_with_from ("opacity", 0U, 255U); + yield builder.run (); + break; + + default: + break; + } + + mapping.remove (actor); + } + + private void dim_parent_window (Meta.Window window) { + if (window.window_type != MODAL_DIALOG) { + return; + } + + unowned var transient = window.get_transient_for (); + if (transient == null || transient == window) { + warning ("No transient found"); + return; + } + + unowned var transient_actor = (Meta.WindowActor) transient.get_compositor_private (); + var dark_effect = new Clutter.BrightnessContrastEffect (); + dark_effect.set_brightness (-0.4f); + transient_actor.add_effect_with_name ("dim-parent", dark_effect); + + window.unmanaged.connect (() => { + if (transient_actor != null && transient_actor.get_effect ("dim-parent") != null) { + transient_actor.remove_effect_by_name ("dim-parent"); + } + }); + } + + public async void animate_destroy (Meta.WindowActor actor) { + var window = actor.meta_window; + + destroying.add (actor); + + switch (window.window_type) { + case Meta.WindowType.NORMAL: + actor.set_pivot_point (0.5f, 0.5f); + actor.show (); + + var builder = new TransitionBuilder (actor, AnimationDuration.CLOSE, LINEAR); + builder.add_property ("scale-x", 0.8); + builder.add_property ("scale-y", 0.8); + builder.add_property ("opacity", 0U); + yield builder.run (); + + Utils.clear_window_cache (window); + break; + + case Meta.WindowType.MODAL_DIALOG: + case Meta.WindowType.DIALOG: + actor.set_pivot_point (0.5f, 0.5f); + + var builder = new TransitionBuilder (actor, 150, EASE_OUT_QUAD); + builder.add_property ("scale-x", 1.05); + builder.add_property ("scale-y", 1.05); + builder.add_property ("opacity", 0U); + yield builder.run (); + break; + + default: + if (NotificationStack.is_notification (window)) { + yield notification_stack.destroy_notification (actor); + } + + break; + } + + destroying.remove (actor); + } + + public async void animate_size_change (Meta.WindowActor actor, Mtk.Rectangle old_rect, Mtk.Rectangle new_rect, Clutter.Actor snapshot) { + kill_window_effects (actor); + + changing_size.add (actor); + + snapshot.set_position (old_rect.x, old_rect.y); + + ui_group.add_child (snapshot); + + var scale_x = (double) new_rect.width / old_rect.width; + var scale_y = (double) new_rect.height / old_rect.height; + + snapshot.save_easing_state (); + snapshot.set_easing_mode (Clutter.AnimationMode.EASE_IN_OUT_QUAD); + snapshot.set_easing_duration (AnimationDuration.SNAP); + snapshot.set_position (new_rect.x, new_rect.y); + snapshot.set_scale (scale_x, scale_y); + snapshot.opacity = 0; + snapshot.restore_easing_state (); + + actor.set_pivot_point (0.0f, 0.0f); + + var actor_transition_builder = new TransitionBuilder (actor, AnimationDuration.SNAP, EASE_IN_OUT_QUAD); + actor_transition_builder.add_property_with_from ("scale-x", 1.0 / scale_x, 1.0); + actor_transition_builder.add_property_with_from ("scale-y", 1.0 / scale_y, 1.0); + actor_transition_builder.add_property_with_from ("translation-x", (float) (old_rect.x - new_rect.x), 0.0f); + actor_transition_builder.add_property_with_from ("translation-y", (float) (old_rect.y - new_rect.y), 0.0f); + + yield actor_transition_builder.run (); + + ui_group.remove_child (snapshot); + changing_size.remove (actor); + } + + public async void animate_minimize (Meta.WindowActor actor) { + if (actor.get_meta_window ().window_type != NORMAL) { + return; + } + + kill_window_effects (actor); + minimizing.add (actor); + + var builder = new TransitionBuilder (actor, AnimationDuration.HIDE, EASE_IN_EXPO); + + Mtk.Rectangle icon = {}; + if (actor.get_meta_window ().get_icon_geometry (out icon)) { + var display = actor.meta_window.display; + + // Fix icon position and size according to ui scaling factor. + var ui_scale = display.get_monitor_scale (display.get_monitor_index_for_rect (icon)); + icon.x = Utils.scale_to_int (icon.x, ui_scale); + icon.y = Utils.scale_to_int (icon.y, ui_scale); + icon.width = Utils.scale_to_int (icon.width, ui_scale); + icon.height = Utils.scale_to_int (icon.height, ui_scale); + + actor.set_pivot_point ( + (actor.x - icon.x) / (icon.width - actor.width), + (actor.y - icon.y) / (icon.height - actor.height) + ); + + builder.add_property ("scale-x", (double) (icon.width / actor.width)); + builder.add_property ("scale-y", (double) (icon.height / actor.height)); + } else { + actor.set_pivot_point (0.5f, 1.0f); + + builder.add_property ("scale-x", 0.0); + builder.add_property ("scale-y", 0.0); + } + + builder.add_property ("opacity", 0u); + + yield builder.run (); + + actor.set_pivot_point (0.0f, 0.0f); + minimizing.remove (actor); + } + + public async void animate_unminimize (Meta.WindowActor actor) { + actor.show (); + + if (actor.meta_window.window_type != NORMAL) { + return; + } + + actor.remove_all_transitions (); + + unminimizing.add (actor); + + actor.set_pivot_point (0.5f, 1.0f); + + var builder = new TransitionBuilder (actor, AnimationDuration.HIDE, EASE_OUT_EXPO); + builder.add_property_with_from ("scale-x", 0.01, 1.0); + builder.add_property_with_from ("scale-y", 0.1, 1.0); + builder.add_property_with_from ("opacity", 0U, 255U); + + yield builder.run (); + + unminimizing.remove (actor); + } + + public void kill_window_effects (Meta.WindowActor actor) { + end_animation (ref unminimizing, actor); + end_animation (ref minimizing, actor); + end_animation (ref mapping, actor); + end_animation (ref destroying, actor); + end_animation (ref changing_size, actor); + } + + // Cancel attached animation of an actor and reset it + private bool end_animation (ref Gee.HashSet list, Meta.WindowActor actor) { + if (!list.contains (actor)) + return false; + + if (actor.is_destroyed ()) { + list.remove (actor); + return false; + } + + actor.remove_all_transitions (); + actor.opacity = 255U; + actor.set_scale (1.0f, 1.0f); + actor.rotation_angle_x = 0.0f; + actor.set_pivot_point (0.0f, 0.0f); + + list.remove (actor); + return true; + } +} diff --git a/src/Misc/WindowListener.vala b/src/Misc/WindowListener.vala index 5945aca1a..1f1b6582b 100644 --- a/src/Misc/WindowListener.vala +++ b/src/Misc/WindowListener.vala @@ -59,16 +59,10 @@ public class Gala.WindowListener : Object { WindowGeometry window_geometry = {}; window_geometry.inner = window.get_frame_rect (); window_geometry.outer = window.get_buffer_rect (); - - unmaximized_state_geometry.@set (window, window_geometry); } public signal void window_fullscreen_changed (Meta.Window window); - private Gee.HashMap unmaximized_state_geometry; - - private WindowListener () { - unmaximized_state_geometry = new Gee.HashMap (); - } + private WindowListener () { } private void monitor_window (Meta.Window window) { window.notify.connect (window_notify); @@ -96,10 +90,6 @@ public class Gala.WindowListener : Object { } } - public WindowGeometry? get_unmaximized_state_geometry (Meta.Window window) { - return unmaximized_state_geometry.@get (window); - } - private void window_removed (Meta.Window window) { window.notify.disconnect (window_notify); window.unmanaged.disconnect (window_removed); diff --git a/src/WindowManager.vala b/src/WindowManager.vala index 06ad43bd9..5fb6be59a 100644 --- a/src/WindowManager.vala +++ b/src/WindowManager.vala @@ -18,6 +18,20 @@ namespace Gala { public class WindowManagerGala : Meta.Plugin, WindowManager { + private class SizeChangeInfo { + public Meta.SizeChange change; + public Mtk.Rectangle old_rect; + public Clutter.Actor snapshot; + + public SizeChangeInfo (Meta.SizeChange change, Mtk.Rectangle old_rect, Clutter.Actor snapshot) { + this.change = change; + this.old_rect = old_rect; + this.snapshot = snapshot; + } + } + + private const Meta.WindowType[] ANIMATABLE_WINDOW_TYPES = { NORMAL, MODAL_DIALOG, DIALOG }; + private const string OPEN_MULTITASKING_VIEW = "dbus-send --session --dest=org.pantheon.gala --print-reply /org/pantheon/gala org.pantheon.gala.PerformAction int32:1"; private const string OPEN_APPLICATIONS_MENU = "io.elementary.wingpanel --toggle-indicator=app-launcher"; @@ -116,15 +130,8 @@ namespace Gala { private Gee.LinkedList modal_stack = new Gee.LinkedList (); - private Gee.HashSet minimizing = new Gee.HashSet (); - private Gee.HashSet maximizing = new Gee.HashSet (); - private Gee.HashSet unmaximizing = new Gee.HashSet (); - private Gee.HashSet mapping = new Gee.HashSet (); - private Gee.HashSet destroying = new Gee.HashSet (); - private Gee.HashSet unminimizing = new Gee.HashSet (); - private Meta.SizeChange? which_change = null; - private Mtk.Rectangle old_rect_size_change; - private Clutter.Actor? latest_window_snapshot; + private WindowEffects window_effects; + private Gee.HashMap pending_size_change = new Gee.HashMap (); private GLib.Settings behavior_settings; @@ -156,6 +163,7 @@ namespace Gala { AccessDialog.watch_portal (); + window_effects = new WindowEffects (ui_group, notification_stack); filter_manager = new FilterManager (this); notifications_manager = new NotificationsManager (); @@ -731,29 +739,6 @@ namespace Gala { display.unset_input_focus (display.get_current_time ()); } - private void dim_parent_window (Meta.Window window) { - if (window.window_type != MODAL_DIALOG) { - return; - } - - unowned var transient = window.get_transient_for (); - if (transient == null || transient == window) { - warning ("No transient found"); - return; - } - - unowned var transient_actor = (Meta.WindowActor) transient.get_compositor_private (); - var dark_effect = new Clutter.BrightnessContrastEffect (); - dark_effect.set_brightness (-0.4f); - transient_actor.add_effect_with_name ("dim-parent", dark_effect); - - window.unmanaged.connect (() => { - if (transient_actor != null && transient_actor.get_effect ("dim-parent") != null) { - transient_actor.remove_effect_by_name ("dim-parent"); - } - }); - } - private void set_grab_trigger (Meta.Window window, Meta.GrabOp op) { var proxy = push_modal (stage, true); @@ -1118,235 +1103,78 @@ namespace Gala { // must wait for size_changed to get updated frame_rect // as which_change is not passed to size_changed, save it as instance variable - public override void size_change (Meta.WindowActor actor, Meta.SizeChange which_change_local, Mtk.Rectangle old_frame_rect, Mtk.Rectangle old_buffer_rect) { - which_change = which_change_local; - old_rect_size_change = old_frame_rect; + public override void size_change (Meta.WindowActor actor, Meta.SizeChange which_change, Mtk.Rectangle old_frame_rect, Mtk.Rectangle old_buffer_rect) { + if (actor.meta_window.window_type != NORMAL || !Meta.Prefs.get_gnome_animations ()) { + size_change_completed (actor); + return; + } + + var snapshot = Utils.get_window_actor_snapshot (actor, old_frame_rect); - if (Meta.Prefs.get_gnome_animations ()) { - latest_window_snapshot = Utils.get_window_actor_snapshot (actor, old_frame_rect); + if (snapshot == null) { + size_change_completed (actor); + return; } + + var info = new SizeChangeInfo (which_change, old_frame_rect, snapshot); + pending_size_change[actor] = info; } // size_changed gets called after frame_rect has updated public override void size_changed (Meta.WindowActor actor) { - if (which_change == null) { + SizeChangeInfo info; + if (!pending_size_change.unset (actor, out info)) { return; } unowned var window = actor.get_meta_window (); var new_rect = window.get_frame_rect (); - switch (which_change) { + var old_rect = info.old_rect; + + switch (info.change) { case Meta.SizeChange.MAXIMIZE: case Meta.SizeChange.FULLSCREEN: // don't animate resizing of two tiled windows with mouse drag if (window.get_tile_match () != null && !window.maximized_horizontally) { - var old_end = old_rect_size_change.x + old_rect_size_change.width; + var old_end = old_rect.x + old_rect.width; var new_end = new_rect.x + new_rect.width; // a tiled window is just resized (and not moved) if its start_x or its end_x stays the same - if (old_rect_size_change.x == new_rect.x || old_end == new_end) { + if (old_rect.x == new_rect.x || old_end == new_end) { break; } } - maximize (actor, new_rect.x, new_rect.y, new_rect.width, new_rect.height); + window_effects.animate_size_change.begin (actor, old_rect, new_rect, info.snapshot); break; case Meta.SizeChange.UNMAXIMIZE: case Meta.SizeChange.UNFULLSCREEN: - unmaximize (actor, new_rect.x, new_rect.y, new_rect.width, new_rect.height); + window_effects.animate_size_change.begin (actor, old_rect, new_rect, info.snapshot); break; default: break; } - which_change = null; size_change_completed (actor); } public override void minimize (Meta.WindowActor actor) { - if (!Meta.Prefs.get_gnome_animations () || - actor.get_meta_window ().window_type != Meta.WindowType.NORMAL) { + if (!can_animate (actor)) { minimize_completed (actor); return; } - kill_window_effects (actor); - minimizing.add (actor); - - Mtk.Rectangle icon = {}; - if (actor.get_meta_window ().get_icon_geometry (out icon)) { - // Fix icon position and size according to ui scaling factor. - var ui_scale = get_display ().get_monitor_scale (get_display ().get_monitor_index_for_rect (icon)); - icon.x = Utils.scale_to_int (icon.x, ui_scale); - icon.y = Utils.scale_to_int (icon.y, ui_scale); - icon.width = Utils.scale_to_int (icon.width, ui_scale); - icon.height = Utils.scale_to_int (icon.height, ui_scale); - - actor.set_pivot_point ( - (actor.x - icon.x) / (icon.width - actor.width), - (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); - }); - } 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); - }); - } - } - - private void maximize (Meta.WindowActor actor, int ex, int ey, int ew, int eh) { - unowned var window = actor.get_meta_window (); - - kill_window_effects (actor); - - if (!Meta.Prefs.get_gnome_animations () || - latest_window_snapshot == null || - window.window_type != Meta.WindowType.NORMAL) { - return; - } - - var duration = AnimationDuration.SNAP; - - maximizing.add (actor); - latest_window_snapshot.set_position (old_rect_size_change.x, old_rect_size_change.y); - - ui_group.add_child (latest_window_snapshot); - - // FIMXE that's a hacky part. There is a short moment right after maximized_completed - // where the texture is screwed up and shows things it's not supposed to show, - // resulting in flashing. Waiting here transparently shortly fixes that issue. There - // appears to be no signal that would inform when that moment happens. - // We can't spend arbitrary amounts of time transparent since the overlay fades away, - // about a third has proven to be a solid time. So this fix will only apply for - // durations >= FLASH_PREVENT_TIMEOUT*3 - const int FLASH_PREVENT_TIMEOUT = 80; - var delay = 0; - if (FLASH_PREVENT_TIMEOUT <= duration / 3) { - actor.opacity = 0; - delay = FLASH_PREVENT_TIMEOUT; - Timeout.add (FLASH_PREVENT_TIMEOUT, () => { - actor.opacity = 255; - return false; - }); - } - - var scale_x = (double) ew / old_rect_size_change.width; - var scale_y = (double) 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.restore_easing_state (); - - // the opacity animation is special, since we have to wait for the - // FLASH_PREVENT_TIMEOUT to be done before we can safely fade away - latest_window_snapshot.save_easing_state (); - latest_window_snapshot.set_easing_delay (delay); - latest_window_snapshot.set_easing_duration (duration - delay); - latest_window_snapshot.opacity = 0; - latest_window_snapshot.restore_easing_state (); - - ulong maximize_old_handler_id = 0; - maximize_old_handler_id = latest_window_snapshot.transition_stopped.connect ((snapshot, name, is_finished) => { - snapshot.disconnect (maximize_old_handler_id); - - actor.set_translation (0.0f, 0.0f, 0.0f); - - unowned var parent = snapshot.get_parent (); - if (parent != null) { - parent.remove_child (snapshot); - } - }); - - latest_window_snapshot = null; - - actor.set_pivot_point (0.0f, 0.0f); - actor.set_translation (old_rect_size_change.x - ex, old_rect_size_change.y - ey, 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); - maximizing.remove (actor); - }); + window_effects.animate_minimize.begin (actor, () => minimize_completed (actor)); } public override void unminimize (Meta.WindowActor actor) { - if (!Meta.Prefs.get_gnome_animations ()) { - actor.show (); + if (!can_animate (actor)) { unminimize_completed (actor); return; } - var duration = AnimationDuration.HIDE; - unowned var window = actor.get_meta_window (); - - actor.remove_all_transitions (); - actor.show (); - - switch (window.window_type) { - case Meta.WindowType.NORMAL: - unminimizing.add (actor); - - actor.set_pivot_point (0.5f, 1.0f); - actor.set_scale (0.01f, 0.1f); - actor.opacity = 0U; - - actor.save_easing_state (); - actor.set_easing_mode (Clutter.AnimationMode.EASE_OUT_EXPO); - actor.set_easing_duration (duration); - actor.set_scale (1.0f, 1.0f); - actor.opacity = 255U; - actor.restore_easing_state (); - - ulong unminimize_handler_id = 0UL; - unminimize_handler_id = actor.transitions_completed.connect (() => { - actor.disconnect (unminimize_handler_id); - unminimizing.remove (actor); - unminimize_completed (actor); - }); - - break; - default: - unminimize_completed (actor); - break; - } + window_effects.animate_unminimize.begin (actor, () => unminimize_completed (actor)); } public override void map (Meta.WindowActor actor) { @@ -1358,230 +1186,38 @@ namespace Gala { actor.show (); // Notifications initial animation is handled by the notification stack - if (NotificationStack.is_notification (window) || !Meta.Prefs.get_gnome_animations ()) { - dim_parent_window (window); + if (NotificationStack.is_notification (window) || !can_animate (actor)) { map_completed (actor); return; } - animate_map.begin (actor); - } - - private async void animate_map (Meta.WindowActor actor) { - var window = actor.meta_window; - - mapping.add (actor); - - switch (window.window_type) { - case Meta.WindowType.NORMAL: - if (window.maximized_vertically || window.maximized_horizontally) { - var outer_rect = window.get_frame_rect (); - actor.set_position (outer_rect.x, outer_rect.y); - } - - actor.set_pivot_point (0.5f, 1.0f); - - var builder = new TransitionBuilder (actor, AnimationDuration.HIDE, EASE_OUT_EXPO); - builder.add_property_with_from ("scale-x", 0.01, 1.0); - builder.add_property_with_from ("scale-y", 0.1, 1.0); - builder.add_property_with_from ("opacity", 0U, 255U); - yield builder.run (); - break; - - case Meta.WindowType.MODAL_DIALOG: - case Meta.WindowType.DIALOG: - dim_parent_window (window); - actor.set_pivot_point (0.5f, 0.5f); - - var builder = new TransitionBuilder (actor, 200, EASE_OUT_QUAD); - builder.add_property_with_from ("scale-x", 1.05, 1.0); - builder.add_property_with_from ("scale-y", 1.05, 1.0); - builder.add_property_with_from ("opacity", 0U, 255U); - yield builder.run (); - break; - - default: - break; - } - - mapping.remove (actor); - map_completed (actor); + window_effects.animate_map.begin (actor, () => map_completed (actor)); } public override void destroy (Meta.WindowActor actor) { - unowned var window = actor.get_meta_window (); - actor.remove_all_transitions (); - if (NotificationStack.is_notification (window)) { - if (Meta.Prefs.get_gnome_animations ()) { - destroying.add (actor); - } - - notification_stack.destroy_notification (actor); - - if (Meta.Prefs.get_gnome_animations ()) { - ulong destroy_handler_id = 0UL; - destroy_handler_id = actor.transitions_completed.connect (() => { - actor.disconnect (destroy_handler_id); - destroying.remove (actor); - destroy_completed (actor); - }); - } else { - destroy_completed (actor); - } - + if (!can_animate (actor)) { + destroy_completed (actor); return; } - animate_destroy.begin (actor); + window_effects.animate_destroy.begin (actor, () => destroy_completed (actor)); } - private async void animate_destroy (Meta.WindowActor actor) { - var window = actor.meta_window; - - destroying.add (actor); - - switch (window.window_type) { - case Meta.WindowType.NORMAL: - actor.set_pivot_point (0.5f, 0.5f); - actor.show (); - - var builder = new TransitionBuilder (actor, AnimationDuration.CLOSE, LINEAR); - builder.add_property ("scale-x", 0.8); - builder.add_property ("scale-y", 0.8); - builder.add_property ("opacity", 0U); - yield builder.run (); - - Utils.clear_window_cache (window); - break; - - case Meta.WindowType.MODAL_DIALOG: - case Meta.WindowType.DIALOG: - actor.set_pivot_point (0.5f, 0.5f); - - var builder = new TransitionBuilder (actor, 150, EASE_OUT_QUAD); - builder.add_property ("scale-x", 1.05); - builder.add_property ("scale-y", 1.05); - builder.add_property ("opacity", 0U); - yield builder.run (); - break; - - default: - break; - } - - destroying.remove (actor); - destroy_completed (actor); - } - - private void unmaximize (Meta.WindowActor actor, int ex, int ey, int ew, int eh) { - unowned var window = actor.get_meta_window (); - - kill_window_effects (actor); - - if (!Meta.Prefs.get_gnome_animations () || - latest_window_snapshot == null || - window.window_type != Meta.WindowType.NORMAL) { - return; - } - - var duration = AnimationDuration.SNAP; - - float offset_x, offset_y; - var unmaximized_window_geometry = WindowListener.get_default ().get_unmaximized_state_geometry (window); - - if (unmaximized_window_geometry != null) { - offset_x = unmaximized_window_geometry.outer.x - unmaximized_window_geometry.inner.x; - offset_y = unmaximized_window_geometry.outer.y - unmaximized_window_geometry.inner.y; - } else { - offset_x = 0; - offset_y = 0; - } - - unmaximizing.add (actor); - - latest_window_snapshot.set_position (old_rect_size_change.x, old_rect_size_change.y); - - 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); - - unowned var parent = snapshot.get_parent (); - if (parent != null) { - parent.remove_child (snapshot); - } - }); - - latest_window_snapshot = null; - - var buffer_rect = window.get_buffer_rect (); - var frame_rect = window.get_frame_rect (); - var real_actor_offset_x = frame_rect.x - buffer_rect.x; - var real_actor_offset_y = frame_rect.y - buffer_rect.y; - - 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); - }); - } - - // Cancel attached animation of an actor and reset it - private bool end_animation (ref Gee.HashSet list, Meta.WindowActor actor) { - if (!list.contains (actor)) - return false; - - if (actor.is_destroyed ()) { - list.remove (actor); - return false; - } - - actor.remove_all_transitions (); - actor.opacity = 255U; - actor.set_scale (1.0f, 1.0f); - actor.rotation_angle_x = 0.0f; - actor.set_pivot_point (0.0f, 0.0f); - - list.remove (actor); - return true; + private bool can_animate (Meta.WindowActor actor) { + /* For other window types mutter expects that we call the corresponding completed method + immediately otherwise it can lead to crashes. However async methods will always + only return in the next main loop iteration. So we have to guard here already. */ + return actor.meta_window.window_type in ANIMATABLE_WINDOW_TYPES; } public override void kill_window_effects (Meta.WindowActor actor) { - if (end_animation (ref unminimizing, actor)) - unminimize_completed (actor); - if (end_animation (ref minimizing, actor)) - minimize_completed (actor); + if (pending_size_change.unset (actor)) { + size_change_completed (actor); + } - end_animation (ref mapping, actor); - end_animation (ref destroying, actor); - end_animation (ref unmaximizing, actor); - end_animation (ref maximizing, actor); + window_effects.kill_window_effects (actor); } public override void switch_workspace (int from, int to, Meta.MotionDirection direction) { diff --git a/src/meson.build b/src/meson.build index c6bd9d9ca..93f6f9fa3 100644 --- a/src/meson.build +++ b/src/meson.build @@ -38,6 +38,7 @@ gala_bin_sources = files( 'Misc/SessionManager.vala', 'Misc/WindowAttentionTracker.vala', 'Misc/WindowDragProvider.vala', + 'Misc/WindowEffects.vala', 'Misc/WindowListener.vala', 'Misc/WindowMover.vala', 'Misc/WindowStateSaver.vala',