diff --git a/lib/DragDropAction.vala b/lib/DragDropAction.vala index 565690bf7..f22e95169 100644 --- a/lib/DragDropAction.vala +++ b/lib/DragDropAction.vala @@ -55,6 +55,15 @@ namespace Gala { */ public signal void destination_crossed (Clutter.Actor destination, bool hovered); + /** + * Emitted on the source when we are hovering over a destination. + * + * @param destination The destination actor that we are hovering over + * @param x The x coordinate relative to the destination actor + * @param y The y coordinate relative to the destination actor + */ + public signal void destination_motion (Clutter.Actor destination, float x, float y); + /** * The source has been clicked, but the movement was not larger than * the drag threshold. Useful if the source is also activatable. @@ -341,7 +350,7 @@ namespace Gala { last_y = y; var stage = actor.get_stage (); - var actor = stage.get_actor_at_pos (Clutter.PickMode.REACTIVE, (int) x, (int) y); + var actor = stage.get_actor_at_pos (NONE, (int) x, (int) y); DragDropAction action = null; // if we're allowed to bubble and this actor is not a destination, check its parents if (actor != null && (action = get_drag_drop_action (actor)) == null && allow_bubbling) { @@ -352,8 +361,12 @@ namespace Gala { } // didn't change, no need to do anything - if (actor == hovered) + if (actor == hovered) { + if (hovered != null) { + destination_motion (hovered, x - hovered.x, y - hovered.y); + } return Clutter.EVENT_STOP; + } if (action == null) { // apparently we left ours if we had one before @@ -436,12 +449,12 @@ namespace Gala { } private void finish () { + drag_end (hovered); + // make sure they reset the style or whatever they changed when hovered emit_crossed (hovered, false); cleanup (); - - drag_end (hovered); } private void cleanup () { diff --git a/lib/WindowManager.vala b/lib/WindowManager.vala index 908b9d6ec..6c8304a5e 100644 --- a/lib/WindowManager.vala +++ b/lib/WindowManager.vala @@ -139,13 +139,20 @@ namespace Gala { public abstract Meta.BackgroundGroup background_group { get; protected set; } /** - * Enters the modal mode, which means that all events are directed to the stage instead - * of the windows. This is the only way to receive keyboard events besides shortcut listeners. + * Enters the modal mode, which will block keybindings and gestures. See {@link ModalProxy} for + * how to allow certain gestures and keybindings. + * If {@link grab} is true all events will be redirected to the given {@link Clutter.Actor}. + * If {@link grab} is false other actors and shell windows may still receive events. + * Normal windows will never receive keyboard focus though they will still receive pointer events + * if {@link grab} is false and their {@link Meta.WindowActor} is visible. + * + * @param actor The actor to grab events for + * @param grab Whether to grab all events onto the actor * * @return a {@link ModalProxy} which is needed to end the modal mode again and provides some * some basic control on the behavior of the window manager while it is in modal mode. */ - public abstract ModalProxy push_modal (Clutter.Actor actor); + public abstract ModalProxy push_modal (Clutter.Actor actor, bool grab); /** * May exit the modal mode again, unless another component has called {@link push_modal} diff --git a/plugins/pip/SelectionArea.vala b/plugins/pip/SelectionArea.vala index 6e52ddd54..246defe7e 100644 --- a/plugins/pip/SelectionArea.vala +++ b/plugins/pip/SelectionArea.vala @@ -126,7 +126,7 @@ public class Gala.Plugins.PIP.SelectionArea : CanvasActor { wm.get_display ().set_cursor (Meta.Cursor.CROSSHAIR); grab_key_focus (); - modal_proxy = wm.push_modal (this); + modal_proxy = wm.push_modal (this, true); } private void get_selection_rectangle (out int x, out int y, out int width, out int height) { diff --git a/po/POTFILES b/po/POTFILES index 3d3454a4b..b96956f18 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -82,8 +82,6 @@ src/ShellClients/NotificationsClient.vala src/ShellClients/PanelWindow.vala src/ShellClients/ShellClientsManager.vala src/Widgets/DwellClickTimer.vala -src/Widgets/MultitaskingView/IconGroup.vala -src/Widgets/MultitaskingView/IconGroupContainer.vala src/Widgets/MultitaskingView/MonitorClone.vala src/Widgets/MultitaskingView/MultitaskingView.vala src/Widgets/MultitaskingView/StaticWindowClone.vala @@ -91,9 +89,7 @@ src/Widgets/MultitaskingView/StaticWindowContainer.vala src/Widgets/MultitaskingView/Tooltip.vala src/Widgets/MultitaskingView/WindowClone.vala src/Widgets/MultitaskingView/WindowCloneContainer.vala -src/Widgets/MultitaskingView/WindowIconActor.vala src/Widgets/MultitaskingView/WorkspaceClone.vala -src/Widgets/MultitaskingView/WorkspaceInsertThumb.vala src/Widgets/MultitaskingView/WorkspaceRow.vala src/Widgets/PixelPicker.vala src/Widgets/PointerLocator.vala diff --git a/protocol/pantheon-desktop-shell-v1.xml b/protocol/pantheon-desktop-shell-v1.xml index 63d9d5982..e19030905 100644 --- a/protocol/pantheon-desktop-shell-v1.xml +++ b/protocol/pantheon-desktop-shell-v1.xml @@ -98,6 +98,12 @@ + + + + Tell the shell that the panel would like to be visible in the multitasking view. + + diff --git a/protocol/pantheon-desktop-shell.vapi b/protocol/pantheon-desktop-shell.vapi index 4c57de974..b026f2650 100644 --- a/protocol/pantheon-desktop-shell.vapi +++ b/protocol/pantheon-desktop-shell.vapi @@ -41,6 +41,7 @@ namespace Pantheon.Desktop { public Focus focus; public SetSize set_size; public SetHideMode set_hide_mode; + public RequestVisibleInMultitaskingView request_visible_in_multitasking_view; } [CCode (cheader_filename = "pantheon-desktop-shell-server-protocol.h", cname = "struct io_elementary_pantheon_widget_v1_interface")] @@ -75,6 +76,8 @@ namespace Pantheon.Desktop { [CCode (has_target = false, has_typedef = false)] public delegate void SetHideMode (Wl.Client client, Wl.Resource resource, [CCode (type = "uint32_t")] HideMode hide_mode); [CCode (has_target = false, has_typedef = false)] + public delegate void RequestVisibleInMultitaskingView (Wl.Client client, Wl.Resource resource); + [CCode (has_target = false, has_typedef = false)] public delegate void SetKeepAbove (Wl.Client client, Wl.Resource resource); [CCode (has_target = false, has_typedef = false)] public delegate void MakeCentered (Wl.Client client, Wl.Resource resource); diff --git a/src/DBus.vala b/src/DBus.vala index 9b9492785..f0968224c 100644 --- a/src/DBus.vala +++ b/src/DBus.vala @@ -14,6 +14,19 @@ public class Gala.DBus { public static void init (WindowManagerGala _wm, NotificationsManager notifications_manager, ScreenshotManager screenshot_manager) { wm = _wm; + Bus.own_name ( + SESSION, "io.elementary.gala", NONE, + (connection) => { + try { + connection.register_object ("/io/elementary/gala", WindowDragProvider.get_instance ()); + } catch (Error e) { + warning (e.message); + } + }, + () => {}, + () => critical ("Could not acquire name") + ); + Bus.own_name (BusType.SESSION, "org.pantheon.gala", BusNameOwnerFlags.NONE, (connection) => { if (instance == null) diff --git a/src/DesktopIntegration.vala b/src/DesktopIntegration.vala index 49818dabb..b70b44fb7 100644 --- a/src/DesktopIntegration.vala +++ b/src/DesktopIntegration.vala @@ -143,19 +143,31 @@ public class Gala.DesktopIntegration : GLib.Object { return (owned) returned_windows; } - public void focus_window (uint64 uid) throws GLib.DBusError, GLib.IOError { + private Meta.Window find_window_by_uid (uint64 uid) throws IOError { var apps = Gala.AppSystem.get_default ().get_running_apps (); foreach (unowned var app in apps) { foreach (weak Meta.Window window in app.get_windows ()) { if (window.get_id () == uid) { - if (window.has_focus ()) { - notify_already_focused (window); - } else { - window.get_workspace ().activate_with_focus (window, wm.get_display ().get_current_time ()); - } + return window; } } } + + throw new IOError.NOT_FOUND ("Window with UID " + uid.to_string () + " not found"); + } + + public void focus_window (uint64 uid) throws GLib.DBusError, GLib.IOError { + var window = find_window_by_uid (uid); + if (window.has_focus ()) { + notify_already_focused (window); + } else { + window.get_workspace ().activate_with_focus (window, wm.get_display ().get_current_time ()); + } + } + + public void move_window_to_workspace (uint64 uid, int index) throws DBusError, IOError { + var window = find_window_by_uid (uid); + window.change_workspace_by_index (index, false); } public void activate_workspace (int index) throws GLib.DBusError, GLib.IOError { diff --git a/src/InternalUtils.vala b/src/InternalUtils.vala index acbeb764d..726395656 100644 --- a/src/InternalUtils.vala +++ b/src/InternalUtils.vala @@ -9,6 +9,7 @@ namespace Gala { public enum InputArea { NONE, FULLSCREEN, + MULTITASKING_VIEW, DEFAULT } @@ -32,6 +33,36 @@ namespace Gala { rects = {rect}; break; + case InputArea.MULTITASKING_VIEW: + var shell_client_rect = ShellClientsManager.get_instance ().get_shell_client_rect (); + + int width, height; + display.get_size (out width, out height); + + if (shell_client_rect != null) { + X.Xrectangle left_rect = {0, 0, (ushort) shell_client_rect.x, (ushort) height}; + X.Xrectangle right_rect = { + (short) (shell_client_rect.x + shell_client_rect.width), 0, + (ushort) (width - shell_client_rect.x - shell_client_rect.width), (ushort) height + }; + X.Xrectangle top_rect = { + (short) shell_client_rect.x, 0, + (ushort) shell_client_rect.width, (ushort) shell_client_rect.y + }; + X.Xrectangle bottom_rect = { + (short) shell_client_rect.x, + (short) (shell_client_rect.y + shell_client_rect.height), + (ushort) shell_client_rect.width, + (ushort) (height - shell_client_rect.y - shell_client_rect.height) + }; + rects = {left_rect, right_rect, top_rect, bottom_rect}; + } else { + X.Xrectangle rect = {0, 0, (ushort)width, (ushort)height}; + rects = {rect}; + } + + break; + case InputArea.DEFAULT: // add plugin's requested areas foreach (var rect in PluginManager.get_default ().get_regions ()) { @@ -159,5 +190,40 @@ namespace Gala { var is_in_fullscreen = display.get_monitor_in_fullscreen (primary_monitor); return !Meta.Util.is_wayland_compositor () && is_in_fullscreen; } + + /** + * Returns the most recently used "normal" window (as gotten via {@link get_window_is_normal}) in the given workspace. + * If there is a not normal but more recent window (e.g. a menu/tooltip) any_window will be set to that window otherwise + * it will be set to the same window that is returned. + */ + public static Meta.Window? get_mru_window (Meta.Workspace workspace, out Meta.Window? any_window = null) { + any_window = null; + + var list = workspace.list_windows (); + + if (list.is_empty ()) { + return null; + } + + list.sort ((a, b) => { + return (int) b.get_user_time () - (int) a.get_user_time (); + }); + + foreach (var window in list) { + if (!ShellClientsManager.get_instance ().is_positioned_window (window)) { + if (any_window == null) { + any_window = window; + } + + if (!Utils.get_window_is_normal (window)) { + continue; + } + + return window; + } + } + + return null; + } } } diff --git a/src/PantheonShell.vala b/src/PantheonShell.vala index f0fcf8d08..bc2782ad5 100644 --- a/src/PantheonShell.vala +++ b/src/PantheonShell.vala @@ -39,6 +39,7 @@ namespace Gala { focus_panel, set_size, set_hide_mode, + request_visible_in_multitasking_view, }; wayland_pantheon_widget_interface = { @@ -292,6 +293,23 @@ namespace Gala { ShellClientsManager.get_instance ().set_hide_mode (window, hide_mode); } + internal static void request_visible_in_multitasking_view (Wl.Client client, Wl.Resource resource) { + unowned PanelSurface? panel_surface = resource.get_user_data (); + if (panel_surface.wayland_surface == null) { + warning ("Window tried to set visible in multitasking view but wayland surface is null."); + return; + } + + Meta.Window? window; + panel_surface.wayland_surface.get ("window", out window, null); + if (window == null) { + warning ("Window tried to set visible in multitasking view but wayland surface had no associated window."); + return; + } + + ShellClientsManager.get_instance ().request_visible_in_multitasking_view (window); + } + internal static void set_keep_above (Wl.Client client, Wl.Resource resource) { unowned ExtendedBehaviorSurface? eb_surface = resource.get_user_data (); if (eb_surface.wayland_surface == null) { diff --git a/src/ShellClients/HideTracker.vala b/src/ShellClients/HideTracker.vala index dcfee7448..233deaaa7 100644 --- a/src/ShellClients/HideTracker.vala +++ b/src/ShellClients/HideTracker.vala @@ -153,7 +153,12 @@ public class Gala.HideTracker : Object { focus_maximized_overlap = false; fullscreen_overlap = display.get_monitor_in_fullscreen (panel.window.get_monitor ()); - foreach (var window in display.get_workspace_manager ().get_active_workspace ().list_windows ()) { + unowned var active_workspace = display.get_workspace_manager ().get_active_workspace (); + + Meta.Window? normal_mru_window, any_mru_window; + normal_mru_window = InternalUtils.get_mru_window (active_workspace, out any_mru_window); + + foreach (var window in active_workspace.list_windows ()) { if (window == panel.window) { continue; } @@ -173,7 +178,7 @@ public class Gala.HideTracker : Object { overlap = true; - if (window != display.focus_window) { + if (window != normal_mru_window && window != any_mru_window) { continue; } diff --git a/src/ShellClients/PanelWindow.vala b/src/ShellClients/PanelWindow.vala index a8b95dff5..c33373c63 100644 --- a/src/ShellClients/PanelWindow.vala +++ b/src/ShellClients/PanelWindow.vala @@ -61,6 +61,11 @@ public class Gala.PanelWindow : ShellWindow, RootTarget { hide_tracker.show.connect (show); } + public void request_visible_in_multitasking_view () { + visible_in_multitasking_view = true; + actor.add_action (new DragDropAction (DESTINATION, "multitaskingview-window")); + } + private void hide () { gesture_controller.goto (1); } diff --git a/src/ShellClients/ShellClientsManager.vala b/src/ShellClients/ShellClientsManager.vala index ff39deb8a..61daa1a1f 100644 --- a/src/ShellClients/ShellClientsManager.vala +++ b/src/ShellClients/ShellClientsManager.vala @@ -185,6 +185,15 @@ public class Gala.ShellClientsManager : Object, GestureTarget { panel_windows[window].hide_mode = hide_mode; } + public void request_visible_in_multitasking_view (Meta.Window window) { + if (!(window in panel_windows)) { + warning ("Set anchor for window before visible in mutltiasking view."); + return; + } + + panel_windows[window].request_visible_in_multitasking_view (); + } + public void make_centered (Meta.Window window) requires (!is_itself_positioned (window)) { positioned_windows[window] = new ShellWindow (window, CENTER); @@ -290,6 +299,10 @@ public class Gala.ShellClientsManager : Object, GestureTarget { } break; + case "visible-in-multitasking-view": + request_visible_in_multitasking_view (window); + break; + case "centered": make_centered (window); break; @@ -309,4 +322,13 @@ public class Gala.ShellClientsManager : Object, GestureTarget { requires (window in panel_windows) { panel_windows[window].restore_previous_x11_region = true; } + + public Mtk.Rectangle? get_shell_client_rect () { + foreach (var client in panel_windows.get_values ()) { + if (client.visible_in_multitasking_view) { + return client.get_custom_window_rect (); + } + } + return null; + } } diff --git a/src/ShellClients/ShellWindow.vala b/src/ShellClients/ShellWindow.vala index 31aba66f5..2e2c1d725 100644 --- a/src/ShellClients/ShellWindow.vala +++ b/src/ShellClients/ShellWindow.vala @@ -8,6 +8,7 @@ public class Gala.ShellWindow : PositionedWindow, GestureTarget { public Clutter.Actor? actor { get { return window_actor; } } public bool restore_previous_x11_region { private get; set; default = false; } + public bool visible_in_multitasking_view { get; set; default = false; } private Meta.WindowActor window_actor; private double custom_progress = 0; @@ -44,9 +45,16 @@ public class Gala.ShellWindow : PositionedWindow, GestureTarget { ); } + private double get_hidden_progress () { + if (visible_in_multitasking_view) { + return double.min (custom_progress, 1 - multitasking_view_progress); + } else { + return double.max (custom_progress, multitasking_view_progress); + } + } + private void update_property () { - var hidden_progress = double.max (custom_progress, multitasking_view_progress); - property_target.propagate (UPDATE, DOCK, hidden_progress); + property_target.propagate (UPDATE, DOCK, get_hidden_progress ()); } public override void propagate (UpdateType update_type, GestureAction action, double progress) { @@ -88,7 +96,7 @@ public class Gala.ShellWindow : PositionedWindow, GestureTarget { } private void update_visibility () { - var visible = double.max (multitasking_view_progress, custom_progress) < 0.1; + var visible = get_hidden_progress () < 0.1; var animating = animations_ongoing > 0; window_actor.visible = animating || visible; diff --git a/src/Widgets/MultitaskingView/IconGroup.vala b/src/Widgets/MultitaskingView/IconGroup.vala deleted file mode 100644 index cd978e5f4..000000000 --- a/src/Widgets/MultitaskingView/IconGroup.vala +++ /dev/null @@ -1,459 +0,0 @@ -/* - * SPDX-License-Identifier: GPL-3.0-or-later - * SPDX-FileCopyrightText: 2014 Tom Beckmann - * 2025 elementary, Inc. (https://elementary.io) - */ - -/** - * Container for WindowIconActors which takes care of the scaling and positioning. - * It also decides whether to draw the container shape, a plus sign or an ellipsis. - * Lastly it also includes the drawing code for the active highlight. - */ -public class Gala.IconGroup : CanvasActor { - public const int SIZE = 64; - - private const int PLUS_SIZE = 6; - private const int PLUS_WIDTH = 26; - private const int BACKDROP_ABSOLUTE_OPACITY = 40; - - /** - * The group has been clicked. The MultitaskingView should consider activating - * its workspace. - */ - public signal void selected (); - - private float _backdrop_opacity = 0.0f; - /** - * The opacity of the backdrop/highlight. - */ - public float backdrop_opacity { - get { - return _backdrop_opacity; - } - set { - _backdrop_opacity = value; - queue_redraw (); - } - } - - private DragDropAction drag_action; - - public Meta.Display display { get; construct; } - public Meta.Workspace workspace { get; construct; } - private float _scale_factor = 1.0f; - public float scale_factor { - get { return _scale_factor; } - set { - if (value != _scale_factor) { - _scale_factor = value; - resize (); - } - } - } - - private Clutter.Actor? prev_parent = null; - private Clutter.Actor icon_container; - - public IconGroup (Meta.Display display, Meta.Workspace workspace, float scale) { - Object (display: display, workspace: workspace, scale_factor: scale); - } - - construct { - reactive = true; - - drag_action = new DragDropAction (DragDropActionType.SOURCE | DragDropActionType.DESTINATION, "multitaskingview-window"); - drag_action.actor_clicked.connect (() => selected ()); - drag_action.drag_begin.connect (drag_begin); - drag_action.drag_end.connect (drag_end); - drag_action.drag_canceled.connect (drag_canceled); - drag_action.notify["dragging"].connect (redraw); - add_action (drag_action); - - icon_container = new Clutter.Actor (); - icon_container.width = width; - icon_container.height = height; - - add_child (icon_container); - - resize (); -#if HAS_MUTTER46 - icon_container.child_removed.connect_after (redraw); -#else - icon_container.actor_removed.connect_after (redraw); -#endif - } - - ~IconGroup () { -#if HAS_MUTTER46 - icon_container.child_removed.disconnect (redraw); -#else - icon_container.actor_removed.disconnect (redraw); -#endif - } - - private void resize () { - var size = Utils.scale_to_int (SIZE, scale_factor); - - width = size; - height = size; - } - - /** - * Override the paint handler to draw our backdrop if necessary - */ - public override void paint (Clutter.PaintContext context) { - if (backdrop_opacity == 0.0 || drag_action.dragging) { - base.paint (context); - return; - } - - var width = Utils.scale_to_int (100, scale_factor); - var x = (Utils.scale_to_int (SIZE, scale_factor) - width) / 2; - var y = -10; - var height = Utils.scale_to_int (WorkspaceClone.BOTTOM_OFFSET, scale_factor); - var backdrop_opacity_int = (uint8) (BACKDROP_ABSOLUTE_OPACITY * backdrop_opacity); - -#if HAS_MUTTER47 -//FIXME: TODO! -#else - Cogl.VertexP2T2C4 vertices[4]; - vertices[0] = { x, y + height, 0, 1, backdrop_opacity_int, backdrop_opacity_int, backdrop_opacity_int, backdrop_opacity_int }; - vertices[1] = { x, y, 0, 0, 0, 0, 0, 0 }; - vertices[2] = { x + width, y + height, 1, 1, backdrop_opacity_int, backdrop_opacity_int, backdrop_opacity_int, backdrop_opacity_int }; - vertices[3] = { x + width, y, 1, 0, 0, 0, 0, 0 }; - - var primitive = new Cogl.Primitive.p2t2c4 (context.get_framebuffer ().get_context (), Cogl.VerticesMode.TRIANGLE_STRIP, vertices); - var pipeline = new Cogl.Pipeline (context.get_framebuffer ().get_context ()); - primitive.draw (context.get_framebuffer (), pipeline); -#endif - base.paint (context); - } - - /** - * Remove all currently added WindowIconActors - */ - public void clear () { - icon_container.destroy_all_children (); - } - - /** - * Creates a WindowIconActor for the given window and adds it to the group - * - * @param window The MetaWindow for which to create the WindowIconActor - * @param no_redraw If you add multiple windows at once you may want to consider - * settings this to true and when done calling redraw() manually - * @param temporary Mark the WindowIconActor as temporary. Used for windows dragged over - * the group. - */ - public void add_window (Meta.Window window, bool no_redraw = false, bool temporary = false) { - var new_window = new WindowIconActor (window); - new_window.set_position (32, 32); - new_window.temporary = temporary; - - icon_container.add_child (new_window); - - if (!no_redraw) - redraw (); - } - - /** - * Remove the WindowIconActor for a MetaWindow from the group - * - * @param animate Whether to fade the icon out before removing it - */ - public void remove_window (Meta.Window window, bool animate = true) { - foreach (unowned var child in icon_container.get_children ()) { - unowned var icon = (WindowIconActor) child; - if (icon.window == window) { - if (animate) { - icon.save_easing_state (); - icon.set_easing_mode (Clutter.AnimationMode.LINEAR); - icon.set_easing_duration (Utils.get_animation_duration (200)); - icon.opacity = 0; - icon.restore_easing_state (); - - var transition = icon.get_transition ("opacity"); - if (transition != null) { - transition.completed.connect (() => { - icon.destroy (); - }); - } else { - icon.destroy (); - } - - } else { - icon.destroy (); - } - - // don't break here! If people spam hover events and we animate - // removal, we can actually multiple instances of the same window icon - } - } - } - - /** - * Sets a hovered actor for the drag action. - */ - public void set_hovered_actor (Clutter.Actor actor) { - drag_action.hovered = actor; - } - - /** - * Trigger a redraw - */ - public void redraw () { - content.invalidate (); - } - - /** - * Draw the background or plus sign and do layouting. We won't lose performance here - * by relayouting in the same function, as it's only ever called when we invalidate it. - */ - protected override void draw (Cairo.Context cr, int cr_width, int cr_height) { - clear_effects (); - cr.set_operator (Cairo.Operator.CLEAR); - cr.paint (); - cr.set_operator (Cairo.Operator.OVER); - - var n_windows = icon_container.get_n_children (); - - // single icon => big icon - if (n_windows == 1) { - var icon = (WindowIconActor) icon_container.get_child_at_index (0); - icon.place (0, 0, 64, scale_factor); - - return; - } - - // more than one => we need a folder - Drawing.Utilities.cairo_rounded_rectangle ( - cr, - 0, - 0, - width, - height, - Utils.scale_to_int (5, scale_factor) - ); - - var shadow_effect = new ShadowEffect ("", scale_factor) { - border_radius = 6 - }; - - var style_manager = Drawing.StyleManager.get_instance (); - - if (style_manager.prefers_color_scheme == DARK) { - const double BG_COLOR = 35.0 / 255.0; - if (drag_action.dragging) { - cr.set_source_rgba (BG_COLOR, BG_COLOR, BG_COLOR, 0.8); - } else { - cr.set_source_rgba (BG_COLOR , BG_COLOR , BG_COLOR , 0.5); - shadow_effect.shadow_opacity = 200; - } - } else { - if (drag_action.dragging) { - cr.set_source_rgba (255, 255, 255, 0.8); - } else { - cr.set_source_rgba (255, 255, 255, 0.3); - shadow_effect.shadow_opacity = 100; - } - } - - if (drag_action.dragging) { - shadow_effect.css_class = "workspace-switcher-dnd"; - } else { - shadow_effect.css_class = "workspace-switcher"; - } - - add_effect (shadow_effect); - cr.fill_preserve (); - - // it's not safe to to call meta_workspace_index() here, we may be still animating something - // while the workspace is already gone, which would result in a crash. - unowned Meta.WorkspaceManager manager = workspace.get_display ().get_workspace_manager (); - int workspace_index = 0; - for (int i = 0; i < manager.get_n_workspaces (); i++) { - if (manager.get_workspace_by_index (i) == workspace) { - workspace_index = i; - break; - } - } - - var scaled_size = Utils.scale_to_int (SIZE, scale_factor); - - if (n_windows < 1) { - if (workspace_index != manager.get_n_workspaces () - 1) { - return; - } - - var buffer = new Drawing.BufferSurface (scaled_size, scaled_size); - var offset = scaled_size / 2 - Utils.scale_to_int (PLUS_WIDTH, scale_factor) / 2; - - buffer.context.rectangle ( - Utils.scale_to_int (PLUS_WIDTH / 2, scale_factor) - Utils.scale_to_int (PLUS_SIZE / 2, scale_factor) + offset, - offset, - Utils.scale_to_int (PLUS_SIZE, scale_factor), - Utils.scale_to_int (PLUS_WIDTH, scale_factor) - ); - - buffer.context.rectangle (offset, - Utils.scale_to_int (PLUS_WIDTH / 2, scale_factor) - Utils.scale_to_int (PLUS_SIZE / 2, scale_factor) + offset, - Utils.scale_to_int (PLUS_WIDTH, scale_factor), - Utils.scale_to_int (PLUS_SIZE, scale_factor) - ); - - if (style_manager.prefers_color_scheme == DARK) { - buffer.context.move_to (0, 1 * scale_factor); - buffer.context.set_source_rgb (0, 0, 0); - buffer.context.fill_preserve (); - buffer.exponential_blur (2); - - buffer.context.move_to (0, 0); - buffer.context.set_source_rgba (1, 1, 1, 0.95); - } else { - buffer.context.move_to (0, 1 * scale_factor); - buffer.context.set_source_rgba (1, 1, 1, 0.4); - buffer.context.fill_preserve (); - buffer.exponential_blur (1); - - buffer.context.move_to (0, 0); - buffer.context.set_source_rgba (0, 0, 0, 0.7); - } - - buffer.context.fill (); - - cr.set_source_surface (buffer.surface, 0, 0); - cr.paint (); - - return; - } - - int size; - if (n_windows < 5) - size = 24; - else - size = 16; - - var n_tiled_windows = uint.min (n_windows, 9); - var columns = (int) Math.ceil (Math.sqrt (n_tiled_windows)); - var rows = (int) Math.ceil (n_tiled_windows / (double) columns); - - int spacing = Utils.scale_to_int (6, scale_factor); - - var width = columns * Utils.scale_to_int (size, scale_factor) + (columns - 1) * spacing; - var height = rows * Utils.scale_to_int (size, scale_factor) + (rows - 1) * spacing; - var x_offset = scaled_size / 2 - width / 2; - var y_offset = scaled_size / 2 - height / 2; - - var show_ellipsis = false; - var n_shown_windows = n_windows; - // make place for an ellipsis - if (n_shown_windows > 9) { - n_shown_windows = 8; - show_ellipsis = true; - } - - var x = x_offset; - var y = y_offset; - for (var i = 0; i < n_windows; i++) { - var window = (WindowIconActor) icon_container.get_child_at_index (i); - - // draw an ellipsis at the 9th position if we need one - if (show_ellipsis && i == 8) { - int top_offset = Utils.scale_to_int (10, scale_factor); - int left_offset = Utils.scale_to_int (2, scale_factor); - int radius = Utils.scale_to_int (2, scale_factor); - int dot_spacing = Utils.scale_to_int (3, scale_factor); - cr.arc (left_offset + x, y + top_offset, radius, 0, 2 * Math.PI); - cr.arc (left_offset + x + radius + dot_spacing, y + top_offset, radius, 0, 2 * Math.PI); - cr.arc (left_offset + x + radius * 2 + dot_spacing * 2, y + top_offset, radius, 0, 2 * Math.PI); - - cr.set_source_rgb (0.3, 0.3, 0.3); - cr.fill (); - } - - if (i >= n_shown_windows) { - window.visible = false; - continue; - } - - window.place (x, y, size, scale_factor); - - x += Utils.scale_to_int (size, scale_factor) + spacing; - if (x + Utils.scale_to_int (size, scale_factor) >= scaled_size) { - x = x_offset; - y += Utils.scale_to_int (size, scale_factor) + spacing; - } - } - } - - private Clutter.Actor? drag_begin (float click_x, float click_y) { - unowned Meta.WorkspaceManager manager = workspace.get_display ().get_workspace_manager (); - if (icon_container.get_n_children () < 1 && - workspace.index () == manager.get_n_workspaces () - 1) { - return null; - } - - float abs_x, abs_y; - float prev_parent_x, prev_parent_y; - - prev_parent = get_parent (); - prev_parent.get_transformed_position (out prev_parent_x, out prev_parent_y); - - var stage = get_stage (); - var container = prev_parent as IconGroupContainer; - if (container != null) { - container.remove_group_in_place (this); - container.reset_thumbs (0); - } else { - prev_parent.remove_child (this); - } - - stage.add_child (this); - - get_transformed_position (out abs_x, out abs_y); - set_position (abs_x + prev_parent_x, abs_y + prev_parent_y); - - // disable reactivity so that workspace thumbs can get events - reactive = false; - -#if HAS_MUTTER48 - display.set_cursor (Meta.Cursor.MOVE); -#else - display.set_cursor (Meta.Cursor.DND_IN_DRAG); -#endif - - return this; - } - - private void drag_end (Clutter.Actor destination) { - if (destination is WorkspaceInsertThumb) { - get_parent ().remove_child (this); - - unowned WorkspaceInsertThumb inserter = (WorkspaceInsertThumb) destination; - unowned Meta.WorkspaceManager manager = workspace.get_display ().get_workspace_manager (); - manager.reorder_workspace (workspace, inserter.workspace_index); - - restore_group (); - } else { - drag_canceled (); - } - - display.set_cursor (Meta.Cursor.DEFAULT); - } - - private void drag_canceled () { - get_parent ().remove_child (this); - restore_group (); - - display.set_cursor (Meta.Cursor.DEFAULT); - } - - private void restore_group () { - var container = prev_parent as IconGroupContainer; - if (container != null) { - container.add_group (this); - container.request_reposition (false); - container.reset_thumbs (WorkspaceInsertThumb.EXPAND_DELAY); - } - } -} diff --git a/src/Widgets/MultitaskingView/IconGroupContainer.vala b/src/Widgets/MultitaskingView/IconGroupContainer.vala deleted file mode 100644 index 58245a0a0..000000000 --- a/src/Widgets/MultitaskingView/IconGroupContainer.vala +++ /dev/null @@ -1,163 +0,0 @@ -/* - * SPDX-License-Identifier: GPL-3.0-or-later - * SPDX-FileCopyrightText: 2014 Tom Beckmann - * 2025 elementary, Inc. - */ - -/** - * This class contains the icon groups at the bottom and will take - * care of displaying actors for inserting windows between the groups - * once implemented - */ -public class Gala.IconGroupContainer : Clutter.Actor { - public const int SPACING = 48; - public const int GROUP_WIDTH = 64; - - public signal void request_reposition (bool animate); - - private float _scale_factor = 1.0f; - public float scale_factor { - get { - return _scale_factor; - } - set { - if (value != _scale_factor) { - _scale_factor = value; - reallocate (); - } - } - } - - public IconGroupContainer (float scale) { - Object (scale_factor: scale); - - layout_manager = new Clutter.BoxLayout (); - } - - private void reallocate () { - foreach (var child in get_children ()) { - unowned WorkspaceInsertThumb thumb = child as WorkspaceInsertThumb; - if (thumb != null) { - thumb.scale_factor = scale_factor; - } - } - } - - public void add_group (IconGroup group) { - var index = group.workspace.index (); - - insert_child_at_index (group, index * 2); - - var thumb = new WorkspaceInsertThumb (index, scale_factor); - thumb.notify["expanded"].connect_after (expanded_changed); - insert_child_at_index (thumb, index * 2); - - update_inserter_indices (); - } - - public void remove_group (IconGroup group) { - var thumb = (WorkspaceInsertThumb) group.get_previous_sibling (); - thumb.notify["expanded"].disconnect (expanded_changed); - remove_child (thumb); - - remove_child (group); - - update_inserter_indices (); - } - - /** - * Removes an icon group "in place". - * When initially dragging an icon group we remove - * it and it's previous WorkspaceInsertThumb. This would make - * the container immediately reallocate and fill the empty space - * with right-most IconGroups. - * - * We don't want that until the IconGroup - * leaves the expanded WorkspaceInsertThumb. - */ - public void remove_group_in_place (IconGroup group) { - var deleted_thumb = (WorkspaceInsertThumb) group.get_previous_sibling (); - var deleted_placeholder_thumb = (WorkspaceInsertThumb) group.get_next_sibling (); - - remove_group (group); - - /* - * We will account for that empty space - * by manually expanding the next WorkspaceInsertThumb with the - * width we deleted. Because the IconGroup is still hovering over - * the expanded thumb, we will also update the drag & drop action - * of IconGroup on that. - */ - if (deleted_placeholder_thumb != null) { - float deleted_width = deleted_thumb.get_width () + group.get_width (); - deleted_placeholder_thumb.expanded = true; - deleted_placeholder_thumb.width += deleted_width; - group.set_hovered_actor (deleted_placeholder_thumb); - } - } - - public void reset_thumbs (int delay) { - foreach (var child in get_children ()) { - unowned WorkspaceInsertThumb thumb = child as WorkspaceInsertThumb; - if (thumb != null) { - thumb.delay = delay; - thumb.destroy_all_children (); - } - } - } - - private void expanded_changed (ParamSpec param) { - request_reposition (true); - } - - /** - * Calculates the width that will be occupied taking currently running animations - * end states into account - */ - public float calculate_total_width () { - var spacing = Utils.scale_to_int (SPACING, scale_factor); - var group_width = Utils.scale_to_int (GROUP_WIDTH, scale_factor); - - var width = 0.0f; - foreach (var child in get_children ()) { - if (child is WorkspaceInsertThumb) { - if (((WorkspaceInsertThumb) child).expanded) - width += group_width + spacing * 2; - else - width += spacing; - } else - width += group_width; - } - - width += spacing; - - return width; - } - - public void force_reposition () { - var children = get_children (); - - foreach (var child in children) { - if (child is IconGroup) { - remove_group ((IconGroup) child); - } - } - - foreach (var child in children) { - if (child is IconGroup) { - add_group ((IconGroup) child); - } - } - } - - private void update_inserter_indices () { - var current_index = 0; - - foreach (var child in get_children ()) { - unowned WorkspaceInsertThumb thumb = child as WorkspaceInsertThumb; - if (thumb != null) { - thumb.workspace_index = current_index++; - } - } - } -} diff --git a/src/Widgets/MultitaskingView/MultitaskingView.vala b/src/Widgets/MultitaskingView/MultitaskingView.vala index c2d7d2c2c..80fc679f8 100644 --- a/src/Widgets/MultitaskingView/MultitaskingView.vala +++ b/src/Widgets/MultitaskingView/MultitaskingView.vala @@ -34,7 +34,6 @@ public class Gala.MultitaskingView : ActorTarget, RootTarget, ActivatableCompone private List window_containers_monitors; - private IconGroupContainer icon_groups; private ActorTarget workspaces; private Clutter.Actor primary_monitor_container; private Clutter.BrightnessContrastEffect brightness_effect; @@ -73,15 +72,12 @@ public class Gala.MultitaskingView : ActorTarget, RootTarget, ActivatableCompone workspaces_gesture_controller.enable_scroll (this, HORIZONTAL); add_gesture_controller (workspaces_gesture_controller); - icon_groups = new IconGroupContainer (display.get_monitor_scale (display.get_primary_monitor ())); - update_blurred_bg (); // Create a child container that will be sized to fit the primary monitor, to contain the "main" // multitasking view UI. The Clutter.Actor of this class has to be allowed to grow to the size of the // stage as it contains MonitorClones for each monitor. primary_monitor_container = new ActorTarget (); - primary_monitor_container.add_child (icon_groups); primary_monitor_container.add_child (workspaces); add_child (primary_monitor_container); @@ -144,7 +140,6 @@ public class Gala.MultitaskingView : ActorTarget, RootTarget, ActivatableCompone var primary_geometry = display.get_monitor_geometry (primary); var scale = display.get_monitor_scale (primary); - icon_groups.scale_factor = scale; primary_monitor_container.set_position (primary_geometry.x, primary_geometry.y); primary_monitor_container.set_size (primary_geometry.width, primary_geometry.height); @@ -182,7 +177,6 @@ public class Gala.MultitaskingView : ActorTarget, RootTarget, ActivatableCompone private void update_workspaces () { foreach (unowned var child in workspaces.get_children ()) { unowned var workspace_clone = (WorkspaceClone) child; - icon_groups.remove_group (workspace_clone.icon_group); workspace_clone.destroy (); } @@ -252,18 +246,9 @@ public class Gala.MultitaskingView : ActorTarget, RootTarget, ActivatableCompone show (); grab_key_focus (); - modal_proxy = wm.push_modal (get_stage ()); + modal_proxy = wm.push_modal (get_stage (), false); modal_proxy.set_keybinding_filter (keybinding_filter); modal_proxy.allow_actions ({ MULTITASKING_VIEW, SWITCH_WORKSPACE, ZOOM }); - - var scale = display.get_monitor_scale (display.get_primary_monitor ()); - icon_groups.force_reposition (); - icon_groups.y = primary_monitor_container.height - Utils.scale_to_int (WorkspaceClone.BOTTOM_OFFSET - 20, scale); - reposition_icon_groups (false); - - if (action != MULTITASKING_VIEW) { - icon_groups.hide (); - } } else if (action == MULTITASKING_VIEW) { DragDropAction.cancel_all_by_id ("multitaskingview-window"); } @@ -271,11 +256,13 @@ public class Gala.MultitaskingView : ActorTarget, RootTarget, ActivatableCompone if (action == SWITCH_WORKSPACE) { WorkspaceManager.get_default ().freeze_remove (); + var mru_window = InternalUtils.get_mru_window (display.get_workspace_manager ().get_active_workspace ()); + if (workspaces_gesture_controller.action_info != null && (bool) workspaces_gesture_controller.action_info - && display.focus_window != null + && mru_window != null ) { - var moving = display.focus_window; + var moving = mru_window; StaticWindowContainer.get_instance (display).notify_window_moving (moving); @@ -318,7 +305,6 @@ public class Gala.MultitaskingView : ActorTarget, RootTarget, ActivatableCompone wm.background_group.show (); wm.window_group.show (); wm.top_window_group.show (); - icon_groups.show (); hide (); wm.pop_modal (modal_proxy); @@ -331,41 +317,14 @@ public class Gala.MultitaskingView : ActorTarget, RootTarget, ActivatableCompone } } - private void reposition_icon_groups (bool animate) { - unowned Meta.WorkspaceManager manager = display.get_workspace_manager (); - var active_index = manager.get_active_workspace ().index (); - - if (animate) { - icon_groups.save_easing_state (); - icon_groups.set_easing_mode (Clutter.AnimationMode.EASE_OUT_QUAD); - icon_groups.set_easing_duration (200); - } - - var scale = display.get_monitor_scale (display.get_primary_monitor ()); - // make sure the active workspace's icongroup is always visible - var icon_groups_width = icon_groups.calculate_total_width (); - if (icon_groups_width > primary_monitor_container.width) { - icon_groups.x = (-active_index * Utils.scale_to_int (IconGroupContainer.SPACING + IconGroup.SIZE, scale) + primary_monitor_container.width / 2) - .clamp (primary_monitor_container.width - icon_groups_width - Utils.scale_to_int (64, scale), Utils.scale_to_int (64, scale)); - } else - icon_groups.x = primary_monitor_container.width / 2 - icon_groups_width / 2; - - if (animate) { - icon_groups.restore_easing_state (); - } - } - private void add_workspace (int num) { unowned var manager = display.get_workspace_manager (); var scale = display.get_monitor_scale (display.get_primary_monitor ()); var workspace = new WorkspaceClone (wm, manager.get_workspace_by_index (num), scale); workspaces.insert_child_at_index (workspace, num); - icon_groups.add_group (workspace.icon_group); workspace.window_selected.connect (window_selected); - - reposition_icon_groups (false); } private void remove_workspace (int num) { @@ -391,15 +350,8 @@ public class Gala.MultitaskingView : ActorTarget, RootTarget, ActivatableCompone } workspace.window_selected.disconnect (window_selected); - - if (icon_groups.contains (workspace.icon_group)) { - icon_groups.remove_group (workspace.icon_group); - } - workspace.destroy (); - reposition_icon_groups (opened); - workspaces_gesture_controller.progress = -manager.get_active_workspace_index (); } @@ -408,8 +360,6 @@ public class Gala.MultitaskingView : ActorTarget, RootTarget, ActivatableCompone unowned var manager = display.get_workspace_manager (); workspaces_gesture_controller.progress = -manager.get_active_workspace_index (); } - - reposition_icon_groups (false); } private void on_workspace_switched (int from, int to) { diff --git a/src/Widgets/MultitaskingView/WindowClone.vala b/src/Widgets/MultitaskingView/WindowClone.vala index 32d5b9229..c65a8e56a 100644 --- a/src/Widgets/MultitaskingView/WindowClone.vala +++ b/src/Widgets/MultitaskingView/WindowClone.vala @@ -117,7 +117,8 @@ public class Gala.WindowClone : ActorTarget, RootTarget { } else { drag_action = new DragDropAction (DragDropActionType.SOURCE, "multitaskingview-window"); drag_action.drag_begin.connect (drag_begin); - drag_action.destination_crossed.connect (drag_destination_crossed); + drag_action.destination_crossed.connect (destination_crossed); + drag_action.destination_motion.connect (destination_motion); drag_action.drag_end.connect (drag_end); drag_action.drag_canceled.connect (drag_canceled); drag_action.actor_clicked.connect (actor_clicked); @@ -512,59 +513,20 @@ public class Gala.WindowClone : ActorTarget, RootTarget { return this; } - /** - * When we cross an IconGroup, we animate to an even smaller size and slightly - * less opacity and add ourselves as temporary window to the group. When left, - * we reverse those steps. - */ - private void drag_destination_crossed (Clutter.Actor destination, bool hovered) { - var icon_group = destination as IconGroup; - var insert_thumb = destination as WorkspaceInsertThumb; - - // if we have don't dynamic workspace, we don't allow inserting - if (icon_group == null && insert_thumb == null - || (insert_thumb != null && !Meta.Prefs.get_dynamic_workspaces ())) { - return; - } - - // for an icon group, we only do animations if there is an actual movement possible - if (icon_group != null - && icon_group.workspace == window.get_workspace () - && window.is_on_primary_monitor ()) { - return; - } - - var scale = hovered ? 0.4 : 1.0; - var opacity = hovered ? 0 : 255; - uint duration = hovered && insert_thumb != null ? insert_thumb.delay : 100; - duration = Utils.get_animation_duration (duration); - - window_icon.save_easing_state (); - - window_icon.set_easing_mode (Clutter.AnimationMode.LINEAR); - window_icon.set_easing_duration (duration); - window_icon.set_scale (scale, scale); - window_icon.set_opacity (opacity); - - window_icon.restore_easing_state (); - - if (insert_thumb != null) { - insert_thumb.set_window_thumb (window); + private void destination_crossed (Clutter.Actor destination, bool hovered) { + if (!(destination is Meta.WindowActor)) { + return; } - if (icon_group != null) { - if (hovered) { - icon_group.add_window (window, false, true); - } else { - icon_group.remove_window (window, false); - } + if (hovered) { + WindowDragProvider.get_instance ().notify_enter (window.get_id ()); + } else { + WindowDragProvider.get_instance ().notify_leave (); } + } -#if HAS_MUTTER48 - wm.get_display ().set_cursor (hovered ? Meta.Cursor.MOVE: Meta.Cursor.NO_DROP); -#else - wm.get_display ().set_cursor (hovered ? Meta.Cursor.DND_MOVE: Meta.Cursor.DND_IN_DRAG); -#endif + private void destination_motion (Clutter.Actor destination, float x, float y) { + WindowDragProvider.get_instance ().notify_motion (x, y); } /** @@ -582,31 +544,8 @@ public class Gala.WindowClone : ActorTarget, RootTarget { display.set_cursor (Meta.Cursor.DEFAULT); - if (destination is IconGroup) { - workspace = ((IconGroup) destination).workspace; - } else if (destination is FramedBackground) { + if (destination is FramedBackground) { workspace = ((WorkspaceClone) destination.get_parent ()).workspace; - } else if (destination is WorkspaceInsertThumb) { - unowned WorkspaceInsertThumb inserter = (WorkspaceInsertThumb) destination; - - var will_move = window.get_workspace ().index () != inserter.workspace_index; - - if (Meta.Prefs.get_workspaces_only_on_primary () && !window.is_on_primary_monitor ()) { - window.move_to_monitor (primary); - will_move = true; - } - - InternalUtils.insert_workspace_with_window (inserter.workspace_index, window); - - // if we don't actually change workspaces, the window-added/removed signals won't - // be emitted so we can just keep our window here - if (will_move) { - unmanaged (); - } else { - drag_canceled (); - } - - return; } else if (destination is MonitorClone) { var monitor = ((MonitorClone) destination).monitor; if (window.get_monitor () != monitor) { @@ -617,6 +556,8 @@ public class Gala.WindowClone : ActorTarget, RootTarget { } return; + } else if (destination is Meta.WindowActor) { + WindowDragProvider.get_instance ().notify_dropped (); } bool did_move = false; diff --git a/src/Widgets/MultitaskingView/WindowIconActor.vala b/src/Widgets/MultitaskingView/WindowIconActor.vala deleted file mode 100644 index 396616420..000000000 --- a/src/Widgets/MultitaskingView/WindowIconActor.vala +++ /dev/null @@ -1,164 +0,0 @@ -/* - * SPDX-License-Identifier: GPL-3.0-or-later - * SPDX-FileCopyrightText: 2014 Tom Beckmann - * 2025 elementary, Inc. (https://elementary.io) - */ - -/** - * Private class which is basically just a container for the actual - * icon and takes care of blending the same icon in different sizes - * over each other and various animations related to the icons - */ -public class Gala.WindowIconActor : Clutter.Actor { - public Meta.Window window { get; construct; } - - private float cur_icon_scale = 1.0f; - private float desired_icon_scale = 1.0f; - - private int _icon_size; - /** - * The icon size of the WindowIcon. Once set the new icon will be - * faded over the old one and the actor animates to the new size. - */ - public int icon_size { - get { - return _icon_size; - } - private set { - if (value == _icon_size && cur_icon_scale == desired_icon_scale) { - return; - } - - _icon_size = value; - cur_icon_scale = desired_icon_scale; - - var scaled_size = Utils.scale_to_int (_icon_size, cur_icon_scale); - set_size (scaled_size, scaled_size); - - fade_new_icon (); - } - } - - private bool _temporary; - /** - * Mark the WindowIcon as temporary. Only effect of this is that a pulse - * animation will be played on the actor. Used while DnDing window thumbs - * over the group. - */ - public bool temporary { - get { - return _temporary; - } - set { - if (_temporary && !value) { - remove_transition ("pulse"); - } else if (!_temporary && value && Meta.Prefs.get_gnome_animations ()) { - var transition = new Clutter.TransitionGroup () { - duration = 800, - auto_reverse = true, - repeat_count = -1, - progress_mode = Clutter.AnimationMode.LINEAR - }; - - var opacity_transition = new Clutter.PropertyTransition ("opacity"); - opacity_transition.set_from_value (100); - opacity_transition.set_to_value (255); - opacity_transition.auto_reverse = true; - - var scale_x_transition = new Clutter.PropertyTransition ("scale-x"); - scale_x_transition.set_from_value (0.8); - scale_x_transition.set_to_value (1.1); - scale_x_transition.auto_reverse = true; - - var scale_y_transition = new Clutter.PropertyTransition ("scale-y"); - scale_y_transition.set_from_value (0.8); - scale_y_transition.set_to_value (1.1); - scale_y_transition.auto_reverse = true; - - transition.add_transition (opacity_transition); - transition.add_transition (scale_x_transition); - transition.add_transition (scale_y_transition); - - add_transition ("pulse", transition); - } - - _temporary = value; - } - } - - private WindowIcon? icon = null; - private WindowIcon? old_icon = null; - - public WindowIconActor (Meta.Window window) { - Object (window: window); - } - - construct { - set_pivot_point (0.5f, 0.5f); - - window.notify["on-all-workspaces"].connect (on_all_workspaces_changed); - } - - ~WindowIconActor () { - window.notify["on-all-workspaces"].disconnect (on_all_workspaces_changed); - } - - private void on_all_workspaces_changed () { - // we don't display windows that are on all workspaces - if (window.on_all_workspaces) - destroy (); - } - - /** - * Shortcut to set both position and size of the icon - * - * @param x The x coordinate to which to animate to - * @param y The y coordinate to which to animate to - * @param size The size to which to animate to and display the icon in - */ - public void place (float x, float y, int size, float scale) { - desired_icon_scale = scale; - set_position (x, y); - icon_size = size; - } - - /** - * Fades out the old icon and fades in the new icon - */ - private void fade_new_icon () { - var new_icon = new WindowIcon (window, icon_size, (int)Math.round (cur_icon_scale)); - new_icon.add_constraint (new Clutter.BindConstraint (this, Clutter.BindCoordinate.SIZE, 0)); - new_icon.opacity = 0; - - add_child (new_icon); - - new_icon.save_easing_state (); - new_icon.set_easing_mode (Clutter.AnimationMode.EASE_OUT_QUAD); - new_icon.set_easing_duration (Utils.get_animation_duration (500)); - new_icon.restore_easing_state (); - - if (icon == null) { - icon = new_icon; - } else { - old_icon = icon; - } - - new_icon.opacity = 255; - - if (old_icon != null) { - old_icon.opacity = 0; - var transition = old_icon.get_transition ("opacity"); - if (transition != null) { - transition.completed.connect (() => { - old_icon.destroy (); - old_icon = null; - }); - } else { - old_icon.destroy (); - old_icon = null; - } - } - - icon = new_icon; - } -} diff --git a/src/Widgets/MultitaskingView/WorkspaceClone.vala b/src/Widgets/MultitaskingView/WorkspaceClone.vala index 386f0169f..3b0670562 100644 --- a/src/Widgets/MultitaskingView/WorkspaceClone.vala +++ b/src/Widgets/MultitaskingView/WorkspaceClone.vala @@ -130,7 +130,6 @@ public class Gala.WorkspaceClone : ActorTarget { public Meta.Workspace workspace { get; construct; } public float monitor_scale { get; construct set; } - public IconGroup icon_group { get; private set; } public WindowCloneContainer window_container { get; private set; } private BackgroundManager background; @@ -162,13 +161,6 @@ public class Gala.WorkspaceClone : ActorTarget { window_container.requested_close.connect (() => activate (true)); bind_property ("monitor-scale", window_container, "monitor-scale"); - icon_group = new IconGroup (display, workspace, monitor_scale); - icon_group.selected.connect (() => activate (true)); - bind_property ("monitor-scale", icon_group, "scale-factor"); - - var icons_drop_action = new DragDropAction (DragDropActionType.DESTINATION, "multitaskingview-window"); - icon_group.add_action (icons_drop_action); - var background_drop_action = new DragDropAction (DragDropActionType.DESTINATION, "multitaskingview-window"); background.add_action (background_drop_action); background_drop_action.crossed.connect ((target, hovered) => { @@ -202,7 +194,6 @@ public class Gala.WorkspaceClone : ActorTarget { && !window.on_all_workspaces && window.is_on_primary_monitor ()) { window_container.add_window (window); - icon_group.add_window (window, true); } } @@ -225,7 +216,6 @@ public class Gala.WorkspaceClone : ActorTarget { background.destroy (); window_container.destroy (); - icon_group.destroy (); } /** @@ -244,7 +234,6 @@ public class Gala.WorkspaceClone : ActorTarget { return; window_container.add_window (window); - icon_group.add_window (window); } /** @@ -252,7 +241,6 @@ public class Gala.WorkspaceClone : ActorTarget { */ private void remove_window (Meta.Window window) { window_container.remove_window (window); - icon_group.remove_window (window, opened); } private void window_entered_monitor (Meta.Display display, int monitor, Meta.Window window) { @@ -303,12 +291,6 @@ public class Gala.WorkspaceClone : ActorTarget { window_container.padding_bottom = Utils.scale_to_int (BOTTOM_OFFSET, monitor_scale); } - public override void update_progress (GestureAction action, double progress) { - if (action == SWITCH_WORKSPACE) { - icon_group.backdrop_opacity = 1 - (float) (workspace.index () + progress).abs ().clamp (0, 1); - } - } - private void activate (bool close_view) { if (close_view && workspace.active) { wm.perform_action (SHOW_MULTITASKING_VIEW); diff --git a/src/Widgets/MultitaskingView/WorkspaceInsertThumb.vala b/src/Widgets/MultitaskingView/WorkspaceInsertThumb.vala deleted file mode 100644 index 583eb7134..000000000 --- a/src/Widgets/MultitaskingView/WorkspaceInsertThumb.vala +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright 2014 Tom Beckmann - * Copyright 2023 elementary, Inc. (https://elementary.io) - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -public class Gala.WorkspaceInsertThumb : Clutter.Actor { - public const int EXPAND_DELAY = 300; - - public int workspace_index { get; construct set; } - public bool expanded { get; set; default = false; } - public int delay { get; set; default = EXPAND_DELAY; } - private float _scale_factor = 1.0f; - public float scale_factor { - get { - return _scale_factor; - } - set { - if (value != _scale_factor) { - _scale_factor = value; - reallocate (); - } - } - } - - private uint expand_timeout = 0; - - public WorkspaceInsertThumb (int workspace_index, float scale) { - Object (workspace_index: workspace_index, scale_factor: scale); - - reallocate (); - opacity = 0; - set_pivot_point (0.5f, 0.5f); - reactive = true; - x_align = Clutter.ActorAlign.CENTER; - - var drop = new DragDropAction (DragDropActionType.DESTINATION, "multitaskingview-window"); - drop.crossed.connect ((target, hovered) => { - if (!hovered) { - if (expand_timeout != 0) { - Source.remove (expand_timeout); - expand_timeout = 0; - } - - transform (false); - } else { - expand_timeout = Timeout.add (delay, expand); - } - }); - - add_action (drop); - } - - private void reallocate () { - width = Utils.scale_to_int (IconGroupContainer.SPACING, scale_factor); - height = Utils.scale_to_int (IconGroupContainer.GROUP_WIDTH, scale_factor); - y = Utils.scale_to_int (IconGroupContainer.GROUP_WIDTH - IconGroupContainer.SPACING, scale_factor) / 2; - } - - public void set_window_thumb (Meta.Window window) { - destroy_all_children (); - - var icon = new WindowIcon (window, IconGroupContainer.GROUP_WIDTH, (int)Math.round (scale_factor)) { - x = IconGroupContainer.SPACING, - x_align = Clutter.ActorAlign.CENTER - }; - add_child (icon); - } - - private bool expand () { - expand_timeout = 0; - - transform (true); - - return Source.REMOVE; - } - - private new void transform (bool expand) { - save_easing_state (); - set_easing_mode (Clutter.AnimationMode.EASE_OUT_QUAD); - set_easing_duration (Utils.get_animation_duration (200)); - - if (!expand) { - remove_transition ("pulse"); - opacity = 0; - width = Utils.scale_to_int (IconGroupContainer.SPACING, scale_factor); - expanded = false; - } else { - add_pulse_animation (); - opacity = 200; - width = Utils.scale_to_int (IconGroupContainer.GROUP_WIDTH + IconGroupContainer.SPACING * 2, scale_factor); - expanded = true; - } - - restore_easing_state (); - } - - private void add_pulse_animation () { - if (!Meta.Prefs.get_gnome_animations ()) { - return; - } - - var transition = new Clutter.TransitionGroup () { - duration = 800, - auto_reverse = true, - repeat_count = -1, - progress_mode = Clutter.AnimationMode.LINEAR - }; - - var scale_x_transition = new Clutter.PropertyTransition ("scale-x"); - scale_x_transition.set_from_value (0.8); - scale_x_transition.set_to_value (1.1); - scale_x_transition.auto_reverse = true; - - var scale_y_transition = new Clutter.PropertyTransition ("scale-y"); - scale_y_transition.set_from_value (0.8); - scale_y_transition.set_to_value (1.1); - scale_y_transition.auto_reverse = true; - - transition.add_transition (scale_x_transition); - transition.add_transition (scale_y_transition); - - add_transition ("pulse", transition); - } -} diff --git a/src/Widgets/PixelPicker.vala b/src/Widgets/PixelPicker.vala index afcd8bb0c..a44d78662 100644 --- a/src/Widgets/PixelPicker.vala +++ b/src/Widgets/PixelPicker.vala @@ -66,6 +66,6 @@ public class Gala.PixelPicker : Clutter.Actor { wm.get_display ().set_cursor (Meta.Cursor.CROSSHAIR); grab_key_focus (); - modal_proxy = wm.push_modal (this); + modal_proxy = wm.push_modal (this, true); } } diff --git a/src/Widgets/SelectionArea.vala b/src/Widgets/SelectionArea.vala index cf9fef2d6..02f4a4427 100644 --- a/src/Widgets/SelectionArea.vala +++ b/src/Widgets/SelectionArea.vala @@ -111,7 +111,7 @@ public class Gala.SelectionArea : CanvasActor { wm.get_display ().set_cursor (Meta.Cursor.CROSSHAIR); grab_key_focus (); - modal_proxy = wm.push_modal (this); + modal_proxy = wm.push_modal (this, true); } public Graphene.Rect get_selection_rectangle () { diff --git a/src/Widgets/SessionLocker.vala b/src/Widgets/SessionLocker.vala index 84053dc72..902fc8823 100644 --- a/src/Widgets/SessionLocker.vala +++ b/src/Widgets/SessionLocker.vala @@ -333,7 +333,7 @@ public class Gala.SessionLocker : Clutter.Actor { tracker.set_pointer_visible (false); visible = true; grab_key_focus (); - modal_proxy = wm.push_modal (this); + modal_proxy = wm.push_modal (this, true); if (Meta.Prefs.get_gnome_animations () && animate) { animate_and_lock (animation_time); diff --git a/src/Widgets/WindowOverview.vala b/src/Widgets/WindowOverview.vala index a112b900d..78ead9884 100644 --- a/src/Widgets/WindowOverview.vala +++ b/src/Widgets/WindowOverview.vala @@ -112,7 +112,7 @@ public class Gala.WindowOverview : ActorTarget, RootTarget, ActivatableComponent grab_key_focus (); - modal_proxy = wm.push_modal (this); + modal_proxy = wm.push_modal (this, true); modal_proxy.set_keybinding_filter (keybinding_filter); modal_proxy.allow_actions ({ ZOOM }); diff --git a/src/Widgets/WindowSwitcher/WindowSwitcher.vala b/src/Widgets/WindowSwitcher/WindowSwitcher.vala index 25cb22871..7b1260e52 100644 --- a/src/Widgets/WindowSwitcher/WindowSwitcher.vala +++ b/src/Widgets/WindowSwitcher/WindowSwitcher.vala @@ -448,7 +448,7 @@ public class Gala.WindowSwitcher : CanvasActor, GestureTarget, RootTarget { } private void push_modal () { - modal_proxy = wm.push_modal (get_stage ()); + modal_proxy = wm.push_modal (get_stage (), true); modal_proxy.allow_actions ({ SWITCH_WINDOWS }); modal_proxy.set_keybinding_filter ((binding) => { var action = Meta.Prefs.get_keybinding_action (binding.get_name ()); diff --git a/src/WindowDragProvider.vala b/src/WindowDragProvider.vala new file mode 100644 index 000000000..b71a6fdc9 --- /dev/null +++ b/src/WindowDragProvider.vala @@ -0,0 +1,35 @@ +/* + * Copyright 2025 elementary, Inc. (https://elementary.io) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Authored by: Leonhard Kargl + */ + +[DBus (name = "io.elementary.desktop.wm.WindowDragProvider")] +public class Gala.WindowDragProvider : Object { + private static GLib.Once instance; + public static WindowDragProvider get_instance () { + return instance.once (() => { return new WindowDragProvider (); }); + } + + public signal void enter (uint64 window_id); + public signal void motion (int x, int y); + public signal void leave (); + public signal void dropped (); + + internal void notify_enter (uint64 window_id) { + enter (window_id); + } + + internal void notify_motion (float x, float y) { + motion ((int) x, (int) y); + } + + internal void notify_leave () { + leave (); + } + + internal void notify_dropped () { + dropped (); + } +} diff --git a/src/WindowManager.vala b/src/WindowManager.vala index b22b019f7..4d7b4b3ab 100644 --- a/src/WindowManager.vala +++ b/src/WindowManager.vala @@ -146,6 +146,8 @@ namespace Gala { Meta.Background.refresh_all (); }); + display.notify["focus-window"].connect (on_focus_window_changed); + #if WITH_SYSTEMD if (Meta.Util.is_wayland_compositor ()) { display.init_xserver.connect ((task) => { @@ -574,10 +576,12 @@ namespace Gala { } } - if (is_modal ()) - InternalUtils.set_input_area (display, InputArea.FULLSCREEN); - else + if (is_modal ()) { + var area = multitasking_view.is_opened () ? InputArea.MULTITASKING_VIEW : InputArea.FULLSCREEN; + InternalUtils.set_input_area (display, area); + } else { InternalUtils.set_input_area (display, InputArea.DEFAULT); + } } /** @@ -611,28 +615,29 @@ namespace Gala { /** * {@inheritDoc} */ - public ModalProxy push_modal (Clutter.Actor actor) { + public ModalProxy push_modal (Clutter.Actor actor, bool grab) { var proxy = new ModalProxy (); modal_stack.offer_head (proxy); + if (grab) { + proxy.grab = stage.grab (actor); + } + + on_focus_window_changed (); + // modal already active - if (modal_stack.size >= 2) + if (modal_stack.size >= 2) { return proxy; - - unowned Meta.Display display = get_display (); + } update_input_area (); - proxy.grab = stage.grab (actor); - if (modal_stack.size == 1) { #if HAS_MUTTER48 - display.get_compositor ().disable_unredirect (); + get_display ().get_compositor ().disable_unredirect (); #else - display.disable_unredirect (); + get_display ().disable_unredirect (); #endif - } - return proxy; } @@ -645,20 +650,25 @@ namespace Gala { return; } - proxy.grab.dismiss (); + if (proxy.grab != null) { + proxy.grab.dismiss (); + } + + on_focus_window_changed (); - if (is_modal ()) + if (is_modal ()) { return; + } update_input_area (); - unowned Meta.Display display = get_display (); - + unowned var display = get_display (); #if HAS_MUTTER48 display.get_compositor ().enable_unredirect (); #else display.enable_unredirect (); #endif + display.focus_default_window (display.get_current_time ()); } /** @@ -675,6 +685,18 @@ namespace Gala { return (proxy in modal_stack); } + private void on_focus_window_changed () { + unowned var display = get_display (); + + if (!is_modal () || modal_stack.peek_head ().grab != null || display.focus_window == null || + ShellClientsManager.get_instance ().is_positioned_window (display.focus_window) + ) { + return; + } + + display.unset_input_focus (display.get_current_time ()); + } + private void dim_parent_window (Meta.Window window) { if (window.window_type != MODAL_DIALOG) { return; @@ -699,7 +721,7 @@ namespace Gala { } private void set_grab_trigger (Meta.Window window, Meta.GrabOp op) { - var proxy = push_modal (stage); + var proxy = push_modal (stage, true); ulong handler = 0; handler = stage.captured_event.connect ((event) => { diff --git a/src/meson.build b/src/meson.build index cee0ff163..e9bc57c69 100644 --- a/src/meson.build +++ b/src/meson.build @@ -16,6 +16,7 @@ gala_bin_sources = files( 'SessionManager.vala', 'SuperScrollAction.vala', 'WindowAttentionTracker.vala', + 'WindowDragProvider.vala', 'WindowListener.vala', 'WindowManager.vala', 'WindowStateSaver.vala', @@ -47,8 +48,6 @@ gala_bin_sources = files( 'ShellClients/ShellClientsManager.vala', 'ShellClients/ShellWindow.vala', 'Widgets/DwellClickTimer.vala', - 'Widgets/MultitaskingView/IconGroup.vala', - 'Widgets/MultitaskingView/IconGroupContainer.vala', 'Widgets/MultitaskingView/MonitorClone.vala', 'Widgets/MultitaskingView/MultitaskingView.vala', 'Widgets/MultitaskingView/StaticWindowClone.vala', @@ -56,9 +55,7 @@ gala_bin_sources = files( 'Widgets/MultitaskingView/Tooltip.vala', 'Widgets/MultitaskingView/WindowClone.vala', 'Widgets/MultitaskingView/WindowCloneContainer.vala', - 'Widgets/MultitaskingView/WindowIconActor.vala', 'Widgets/MultitaskingView/WorkspaceClone.vala', - 'Widgets/MultitaskingView/WorkspaceInsertThumb.vala', 'Widgets/MultitaskingView/WorkspaceRow.vala', 'Widgets/PixelPicker.vala', 'Widgets/PointerLocator.vala',