diff --git a/lib/Utils.vala b/lib/Utils.vala index d48eb754..f651d622 100644 --- a/lib/Utils.vala +++ b/lib/Utils.vala @@ -400,5 +400,50 @@ namespace Gala { return texture; } + + private static HashTable regions = new HashTable (null, null); + + public static void x11_set_window_pass_through (Meta.Window window) { + unowned var x11_display = window.display.get_x11_display (); + +#if HAS_MUTTER46 + var x_window = x11_display.lookup_xwindow (window); +#else + var x_window = window.get_xwindow (); +#endif + unowned var xdisplay = x11_display.get_xdisplay (); + + regions[window] = X.Fixes.create_region_from_window (xdisplay, x_window, 0); + + X.Xrectangle rect = {}; + + var region = X.Fixes.create_region (xdisplay, {rect}); + + X.Fixes.set_window_shape_region (xdisplay, x_window, 2, 0, 0, region); + + X.Fixes.destroy_region (xdisplay, region); + } + + public static void x11_unset_window_pass_through (Meta.Window window) { + unowned var x11_display = window.display.get_x11_display (); + +#if HAS_MUTTER46 + var x_window = x11_display.lookup_xwindow (window); +#else + var x_window = window.get_xwindow (); +#endif + unowned var xdisplay = x11_display.get_xdisplay (); + + var region = regions[window]; + + if (region == null) { + return; + } + + X.Fixes.set_window_shape_region (xdisplay, x_window, 2, 0, 0, region); + + regions.remove (window); + X.Fixes.destroy_region (xdisplay, region); + } } } diff --git a/src/Gestures/GesturePropertyTransition.vala b/src/Gestures/GesturePropertyTransition.vala new file mode 100644 index 00000000..e2471c0d --- /dev/null +++ b/src/Gestures/GesturePropertyTransition.vala @@ -0,0 +1,180 @@ +/* + * Copyright 2024 elementary, Inc. (https://elementary.io) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Authored by: Leonhard Kargl + */ + +/** + * A class that will animate a property of a {@link Clutter.Actor} one to one with a gesture or + * with easing without a gesture. Respects the enable animation setting. + */ +public class Gala.GesturePropertyTransition : Object { + public delegate void DoneCallback (); + + /** + * The actor whose property will be animated. + */ + public Clutter.Actor actor { get; construct; } + + public GestureTracker gesture_tracker { get; construct; } + + /** + * The property that will be animated. To be properly animated it has to be marked as + * animatable in the Clutter documentation and should be numeric. + */ + public string property { get; construct; } + + /** + * The starting value of the animation or null to use the current value. The value + * has to be of the same type as the property. + */ + public Value? from_value { get; construct set; } + + /** + * The value to animate to. It has to be of the same type as the property. + */ + 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. + */ + public Value? intermediate_value { get; construct; } + + /** + * 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 DoneCallback? done_callback; + + public GesturePropertyTransition ( + Clutter.Actor actor, + GestureTracker gesture_tracker, + string property, + Value? from_value, + Value to_value, + Value? intermediate_value = null + ) { + Object ( + actor: actor, + gesture_tracker: gesture_tracker, + property: property, + from_value: from_value, + to_value: to_value, + intermediate_value: intermediate_value + ); + } + + /** + * Starts animating the property from {@link from_value} to {@link to_value}. If with_gesture is true + * it will connect to the gesture trackers signals and animate according to the input finishing with an easing + * to the final position. If with_gesture is false it will just ease to the {@link to_value}. + * #this will keep itself alive until the animation finishes so it is safe to immediatly unref it after creation and calling start. + * + * @param done_callback a callback for when the transition finishes. It is guaranteed to be called exactly once. + */ + public void 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; + + 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; + } + + 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; + } + + 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); + actor.set_property (property, value_from_float (animation_value)); + }; + + GestureTracker.OnEnd on_animation_end = (percentage, cancel_action, calculated_duration) => { + 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.restore_easing_state (); + + unowned var transition = actor.get_transition (property); + if (transition == null) { + finish (); + } else { + transition.stopped.connect (finish); + } + }; + + if (with_gesture && AnimationsSettings.get_enable_animations ()) { + gesture_tracker.connect_handlers (on_animation_begin, on_animation_update, on_animation_end); + } else { + on_animation_begin (0); + if (intermediate_value != null) { + 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.restore_easing_state (); + + unowned var transition = actor.get_transition (property); + if (transition == null) { + on_animation_end (1, false, gesture_tracker.min_animation_duration); + } else { + transition.stopped.connect (() => on_animation_end (1, false, gesture_tracker.min_animation_duration)); + } + } else { + on_animation_end (1, false, gesture_tracker.min_animation_duration); + } + } + } + + private void finish () { + if (done_callback != null) { + done_callback (); + } + + unref (); + } + + private float value_to_float (Value val) { + Value float_val = Value (typeof (float)); + if (val.transform (ref float_val)) { + return float_val.get_float (); + } + + critical ("Non numeric property specified"); + return 0; + } + + private Value value_from_float (float f) { + var float_val = Value (typeof (float)); + float_val.set_float (f); + + var val = Value (actual_from_value.type ()); + + if (!float_val.transform (ref val)) { + warning ("Failed to transform float to give type"); + } + + return val; + } +} diff --git a/src/PantheonShell.vala b/src/PantheonShell.vala index 1e0f173f..0dabcb68 100644 --- a/src/PantheonShell.vala +++ b/src/PantheonShell.vala @@ -243,25 +243,7 @@ namespace Gala { return; } - Meta.Side side = TOP; - switch (anchor) { - case TOP: - break; - - case BOTTOM: - side = BOTTOM; - break; - - case LEFT: - side = LEFT; - break; - - case RIGHT: - side = RIGHT; - break; - } - - ShellClientsManager.get_instance ().set_anchor (window, side); + ShellClientsManager.get_instance ().set_anchor (window, anchor); } internal static void focus_panel (Wl.Client client, Wl.Resource resource) { diff --git a/src/ShellClients/DelegateActor.vala b/src/ShellClients/DelegateActor.vala deleted file mode 100644 index bd93f912..00000000 --- a/src/ShellClients/DelegateActor.vala +++ /dev/null @@ -1,42 +0,0 @@ -/* - * SPDX-License-Identifier: GPL-3.0-or-later - * SPDX-FileCopyrightText: 2024 elementary, Inc. (https://elementary.io) - */ - -/* This class is used to workaround https://github.com/elementary/gala/issues/2101 */ -public class Gala.DelegateActor : GLib.Object { - public const int OUT_OF_BOUNDS = 1000000; - - public Meta.WindowActor actor { get; construct; } - - /* Current window actor position or position before the window was moved out of bounds */ - public float x { get; private set; default = 0.0f; } - public float y { get; private set; default = 0.0f; } - - /* Current window position or position before it was moved out of bounds */ - public int actual_x { get; private set; default = 0; } - public int actual_y { get; private set; default = 0; } - - public DelegateActor (Meta.WindowActor actor) { - Object (actor: actor); - } - - construct { - actor.meta_window.position_changed.connect ((_window) => { - var rect = _window.get_frame_rect (); - - if (rect.x != OUT_OF_BOUNDS) { - actual_x = rect.x; - Idle.add_once (() => { - x = actor.x; - }); - } - if (rect.y != OUT_OF_BOUNDS) { - actual_y = rect.y; - Idle.add_once (() => { - y = actor.y; - }); - } - }); - } -} diff --git a/src/ShellClients/HideTracker.vala b/src/ShellClients/HideTracker.vala index 35ed7a0d..ee6f9cd2 100644 --- a/src/ShellClients/HideTracker.vala +++ b/src/ShellClients/HideTracker.vala @@ -16,17 +16,7 @@ public class Gala.HideTracker : Object { public Meta.Display display { get; construct; } public unowned PanelWindow panel { get; construct; } - private Pantheon.Desktop.HideMode _hide_mode = NEVER; - public Pantheon.Desktop.HideMode hide_mode { - get { - return _hide_mode; - } - set { - _hide_mode = value; - - setup_barrier (); - } - } + public Pantheon.Desktop.HideMode hide_mode { get; set; } private Clutter.PanAction pan_action; @@ -97,6 +87,16 @@ public class Gala.HideTracker : Object { pan_action.pan.connect (on_pan); display.get_stage ().add_action_full ("panel-swipe-gesture", CAPTURE, pan_action); + + panel.notify["anchor"].connect (setup_barrier); + + var monitor_manager = display.get_context ().get_backend ().get_monitor_manager (); + monitor_manager.monitors_changed.connect (() => { + setup_barrier (); //Make sure barriers are still on the primary monitor + schedule_update (); + }); + + setup_barrier (); } #if !HAS_MUTTER45 @@ -171,7 +171,7 @@ public class Gala.HideTracker : Object { continue; } - if (!panel.get_custom_window_rect ().overlap (window.get_frame_rect ())) { + if (!panel.window.get_frame_rect ().overlap (window.get_frame_rect ())) { continue; } diff --git a/src/ShellClients/PanelClone.vala b/src/ShellClients/PanelClone.vala index 15fc8d23..6a32b101 100644 --- a/src/ShellClients/PanelClone.vala +++ b/src/ShellClients/PanelClone.vala @@ -48,8 +48,8 @@ public class Gala.PanelClone : Object { actor = (Meta.WindowActor) panel.window.get_compositor_private (); // WindowActor position and Window position aren't necessarily the same. // The clone needs the actor position - panel.delegate_actor.notify["x"].connect (update_clone_position); - panel.delegate_actor.notify["y"].connect (update_clone_position); + actor.notify["x"].connect (update_clone_position); + actor.notify["y"].connect (update_clone_position); // Actor visibility might be changed by something else e.g. workspace switch // but we want to keep it in sync with us actor.notify["visible"].connect (update_visible); @@ -97,7 +97,7 @@ public class Gala.PanelClone : Object { switch (panel.anchor) { case TOP: case BOTTOM: - return panel.delegate_actor.x; + return actor.x; default: return 0; } @@ -106,9 +106,9 @@ public class Gala.PanelClone : Object { private float calculate_clone_y (bool hidden) { switch (panel.anchor) { case TOP: - return hidden ? panel.delegate_actor.y - actor.height : panel.delegate_actor.y; + return hidden ? actor.y - actor.height : actor.y; case BOTTOM: - return hidden ? panel.delegate_actor.y + actor.height : panel.delegate_actor.y; + return hidden ? actor.y + actor.height : actor.y; default: return 0; } @@ -128,7 +128,7 @@ public class Gala.PanelClone : Object { panel_hidden = true; if (!Meta.Util.is_wayland_compositor ()) { - panel.window.move_frame (false, DelegateActor.OUT_OF_BOUNDS, DelegateActor.OUT_OF_BOUNDS); + Utils.x11_set_window_pass_through (panel.window); } if (panel.anchor != TOP && panel.anchor != BOTTOM) { @@ -151,7 +151,7 @@ public class Gala.PanelClone : Object { } if (!Meta.Util.is_wayland_compositor ()) { - panel.window.move_frame (false, panel.delegate_actor.actual_x, panel.delegate_actor.actual_y); + Utils.x11_unset_window_pass_through (panel.window); } clone.save_easing_state (); diff --git a/src/ShellClients/PanelWindow.vala b/src/ShellClients/PanelWindow.vala index 107218e1..7e29bd65 100644 --- a/src/ShellClients/PanelWindow.vala +++ b/src/ShellClients/PanelWindow.vala @@ -10,35 +10,21 @@ public class Gala.PanelWindow : Object { public WindowManager wm { get; construct; } public Meta.Window window { get; construct; } + public Pantheon.Desktop.Anchor anchor { get; construct set; } - public bool hidden { get; private set; default = false; } + private WindowPositioner window_positioner; - public Meta.Side anchor; - - public DelegateActor delegate_actor; private PanelClone clone; - private uint idle_move_id = 0; - private int width = -1; private int height = -1; - public PanelWindow (WindowManager wm, Meta.Window window, Meta.Side anchor) { - Object (wm: wm, window: window); - - // Meta.Side seems to be currently not supported as GLib.Object property ...? - // At least it always crashed for me with some paramspec, g_type_fundamental backtrace - this.anchor = anchor; + public PanelWindow (WindowManager wm, Meta.Window window, Pantheon.Desktop.Anchor anchor) { + Object (wm: wm, window: window, anchor: anchor); } construct { - window.size_changed.connect (position_window); - window.unmanaging.connect (() => { - if (idle_move_id != 0) { - Source.remove (idle_move_id); - } - if (window_struts.remove (window)) { update_struts (); } @@ -46,16 +32,20 @@ public class Gala.PanelWindow : Object { window.stick (); - delegate_actor = new DelegateActor ((Meta.WindowActor) window.get_compositor_private ()); clone = new PanelClone (wm, this); - var monitor_manager = wm.get_display ().get_context ().get_backend ().get_monitor_manager (); - monitor_manager.monitors_changed.connect (() => update_anchor (anchor)); - monitor_manager.monitors_changed_internal.connect (() => update_anchor (anchor)); + unowned var display = wm.get_display (); - var workspace_manager = wm.get_display ().get_workspace_manager (); + unowned var workspace_manager = display.get_workspace_manager (); workspace_manager.workspace_added.connect (update_strut); workspace_manager.workspace_removed.connect (update_strut); + + window.size_changed.connect (update_strut); + window.position_changed.connect (update_strut); + + window_positioner = new WindowPositioner (display, window, WindowPositioner.Position.from_anchor (anchor)); + + notify["anchor"].connect (() => window_positioner.position = WindowPositioner.Position.from_anchor (anchor)); } #if HAS_MUTTER45 @@ -64,8 +54,6 @@ public class Gala.PanelWindow : Object { public Meta.Rectangle get_custom_window_rect () { #endif var window_rect = window.get_frame_rect (); - window_rect.x = delegate_actor.actual_x; - window_rect.y = delegate_actor.actual_y; if (width > 0) { window_rect.width = width; @@ -87,73 +75,9 @@ public class Gala.PanelWindow : Object { this.width = width; this.height = height; - position_window (); - set_hide_mode (clone.hide_mode); // Resetup barriers etc. - } - - public void update_anchor (Meta.Side anchor) { - this.anchor = anchor; - - position_window (); - set_hide_mode (clone.hide_mode); // Resetup barriers etc. - } - - private void position_window () { - var display = wm.get_display (); - var monitor_geom = display.get_monitor_geometry (display.get_primary_monitor ()); - var window_rect = window.get_frame_rect (); - - switch (anchor) { - case TOP: - position_window_top (monitor_geom, window_rect); - break; - - case BOTTOM: - position_window_bottom (monitor_geom, window_rect); - break; - - default: - warning ("Side not supported yet"); - break; - } - update_strut (); } -#if HAS_MUTTER45 - private void position_window_top (Mtk.Rectangle monitor_geom, Mtk.Rectangle window_rect) { -#else - private void position_window_top (Meta.Rectangle monitor_geom, Meta.Rectangle window_rect) { -#endif - var x = monitor_geom.x + (monitor_geom.width - window_rect.width) / 2; - - move_window_idle (x, monitor_geom.y); - } - -#if HAS_MUTTER45 - private void position_window_bottom (Mtk.Rectangle monitor_geom, Mtk.Rectangle window_rect) { -#else - private void position_window_bottom (Meta.Rectangle monitor_geom, Meta.Rectangle window_rect) { -#endif - var x = monitor_geom.x + (monitor_geom.width - window_rect.width) / 2; - var y = monitor_geom.y + monitor_geom.height - window_rect.height; - - move_window_idle (x, y); - } - - private void move_window_idle (int x, int y) { - if (idle_move_id != 0) { - Source.remove (idle_move_id); - } - - idle_move_id = Idle.add (() => { - window.move_frame (false, x, y); - - idle_move_id = 0; - return Source.REMOVE; - }); - } - public void set_hide_mode (Pantheon.Desktop.HideMode hide_mode) { clone.hide_mode = hide_mode; @@ -177,7 +101,7 @@ public class Gala.PanelWindow : Object { Meta.Strut strut = { rect, - anchor + side_from_anchor (anchor) }; window_struts[window] = strut; @@ -203,4 +127,20 @@ public class Gala.PanelWindow : Object { update_struts (); } } + + private Meta.Side side_from_anchor (Pantheon.Desktop.Anchor anchor) { + switch (anchor) { + case BOTTOM: + return BOTTOM; + + case LEFT: + return LEFT; + + case RIGHT: + return RIGHT; + + default: + return TOP; + } + } } diff --git a/src/ShellClients/ShellClientsManager.vala b/src/ShellClients/ShellClientsManager.vala index 757b1f08..dfaa8b0f 100644 --- a/src/ShellClients/ShellClientsManager.vala +++ b/src/ShellClients/ShellClientsManager.vala @@ -143,16 +143,16 @@ public class Gala.ShellClientsManager : Object { xdisplay.change_property (x_window, atom, (X.Atom) 4, 32, 0, (uchar[]) dock_atom, 1); } - public void set_anchor (Meta.Window window, Meta.Side side) { + public void set_anchor (Meta.Window window, Pantheon.Desktop.Anchor anchor) { if (window in panel_windows) { - panel_windows[window].update_anchor (side); + panel_windows[window].anchor = anchor; return; } make_dock (window); // TODO: Return if requested by window that's not a trusted client? - panel_windows[window] = new PanelWindow (wm, window, side); + panel_windows[window] = new PanelWindow (wm, window, anchor); // connect_after so we make sure the PanelWindow can destroy its barriers and struts window.unmanaging.connect_after ((_window) => panel_windows.remove (_window)); @@ -226,8 +226,30 @@ public class Gala.ShellClientsManager : Object { switch (key) { case "anchor": - int parsed; // Will be used as Meta.Side which is a 4 value bitfield so check bounds for that - if (int.try_parse (val, out parsed) && 0 <= parsed && parsed <= 15) { + int meta_side_parsed; // Will be used as Meta.Side which is a 4 value bitfield so check bounds for that + if (int.try_parse (val, out meta_side_parsed) && 0 <= meta_side_parsed && meta_side_parsed <= 15) { + //FIXME: Next major release change dock and wingpanel calls to get rid of this + Pantheon.Desktop.Anchor parsed = TOP; + switch ((Meta.Side) meta_side_parsed) { + case BOTTOM: + parsed = BOTTOM; + break; + + case LEFT: + parsed = LEFT; + break; + + case RIGHT: + parsed = RIGHT; + break; + + default: + break; + } + + set_anchor (window, parsed); + // We need to set a second time because the intention is to call this before the window is shown which it is on wayland + // but on X the window was already shown when we get here so we have to call again to instantly apply it. set_anchor (window, parsed); } else { warning ("Failed to parse %s as anchor", val); diff --git a/src/ShellClients/WindowPositioner.vala b/src/ShellClients/WindowPositioner.vala index 62b7928d..daf838f1 100644 --- a/src/ShellClients/WindowPositioner.vala +++ b/src/ShellClients/WindowPositioner.vala @@ -7,13 +7,28 @@ public class Gala.WindowPositioner : Object { public enum Position { - CENTER + TOP, + BOTTOM, + CENTER; + + public static Position from_anchor (Pantheon.Desktop.Anchor anchor) { + if (anchor > 1) { + warning ("Position %s not supported yet", anchor.to_string ()); + return CENTER; + } + + return (Position) anchor; + } } public Meta.Display display { get; construct; } public Meta.Window window { get; construct; } - public Position position { get; private set; } - public Variant? position_data { get; private set; } + /** + * This may only be set after the window was shown. + * The initial position should only be given in the constructor. + */ + public Position position { get; construct set; } + public Variant? position_data { get; construct set; } public WindowPositioner (Meta.Display display, Meta.Window window, Position position, Variant? position_data = null) { Object (display: display, window: window, position: position, position_data: position_data); @@ -29,29 +44,34 @@ public class Gala.WindowPositioner : Object { unowned var monitor_manager = display.get_context ().get_backend ().get_monitor_manager (); monitor_manager.monitors_changed.connect (position_window); monitor_manager.monitors_changed_internal.connect (position_window); - } - /** - * This may only be called after the window was shown. - */ - public void update_position (Position new_position, Variant? new_position_data = null) { - position = new_position; - position_data = new_position_data; - - position_window (); + notify["position"].connect (position_window); + notify["position-data"].connect (position_window); } private void position_window () { int x = 0, y = 0; + var window_rect = window.get_frame_rect (); + switch (position) { case CENTER: var monitor_geom = display.get_monitor_geometry (display.get_primary_monitor ()); - var window_rect = window.get_frame_rect (); - x = monitor_geom.x + (monitor_geom.width - window_rect.width) / 2; y = monitor_geom.y + (monitor_geom.height - window_rect.height) / 2; break; + + case TOP: + var monitor_geom = display.get_monitor_geometry (display.get_primary_monitor ()); + x = monitor_geom.x + (monitor_geom.width - window_rect.width) / 2; + y = monitor_geom.y; + break; + + case BOTTOM: + var monitor_geom = display.get_monitor_geometry (display.get_primary_monitor ()); + x = monitor_geom.x + (monitor_geom.width - window_rect.width) / 2; + y = monitor_geom.y + monitor_geom.height - window_rect.height; + break; } window.move_frame (false, x, y); diff --git a/src/Widgets/MultitaskingView.vala b/src/Widgets/MultitaskingView.vala index 4f58707e..a6bf15a7 100644 --- a/src/Widgets/MultitaskingView.vala +++ b/src/Widgets/MultitaskingView.vala @@ -383,58 +383,17 @@ namespace Gala { target_workspace.activate (display.get_current_time ()); } - GestureTracker.OnUpdate on_animation_update = (percentage) => { - var x = GestureTracker.animation_value (initial_x, target_x, percentage, true); - var icon_group_opacity = GestureTracker.animation_value (0.0f, 1.0f, percentage, false); - - if (is_nudge_animation) { - x = x.clamp (initial_x - nudge_gap, initial_x + nudge_gap); - } - - workspaces.x = x; - - if (!is_nudge_animation) { - active_icon_group.backdrop_opacity = 1.0f - icon_group_opacity; - target_icon_group.backdrop_opacity = icon_group_opacity; - } - }; + 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); + } GestureTracker.OnEnd on_animation_end = (percentage, cancel_action, calculated_duration) => { switching_workspace_with_gesture = false; - var duration = is_nudge_animation ? - (uint) (AnimationDuration.NUDGE / 2) : - (uint) calculated_duration; - - workspaces.save_easing_state (); - workspaces.set_easing_mode (Clutter.AnimationMode.EASE_OUT_QUAD); - workspaces.set_easing_duration (duration); - workspaces.x = (is_nudge_animation || cancel_action) ? initial_x : target_x; - workspaces.restore_easing_state (); - - if (!is_nudge_animation) { - if (AnimationsSettings.get_enable_animations ()) { - var active_transition = new Clutter.PropertyTransition ("backdrop-opacity") { - duration = duration, - remove_on_complete = true - }; - active_transition.set_from_value (active_icon_group.backdrop_opacity); - active_transition.set_to_value (cancel_action ? 1.0f : 0.0f); - active_icon_group.add_transition ("backdrop-opacity", active_transition); - - var target_transition = new Clutter.PropertyTransition ("backdrop-opacity") { - duration = duration, - remove_on_complete = true - }; - target_transition.set_from_value (target_icon_group.backdrop_opacity); - target_transition.set_to_value (cancel_action ? 0.0f : 1.0f); - target_icon_group.add_transition ("backdrop-opacity", target_transition); - } else { - active_icon_group.backdrop_opacity = cancel_action ? 1.0f : 0.0f; - target_icon_group.backdrop_opacity = cancel_action ? 0.0f : 1.0f; - } - } - if (is_nudge_animation || cancel_action) { active_workspace.activate (display.get_current_time ()); } @@ -443,7 +402,7 @@ namespace Gala { if (!AnimationsSettings.get_enable_animations ()) { on_animation_end (1, false, 0); } else { - workspace_gesture_tracker.connect_handlers (null, (owned) on_animation_update, (owned) on_animation_end); + workspace_gesture_tracker.connect_handlers (null, null, (owned) on_animation_end); } } diff --git a/src/Widgets/WindowClone.vala b/src/Widgets/WindowClone.vala index b984dad2..4aa1c5cb 100644 --- a/src/Widgets/WindowClone.vala +++ b/src/Widgets/WindowClone.vala @@ -207,8 +207,6 @@ public class Gala.WindowClone : Clutter.Actor { set_child_above_sibling (window_icon, clone); set_child_above_sibling (window_title, clone); - transition_to_original_state (false); - check_shadow_requirements (); if (should_fade ()) { @@ -221,7 +219,7 @@ public class Gala.WindowClone : Clutter.Actor { // window was opened, so we stay at our old place. if (was_waiting && slot != null) { opacity = 0; - take_slot (slot); + take_slot (slot, true); opacity = 255; request_reposition (); @@ -268,7 +266,7 @@ public class Gala.WindowClone : Clutter.Actor { * * @param animate Animate the transformation of the placement */ - public void transition_to_original_state (bool animate, bool with_gesture = false, bool is_cancel_animation = false) { + public void transition_to_original_state (bool with_gesture = false, bool is_cancel_animation = false) { var outer_rect = window.get_frame_rect (); unowned var display = window.get_display (); @@ -278,8 +276,6 @@ public class Gala.WindowClone : Clutter.Actor { var offset_x = monitor_geom.x; var offset_y = monitor_geom.y; - var initial_x = x; - var initial_y = y; var initial_width = width; var initial_height = height; @@ -290,22 +286,23 @@ public class Gala.WindowClone : Clutter.Actor { in_slot_animation = true; place_widgets (outer_rect.width, outer_rect.height, initial_scale); + new GesturePropertyTransition (this, gesture_tracker, "x", null, (float) target_x).start (with_gesture); + new GesturePropertyTransition (this, gesture_tracker, "y", null, (float) target_y).start (with_gesture); + new GesturePropertyTransition (this, gesture_tracker, "width", null, (float) outer_rect.width).start (with_gesture); + new GesturePropertyTransition (this, gesture_tracker, "height", null, (float) outer_rect.height).start (with_gesture); + new GesturePropertyTransition (this, gesture_tracker, "shadow-opacity", (uint8) 255, (uint8) 0).start (with_gesture); + new GesturePropertyTransition (window_icon, gesture_tracker, "opacity", 255u, 0u).start (with_gesture, () => { + in_slot_animation = false; + place_widgets (outer_rect.width, outer_rect.height, target_scale); + }); + GestureTracker.OnUpdate on_animation_update = (percentage) => { - var x = GestureTracker.animation_value (initial_x, target_x, percentage); - var y = GestureTracker.animation_value (initial_y, target_y, percentage); var width = GestureTracker.animation_value (initial_width, outer_rect.width, percentage); var height = GestureTracker.animation_value (initial_height, outer_rect.height, percentage); var scale = GestureTracker.animation_value (initial_scale, target_scale, percentage); - var opacity = GestureTracker.animation_value (255f, 0f, percentage); - set_size (width, height); - set_position (x, y); - - window_icon.opacity = (uint) opacity; set_window_icon_position (width, height, scale, false); place_widgets ((int)width, (int)height, scale); - - shadow_opacity = (uint8) opacity; }; GestureTracker.OnEnd on_animation_end = (percentage, cancel_action) => { @@ -313,45 +310,16 @@ public class Gala.WindowClone : Clutter.Actor { return; } - var duration = (animate && AnimationsSettings.get_enable_animations ()) ? MultitaskingView.ANIMATION_DURATION : 0; - - save_easing_state (); - set_easing_mode (Clutter.AnimationMode.EASE_OUT_QUAD); - set_easing_duration (duration); - - set_position (target_x, target_y); - set_size (outer_rect.width, outer_rect.height); - - if (should_fade ()) { - opacity = 0; - } - - restore_easing_state (); - - if (animate) { - toggle_shadow (false); - } + toggle_shadow (false); window_icon.save_easing_state (); window_icon.set_easing_mode (Clutter.AnimationMode.EASE_OUT_QUAD); - window_icon.set_easing_duration (duration); - window_icon.opacity = 0; + window_icon.set_easing_duration (AnimationsSettings.get_animation_duration (MultitaskingView.ANIMATION_DURATION)); set_window_icon_position (outer_rect.width, outer_rect.height, target_scale); window_icon.restore_easing_state (); - - var transition = window_icon.get_transition ("opacity"); - if (transition != null) { - transition.completed.connect (() => { - in_slot_animation = false; - place_widgets (outer_rect.width, outer_rect.height, target_scale); - }); - } else { - in_slot_animation = false; - place_widgets (outer_rect.width, outer_rect.height, target_scale); - } }; - if (!animate || gesture_tracker == null || !with_gesture || !AnimationsSettings.get_enable_animations ()) { + if (gesture_tracker == null || !with_gesture || !AnimationsSettings.get_enable_animations ()) { on_animation_end (1, false, 0); } else { gesture_tracker.connect_handlers (null, (owned) on_animation_update, (owned) on_animation_end); @@ -362,36 +330,44 @@ public class Gala.WindowClone : Clutter.Actor { * Animate the window to the given slot */ #if HAS_MUTTER45 - public void take_slot (Mtk.Rectangle rect, bool with_gesture = false, bool is_cancel_animation = false) { + public void take_slot (Mtk.Rectangle rect, bool from_window_position, bool with_gesture = false, bool is_cancel_animation = false) { #else - public void take_slot (Meta.Rectangle rect, bool with_gesture = false, bool is_cancel_animation = false) { + public void take_slot (Meta.Rectangle rect, bool from_window_position, bool with_gesture = false, bool is_cancel_animation = false) { #endif slot = rect; - var initial_x = x; - var initial_y = y; - var initial_width = width; - var initial_height = height; + in_slot_animation = true; active = false; - var scale = display.get_monitor_scale (display.get_monitor_index_for_rect (rect)); - in_slot_animation = true; + var outer_rect = window.get_frame_rect (); + + float initial_width = from_window_position ? outer_rect.width : width; + float initial_height = from_window_position ? outer_rect.height : height; + + var monitor_geom = display.get_monitor_geometry (window.get_monitor ()); + float intial_x = from_window_position ? outer_rect.x - monitor_geom.x : x; + float intial_y = from_window_position ? outer_rect.y - monitor_geom.y : y; + + var scale = display.get_monitor_scale (display.get_monitor_index_for_rect (rect)); place_widgets (rect.width, rect.height, scale); + set_window_icon_position (initial_width, initial_height, scale); + + new GesturePropertyTransition (this, gesture_tracker, "x", intial_x, (float) rect.x).start (with_gesture); + new GesturePropertyTransition (this, gesture_tracker, "y", intial_y, (float) rect.y).start (with_gesture); + new GesturePropertyTransition (this, gesture_tracker, "width", (float) initial_width, (float) rect.width).start (with_gesture); + new GesturePropertyTransition (this, gesture_tracker, "height", (float) initial_height, (float) rect.height).start (with_gesture); + new GesturePropertyTransition (this, gesture_tracker, "shadow-opacity", (uint8) 0, (uint8) 255).start (with_gesture); + new GesturePropertyTransition (window_icon, gesture_tracker, "opacity", 0u, 255u).start (with_gesture, () => { + in_slot_animation = false; + place_widgets (rect.width, rect.height, scale); + }); + GestureTracker.OnUpdate on_animation_update = (percentage) => { - var x = GestureTracker.animation_value (initial_x, rect.x, percentage); - var y = GestureTracker.animation_value (initial_y, rect.y, percentage); var width = GestureTracker.animation_value (initial_width, rect.width, percentage); var height = GestureTracker.animation_value (initial_height, rect.height, percentage); - var opacity = GestureTracker.animation_value (0f, 255f, percentage); - set_size (width, height); - set_position (x, y); - - window_icon.opacity = (uint) opacity; set_window_icon_position (width, height, scale, false); - - shadow_opacity = (uint8) opacity; }; GestureTracker.OnEnd on_animation_end = (percentage, cancel_action) => { @@ -401,34 +377,13 @@ public class Gala.WindowClone : Clutter.Actor { var duration = AnimationsSettings.get_animation_duration (MultitaskingView.ANIMATION_DURATION); - save_easing_state (); - set_easing_mode (Clutter.AnimationMode.EASE_OUT_QUAD); - set_easing_duration (duration); - - set_size (rect.width, rect.height); - set_position (rect.x, rect.y); - opacity = 255; - restore_easing_state (); - window_icon.save_easing_state (); window_icon.set_easing_mode (Clutter.AnimationMode.EASE_OUT_QUAD); window_icon.set_easing_duration (duration); - window_icon.opacity = 255; set_window_icon_position (rect.width, rect.height, scale); window_icon.restore_easing_state (); toggle_shadow (true); - - var transition = window_icon.get_transition ("opacity"); - if (transition != null) { - transition.completed.connect (() => { - in_slot_animation = false; - place_widgets (rect.width, rect.height, scale); - }); - } else { - in_slot_animation = false; - place_widgets (rect.width, rect.height, scale); - } }; if (gesture_tracker == null || !with_gesture || !AnimationsSettings.get_enable_animations ()) { diff --git a/src/Widgets/WindowCloneContainer.vala b/src/Widgets/WindowCloneContainer.vala index f39571a3..d6ce8045 100644 --- a/src/Widgets/WindowCloneContainer.vala +++ b/src/Widgets/WindowCloneContainer.vala @@ -167,7 +167,7 @@ namespace Gala { * Recalculate the tiling positions of the windows and animate them to * the resulting spots. */ - public void reflow (bool with_gesture = false, bool is_cancel_animation = false) { + public void reflow (bool with_gesture = false, bool is_cancel_animation = false, bool opening = false) { if (!opened) { return; } @@ -206,7 +206,7 @@ namespace Gala { foreach (var tilable in window_positions) { unowned var clone = (WindowClone) tilable.id; - clone.take_slot (tilable.rect, with_gesture, is_cancel_animation); + clone.take_slot (tilable.rect, opening, with_gesture, is_cancel_animation); } } @@ -393,15 +393,7 @@ namespace Gala { current_window = null; } - // make sure our windows are where they belong in case they were moved - // while were closed. - if (gesture_tracker == null || !is_cancel_animation) { - foreach (var window in get_children ()) { - ((WindowClone) window).transition_to_original_state (false, with_gesture, is_cancel_animation); - } - } - - reflow (with_gesture, is_cancel_animation); + reflow (with_gesture, is_cancel_animation, true); } /** @@ -416,7 +408,7 @@ namespace Gala { opened = false; foreach (var window in get_children ()) { - ((WindowClone) window).transition_to_original_state (true, with_gesture, is_cancel_animation); + ((WindowClone) window).transition_to_original_state (with_gesture, is_cancel_animation); } } } diff --git a/src/Widgets/WorkspaceClone.vala b/src/Widgets/WorkspaceClone.vala index e72e081e..0c4721ab 100644 --- a/src/Widgets/WorkspaceClone.vala +++ b/src/Widgets/WorkspaceClone.vala @@ -364,46 +364,13 @@ namespace Gala { var scale = (float)(monitor.height - InternalUtils.scale_to_int (TOP_OFFSET + BOTTOM_OFFSET, scale_factor)) / monitor.height; var pivot_y = InternalUtils.scale_to_int (TOP_OFFSET, scale_factor) / (monitor.height - monitor.height * scale); + background.set_pivot_point (0.5f, pivot_y); update_size (monitor); - GestureTracker.OnBegin on_animation_begin = () => { - x = initial_x; - background.set_pivot_point (0.5f, pivot_y); - }; - - GestureTracker.OnUpdate on_animation_update = (percentage) => { - var x = GestureTracker.animation_value (initial_x, target_x, percentage); - set_x (x); - - var update_scale = (double) GestureTracker.animation_value (1.0f, (float)scale, percentage); - background.set_scale (update_scale, update_scale); - }; - - GestureTracker.OnEnd on_animation_end = (percentage, cancel_action) => { - if (cancel_action) { - return; - } - - save_easing_state (); - set_easing_mode (Clutter.AnimationMode.EASE_OUT_QUAD); - set_easing_duration (AnimationsSettings.get_animation_duration (MultitaskingView.ANIMATION_DURATION)); - set_x (target_x); - restore_easing_state (); - - background.save_easing_state (); - background.set_easing_mode (Clutter.AnimationMode.EASE_OUT_QUAD); - background.set_easing_duration (AnimationsSettings.get_animation_duration (MultitaskingView.ANIMATION_DURATION)); - background.set_scale (scale, scale); - background.restore_easing_state (); - }; - - if (!with_gesture || !AnimationsSettings.get_enable_animations ()) { - on_animation_begin (0); - on_animation_end (1, false, 0); - } else { - gesture_tracker.connect_handlers ((owned) on_animation_begin, (owned) on_animation_update, (owned)on_animation_end); - } + new GesturePropertyTransition (this, gesture_tracker, "x", initial_x, target_x).start (with_gesture); + new GesturePropertyTransition (background, gesture_tracker, "scale-x", 1.0d, (double) scale).start (with_gesture); + new GesturePropertyTransition (background, gesture_tracker, "scale-y", 1.0d, (double) scale).start (with_gesture); #if HAS_MUTTER45 Mtk.Rectangle area = { @@ -444,41 +411,9 @@ namespace Gala { var initial_x = is_cancel_animation ? x : multitasking_view_x (); var target_x = multitasking_view_x () + current_x_overlap (); - double initial_scale_x, initial_scale_y; - background.get_scale (out initial_scale_x, out initial_scale_y); - - GestureTracker.OnUpdate on_animation_update = (percentage) => { - var x = GestureTracker.animation_value (initial_x, target_x, percentage); - set_x (x); - - double scale_x = (double) GestureTracker.animation_value ((float) initial_scale_x, 1.0f, percentage); - double scale_y = (double) GestureTracker.animation_value ((float) initial_scale_y, 1.0f, percentage); - background.set_scale (scale_x, scale_y); - }; - - GestureTracker.OnEnd on_animation_end = (percentage, cancel_action) => { - if (cancel_action) { - return; - } - - save_easing_state (); - set_easing_mode (Clutter.AnimationMode.EASE_OUT_QUAD); - set_easing_duration (AnimationsSettings.get_animation_duration (MultitaskingView.ANIMATION_DURATION)); - set_x (target_x); - restore_easing_state (); - - background.save_easing_state (); - background.set_easing_mode (Clutter.AnimationMode.EASE_OUT_QUAD); - background.set_easing_duration (AnimationsSettings.get_animation_duration (MultitaskingView.ANIMATION_DURATION)); - background.set_scale (1, 1); - background.restore_easing_state (); - }; - - if (!with_gesture || !AnimationsSettings.get_enable_animations ()) { - on_animation_end (1, false, 0); - } else { - gesture_tracker.connect_handlers (null, (owned) on_animation_update, (owned) on_animation_end); - } + new GesturePropertyTransition (this, gesture_tracker, "x", initial_x, target_x).start (with_gesture); + new GesturePropertyTransition (background, gesture_tracker, "scale-x", null, 1.0d).start (with_gesture); + new GesturePropertyTransition (background, gesture_tracker, "scale-y", null, 1.0d).start (with_gesture); window_container.close (with_gesture, is_cancel_animation); } diff --git a/src/WindowManager.vala b/src/WindowManager.vala index 3b2d08ef..37d69c63 100644 --- a/src/WindowManager.vala +++ b/src/WindowManager.vala @@ -662,81 +662,16 @@ namespace Gala { var monitor_scale = display.get_monitor_scale (display.get_primary_monitor ()); var nudge_gap = InternalUtils.scale_to_int (NUDGE_GAP, monitor_scale); - float dest = 0; - if (!switch_workspace_with_gesture) { - dest = nudge_gap; - } else { - var workspaces_geometry = InternalUtils.get_workspaces_geometry (display); - dest = workspaces_geometry.width; + + if (direction == RIGHT) { + nudge_gap *= -1; } - if (direction == Meta.MotionDirection.RIGHT) { - dest *= -1; - } - - var animation_mode = Clutter.AnimationMode.EASE_OUT_CUBIC; - - GestureTracker.OnUpdate on_animation_update = (percentage) => { - var x_out = GestureTracker.animation_value (0.0f, dest, percentage, true).clamp (-nudge_gap, nudge_gap); - out_group.x = x_out; - wallpaper.x = x_out; - }; - - GestureTracker.OnEnd on_animation_end = (percentage, cancel_action, duration) => { - out_group.save_easing_state (); - out_group.set_easing_mode (animation_mode); - out_group.set_easing_duration (AnimationDuration.NUDGE / 2); - - wallpaper.save_easing_state (); - wallpaper.set_easing_mode (animation_mode); - wallpaper.set_easing_duration (AnimationDuration.NUDGE / 2); - - out_group.x = 0.0f; - out_group.restore_easing_state (); - - wallpaper.x = 0.0f; - wallpaper.restore_easing_state (); - - unowned var transition = out_group.get_transition ("x"); - transition.completed.connect (() => { - switch_workspace_animation_finished (direction, false, true); - animating_switch_workspace = false; - }); - }; - - if (!switch_workspace_with_gesture) { - double[] keyframes = { 0.5 }; - GLib.Value[] x = { dest }; - - var out_group_nudge = new Clutter.KeyframeTransition ("translation-x") { - duration = AnimationDuration.NUDGE, - remove_on_complete = true, - progress_mode = Clutter.AnimationMode.EASE_IN_QUAD - }; - out_group_nudge.set_from_value (0.0f); - out_group_nudge.set_to_value (0.0f); - out_group_nudge.set_key_frames (keyframes); - out_group_nudge.set_values (x); - out_group.add_transition ("nudge", out_group_nudge); - - var wallpaper_nudge = new Clutter.KeyframeTransition ("translation-x") { - duration = AnimationDuration.NUDGE, - remove_on_complete = true, - progress_mode = Clutter.AnimationMode.EASE_IN_QUAD - }; - wallpaper_nudge.set_from_value (0.0f); - wallpaper_nudge.set_to_value (0.0f); - wallpaper_nudge.set_key_frames (keyframes); - wallpaper_nudge.set_values (x); - wallpaper.add_transition ("nudge", wallpaper_nudge); - - wallpaper_nudge.completed.connect (() => { - switch_workspace_animation_finished (direction, false, true); - animating_switch_workspace = false; - }); - } else { - gesture_tracker.connect_handlers (null, (owned) on_animation_update, (owned) on_animation_end); - } + new GesturePropertyTransition (out_group, gesture_tracker, "x", 0f, 0f, nudge_gap).start (switch_workspace_with_gesture); + new GesturePropertyTransition (wallpaper, gesture_tracker, "x", 0f, 0f, nudge_gap).start (switch_workspace_with_gesture, () => { + switch_workspace_animation_finished (direction, false, true); + animating_switch_workspace = false; + }); } private void update_input_area () { @@ -2261,6 +2196,11 @@ namespace Gala { private void switch_workspace_animation_finished (Meta.MotionDirection animation_direction, bool cancel_action, bool is_nudge_animation = false) { + if (!animating_switch_workspace) { + return; + } + animating_switch_workspace = cancel_action; + if (switch_workspace_window_created_id > 0) { disconnect (switch_workspace_window_created_id); switch_workspace_window_created_id = 0; @@ -2269,7 +2209,6 @@ namespace Gala { if (!is_nudge_animation) { switch_workspace_completed (); } - animating_switch_workspace = cancel_action; if (cancel_action) { var cancel_direction = (animation_direction == Meta.MotionDirection.LEFT) @@ -2352,7 +2291,8 @@ namespace Gala { } public override void kill_switch_workspace () { - end_switch_workspace (); + // We don't care about animation direction, we don't want to cancel it, make it nudge so that it doesn't call switch_workspace_completed () + switch_workspace_animation_finished (LEFT, false, true); } public override void locate_pointer () { diff --git a/src/meson.build b/src/meson.build index d8fa147a..70d9d7b9 100644 --- a/src/meson.build +++ b/src/meson.build @@ -34,6 +34,7 @@ gala_bin_sources = files( 'ColorFilters/FilterManager.vala', 'ColorFilters/MonochromeEffect.vala', 'Gestures/Gesture.vala', + 'Gestures/GesturePropertyTransition.vala', 'Gestures/GestureSettings.vala', 'Gestures/GestureTracker.vala', 'Gestures/ScrollBackend.vala', @@ -41,7 +42,6 @@ gala_bin_sources = files( 'HotCorners/Barrier.vala', 'HotCorners/HotCorner.vala', 'HotCorners/HotCornerManager.vala', - 'ShellClients/DelegateActor.vala', 'ShellClients/HideTracker.vala', 'ShellClients/ManagedClient.vala', 'ShellClients/NotificationsClient.vala', diff --git a/vapi/xfixes-4.0.vapi b/vapi/xfixes-4.0.vapi index 3cbcfc76..acd84e05 100644 --- a/vapi/xfixes-4.0.vapi +++ b/vapi/xfixes-4.0.vapi @@ -6,8 +6,12 @@ namespace X { namespace Fixes { [CCode (cheader_filename = "X11/extensions/Xfixes.h", cname = "XFixesCreateRegion")] public static X.XserverRegion create_region (X.Display display, [CCode (array_length = true)] X.Xrectangle[] rectangles); + [CCode (cheader_filename = "X11/extensions/Xfixes.h", cname = "XFixesCreateRegionFromWindow")] + public static X.XserverRegion create_region_from_window (X.Display display, X.Window window, int shape_kind); [CCode (cheader_filename = "X11/extensions/Xfixes.h", cname = "XFixesDestroyRegion")] public static void destroy_region (X.Display display, X.XserverRegion region); + [CCode (cheader_filename = "X11/extensions/Xfixes.h", cname = "XFixesSetWindowShapeRegion")] + public static void set_window_shape_region (X.Display display, X.Window win, int shape_kind, int x_off, int y_off, XserverRegion region); } [SimpleType] [CCode (cheader_filename = "X11/extensions/Xfixes.h", cname = "XserverRegion", has_type_id = false)]