diff --git a/lib/Gestures/Gesture.vala b/lib/Gestures/Gesture.vala index 2e2a5bcde..7869ac333 100644 --- a/lib/Gestures/Gesture.vala +++ b/lib/Gestures/Gesture.vala @@ -41,6 +41,7 @@ namespace Gala { MULTITASKING_VIEW, ZOOM, CUSTOM, + CUSTOM_2, N_ACTIONS } diff --git a/lib/Gestures/WorkspaceHideTracker.vala b/lib/Gestures/WorkspaceHideTracker.vala new file mode 100644 index 000000000..dc72fe8b6 --- /dev/null +++ b/lib/Gestures/WorkspaceHideTracker.vala @@ -0,0 +1,134 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + * SPDX-FileCopyrightText: 2025 elementary, Inc. (https://elementary.io) + */ + +public class Gala.WorkspaceHideTracker : Object, GestureTarget { + public signal double compute_progress (Meta.Workspace workspace); + public signal void switching_workspace_progress_updated (double new_progress); + public signal void window_state_changed_progress_updated (double new_progress); + + //we don't want to hold a strong reference to the actor because we might've been added to it which would form a reference cycle + private weak Clutter.Actor? _actor; + public Clutter.Actor? actor { get { return _actor; }} + public Meta.Display display { private get; construct; } + + private double switch_workspace_progress = 0.0; + private double[] workspace_hide_progress_cache = {}; + + public WorkspaceHideTracker (Meta.Display display, Clutter.Actor actor) { + Object (display: display); + _actor = actor; + } + + construct { + display.list_all_windows ().foreach (setup_window); + display.window_created.connect (setup_window); + + unowned var monitor_manager = display.get_context ().get_backend ().get_monitor_manager (); + monitor_manager.monitors_changed.connect (recalculate_all_workspaces); + + unowned var workspace_manager = display.get_workspace_manager (); + workspace_manager.workspace_added.connect (recalculate_all_workspaces); + workspace_manager.workspace_removed.connect (recalculate_all_workspaces); + + recalculate_all_workspaces (); + } + + private void setup_window (Meta.Window window) { + window.notify["window-type"].connect (on_window_type_changed); + + if (!Utils.get_window_is_normal (window)) { + return; + } + + if (window.on_all_workspaces) { + recalculate_all_workspaces (); + } else { + recalculate_workspace (window); + } + + window.position_changed.connect (recalculate_workspace); + window.size_changed.connect (recalculate_workspace); + window.workspace_changed.connect (recalculate_all_workspaces); + window.focused.connect (recalculate_workspace); + window.notify["on-all-workspaces"].connect (recalculate_all_workspaces); + window.notify["fullscreen"].connect (recalculate_workspace_pspec); + window.notify["minimized"].connect (recalculate_workspace_pspec); + window.notify["above"].connect (recalculate_workspace_pspec); + window.unmanaged.connect (recalculate_workspace); + } + + private void on_window_type_changed (Object obj, ParamSpec pspec) { + var window = (Meta.Window) obj; + + window.notify["window-type"].disconnect (on_window_type_changed); + window.position_changed.disconnect (recalculate_workspace); + window.size_changed.disconnect (recalculate_workspace); + window.workspace_changed.disconnect (recalculate_all_workspaces); + window.focused.disconnect (recalculate_workspace); + window.notify["on-all-workspaces"].disconnect (recalculate_all_workspaces); + window.notify["fullscreen"].disconnect (recalculate_workspace_pspec); + window.notify["minimized"].disconnect (recalculate_workspace_pspec); + window.notify["above"].disconnect (recalculate_workspace_pspec); + window.unmanaged.disconnect (recalculate_workspace); + + setup_window (window); + } + + public override void propagate (UpdateType update_type, GestureAction action, double progress) { + if (action != SWITCH_WORKSPACE || update_type == COMMIT) { + return; + } + + switch_workspace_progress = progress.abs (); + switching_workspace_progress_updated (get_hidden_progress ()); + } + + private double get_hidden_progress () { + var n_workspaces = workspace_hide_progress_cache.length; + + var left_workspace = int.max ((int) Math.floor (switch_workspace_progress), 0); + var right_workspace = int.min ((int) Math.ceil (switch_workspace_progress), n_workspaces - 1); + + var relative_progress = switch_workspace_progress - left_workspace; + + return ( + workspace_hide_progress_cache[left_workspace] * (1 - relative_progress) + + workspace_hide_progress_cache[right_workspace] * relative_progress + ); + } + + /** + * Trigger recalculation of all workspaces + */ + public void recalculate_all_workspaces () { + unowned var workspace_manager = display.get_workspace_manager (); + workspace_hide_progress_cache = new double[workspace_manager.n_workspaces]; + foreach (unowned var workspace in workspace_manager.get_workspaces ()) { + internal_recalculate_workspace (workspace, false); + } + + window_state_changed_progress_updated (get_hidden_progress ()); + } + + private void internal_recalculate_workspace (Meta.Workspace? workspace, bool send_signal) { + if (workspace == null || workspace.workspace_index >= workspace_hide_progress_cache.length) { + return; + } + + workspace_hide_progress_cache[workspace.workspace_index] = compute_progress (workspace); + + if (send_signal) { + window_state_changed_progress_updated (get_hidden_progress ()); + } + } + + private void recalculate_workspace (Meta.Window window) { + internal_recalculate_workspace (window.get_workspace (), true); + } + + private void recalculate_workspace_pspec (Object obj, ParamSpec pspec) { + internal_recalculate_workspace (((Meta.Window) obj).get_workspace (), true); + } +} diff --git a/lib/WindowManager.vala b/lib/WindowManager.vala index 41c03517a..1eb1de35a 100644 --- a/lib/WindowManager.vala +++ b/lib/WindowManager.vala @@ -173,5 +173,13 @@ namespace Gala { * @return true if the action should be prohibited, false otherwise */ public abstract bool filter_action (GestureAction action); + + /** + * Adds target to the multitasking view and window overview so the target responds to the multitasking view + * close/open gesture and shortcuts. + * + * @param target Target to add to multitasking view and window overview + */ + public abstract void add_multitasking_view_target (GestureTarget target); } } diff --git a/lib/meson.build b/lib/meson.build index 058829832..94885b458 100644 --- a/lib/meson.build +++ b/lib/meson.build @@ -36,7 +36,8 @@ gala_lib_sources = files( 'Gestures/ScrollBackend.vala', 'Gestures/SpringTimeline.vala', 'Gestures/ToucheggBackend.vala', - 'Gestures/TouchpadBackend.vala' + 'Gestures/TouchpadBackend.vala', + 'Gestures/WorkspaceHideTracker.vala' ) + gala_common_enums gala_resources = gnome.compile_resources( diff --git a/plugins/pip/Main.vala b/plugins/pip/Main.vala index c5976e7e2..b0fedd1ff 100644 --- a/plugins/pip/Main.vala +++ b/plugins/pip/Main.vala @@ -97,33 +97,31 @@ public class Gala.Plugins.PIP.Plugin : Gala.Plugin { return; } - var popup_window = new PopupWindow (wm.get_display (), active); + var popup_window = new PopupWindow (wm, active); popup_window.set_container_clip (rect); - popup_window.show.connect (on_popup_window_show); - popup_window.hide.connect (on_popup_window_hide); add_window (popup_window); } - private void on_popup_window_show (Clutter.Actor popup_window) { - track_actor (popup_window); - update_region (); - } - - private void on_popup_window_hide (Clutter.Actor popup_window) { - untrack_actor (popup_window); - update_region (); - } - private void select_window_at (int x, int y) { var selected = get_window_actor_at (x, y); if (selected != null) { - var popup_window = new PopupWindow (wm.get_display (), selected); - popup_window.show.connect (on_popup_window_show); - popup_window.hide.connect (on_popup_window_hide); + var popup_window = new PopupWindow (wm, selected); add_window (popup_window); } } + private void popup_window_reactive_changed (GLib.Object obj, GLib.ParamSpec pspec) requires (obj is PopupWindow) { + var popup_window = (PopupWindow) obj; + + if (popup_window.reactive) { + track_actor (popup_window); + } else { + untrack_actor (popup_window); + } + + update_region (); + } + private void clear_selection_area () { if (selection_area != null) { untrack_actor (selection_area); @@ -189,6 +187,8 @@ public class Gala.Plugins.PIP.Plugin : Gala.Plugin { } private void add_window (PopupWindow popup_window) { + track_actor (popup_window); + popup_window.notify["reactive"].connect (popup_window_reactive_changed); popup_window.closed.connect (() => remove_window (popup_window)); windows.add (popup_window); wm.ui_group.add_child (popup_window); diff --git a/plugins/pip/PopupWindow.vala b/plugins/pip/PopupWindow.vala index 31c1e3a81..75be04334 100644 --- a/plugins/pip/PopupWindow.vala +++ b/plugins/pip/PopupWindow.vala @@ -4,7 +4,7 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -public class Gala.Plugins.PIP.PopupWindow : Clutter.Actor { +public class Gala.Plugins.PIP.PopupWindow : Clutter.Actor, GestureTarget, RootTarget { private int button_size; private int container_margin; private const uint FADE_OUT_TIMEOUT = 200; @@ -15,7 +15,9 @@ public class Gala.Plugins.PIP.PopupWindow : Clutter.Actor { public signal void closed (); - public Meta.Display display { get; construct; } + public Clutter.Actor? actor { get { return this; } } + + public WindowManager wm { get; construct; } public Meta.WindowActor window_actor { get; construct; } private Clutter.Clone clone; // clone itself @@ -34,8 +36,10 @@ public class Gala.Plugins.PIP.PopupWindow : Clutter.Actor { private bool resizing = false; private bool off_screen = false; private Clutter.Grab? grab = null; - - private static unowned Meta.Window? previous_focus = null; + + private GestureController gesture_controller; + private WorkspaceHideTracker workspace_hide_tracker; + private PropertyTarget property_target; // From https://opensourcehacker.com/2011/12/01/calculate-aspect-ratio-conserving-resize-for-images-in-javascript/ private static void calculate_aspect_ratio_size_fit (float src_width, float src_height, float max_width, float max_height, @@ -45,11 +49,12 @@ public class Gala.Plugins.PIP.PopupWindow : Clutter.Actor { height = src_height * ratio; } - public PopupWindow (Meta.Display display, Meta.WindowActor window_actor) { - Object (display: display, window_actor: window_actor); + public PopupWindow (WindowManager wm, Meta.WindowActor window_actor) { + Object (wm: wm, window_actor: window_actor); } construct { + unowned var display = wm.get_display (); var scale = display.get_monitor_scale (display.get_current_monitor ()); button_size = Gala.Utils.scale_to_int (36, scale); @@ -122,41 +127,35 @@ public class Gala.Plugins.PIP.PopupWindow : Clutter.Actor { unowned var window = window_actor.get_meta_window (); window.unmanaged.connect (on_close_click_clicked); - window.notify["appears-focused"].connect (update_window_focus); - unowned var workspace_manager = display.get_workspace_manager (); - workspace_manager.active_workspace_changed.connect (update_window_focus); - } + wm.add_multitasking_view_target (this); - public override void show () { - base.show (); + gesture_controller = new GestureController (CUSTOM, wm) { + progress = 0.0 + }; + add_gesture_controller (gesture_controller); - opacity = 0; + workspace_hide_tracker = new WorkspaceHideTracker (display, this); + workspace_hide_tracker.compute_progress.connect (calculate_progress); + workspace_hide_tracker.switching_workspace_progress_updated.connect ((value) => gesture_controller.progress = value); + workspace_hide_tracker.window_state_changed_progress_updated.connect (gesture_controller.goto); - save_easing_state (); - set_easing_duration (Utils.get_animation_duration (200)); - opacity = 255; - restore_easing_state (); + property_target = new PropertyTarget (CUSTOM, this, "opacity", typeof (uint), 255u, 0u); } - public override void hide () { - opacity = 255; + public override void propagate (UpdateType update_type, GestureAction action, double progress) { + warning ("%s %s %f", update_type.to_string (), action.to_string (), progress); - var duration = Utils.get_animation_duration (200); - save_easing_state (); - set_easing_duration (duration); - opacity = 0; - restore_easing_state (); + workspace_hide_tracker.propagate (update_type, action, progress); - if (duration == 0) { - base.hide (); - } else { - ulong completed_id = 0; - completed_id = transitions_completed.connect (() => { - disconnect (completed_id); - base.hide (); - }); + if (action != CUSTOM || update_type == COMMIT) { + return; } + + // warning ("Setting progress to %f", progress); + property_target.propagate (UPDATE, CUSTOM, progress); + + reactive = update_type == END; } public override bool enter_event (Clutter.Event event) { @@ -199,9 +198,9 @@ public class Gala.Plugins.PIP.PopupWindow : Clutter.Actor { private Clutter.Actor on_move_begin () { #if HAS_MUTTER48 - display.set_cursor (Meta.Cursor.MOVE); + wm.get_display ().set_cursor (Meta.Cursor.MOVE); #else - display.set_cursor (Meta.Cursor.DND_IN_DRAG); + wm.get_display ().set_cursor (Meta.Cursor.DND_IN_DRAG); #endif return this; @@ -210,7 +209,7 @@ public class Gala.Plugins.PIP.PopupWindow : Clutter.Actor { private void on_move_end () { reactive = true; update_screen_position (); - display.set_cursor (Meta.Cursor.DEFAULT); + wm.get_display ().set_cursor (Meta.Cursor.DEFAULT); } private bool on_resize_button_press (Clutter.Event event) { @@ -228,7 +227,7 @@ public class Gala.Plugins.PIP.PopupWindow : Clutter.Actor { grab = resize_button.get_stage ().grab (resize_button); resize_button.event.connect (on_resize_event); - display.set_cursor (Meta.Cursor.SE_RESIZE); + wm.get_display ().set_cursor (Meta.Cursor.SE_RESIZE); return Clutter.EVENT_PROPAGATE; } @@ -289,7 +288,7 @@ public class Gala.Plugins.PIP.PopupWindow : Clutter.Actor { update_screen_position (); - display.set_cursor (Meta.Cursor.DEFAULT); + wm.get_display ().set_cursor (Meta.Cursor.DEFAULT); } private void on_allocation_changed () { @@ -315,25 +314,14 @@ public class Gala.Plugins.PIP.PopupWindow : Clutter.Actor { }); } - private void update_window_focus () { - unowned Meta.Window focus_window = display.get_focus_window (); - if ((focus_window != null && !Utils.get_window_is_normal (focus_window)) - || (previous_focus != null && !Utils.get_window_is_normal (previous_focus))) { - previous_focus = focus_window; - return; - } - - unowned var workspace_manager = display.get_workspace_manager (); - unowned var active_workspace = workspace_manager.get_active_workspace (); + private double calculate_progress (Meta.Workspace workspace) { unowned var window = window_actor.get_meta_window (); - if (window.appears_focused && window.located_on_workspace (active_workspace)) { - hide (); - } else if (!window_actor.is_destroyed ()) { - show (); + if (window.has_focus () && window.get_workspace () == workspace) { + return 1.0; + } else { + return 0.0; } - - previous_focus = focus_window; } private void update_size () { @@ -422,7 +410,7 @@ public class Gala.Plugins.PIP.PopupWindow : Clutter.Actor { private void place_window_in_screen () { off_screen = false; - var workarea_rect = display.get_workspace_manager ().get_active_workspace ().get_work_area_all_monitors (); + var workarea_rect = wm.get_display ().get_workspace_manager ().get_active_workspace ().get_work_area_all_monitors (); var screen_limit_start_x = workarea_rect.x; var screen_limit_end_x = workarea_rect.x + workarea_rect.width - width; @@ -448,6 +436,7 @@ public class Gala.Plugins.PIP.PopupWindow : Clutter.Actor { set_easing_mode (Clutter.AnimationMode.EASE_OUT_BACK); set_easing_duration (duration); + unowned var display = wm.get_display (); var monitor_rect = display.get_monitor_geometry (display.get_current_monitor ()); int monitor_x = monitor_rect.x; @@ -495,6 +484,7 @@ public class Gala.Plugins.PIP.PopupWindow : Clutter.Actor { } private bool coord_is_in_other_monitor (float coord, Clutter.Orientation axis) { + unowned var display = wm.get_display (); int n_monitors = display.get_n_monitors (); if (n_monitors == 1) { diff --git a/src/ShellClients/HideTracker.vala b/src/ShellClients/HideTracker.vala index 7ab79b375..6fe97a604 100644 --- a/src/ShellClients/HideTracker.vala +++ b/src/ShellClients/HideTracker.vala @@ -1,5 +1,5 @@ /* - * Copyright 2024 elementary, Inc. (https://elementary.io) + * Copyright 2024-2025 elementary, Inc. (https://elementary.io) * SPDX-License-Identifier: GPL-3.0-or-later * * Authored by: Leonhard Kargl @@ -7,15 +7,13 @@ public class Gala.HideTracker : Object { private const int BARRIER_OFFSET = 50; // Allow hot corner trigger - private const int UPDATE_TIMEOUT = 200; private const int HIDE_DELAY = 500; public signal void hide (); public signal void show (); public Meta.Display display { get; construct; } - public unowned PanelWindow panel { get; construct; } - public Pantheon.Desktop.HideMode hide_mode { get; set; } + public unowned ShellWindow panel { get; construct; } private static GLib.Settings behavior_settings; @@ -23,19 +21,14 @@ public class Gala.HideTracker : Object { private bool hovered = false; - private bool overlap = false; - private bool focus_overlap = false; - private bool focus_maximized_overlap = false; - private bool fullscreen_overlap = false; - - private Meta.Window current_focus_window; + private uint num_transients = 0; + private bool has_transients { get { return num_transients > 0; } } private Barrier? barrier; private uint hide_timeout_id = 0; - private uint update_timeout_id = 0; - public HideTracker (Meta.Display display, PanelWindow panel) { + public HideTracker (Meta.Display display, ShellWindow panel) { Object (display: display, panel: panel); } @@ -49,22 +42,6 @@ public class Gala.HideTracker : Object { // access the panel which was already freed. To prevent that make sure we reset // the timeouts so that we get freed immediately reset_hide_timeout (); - reset_update_timeout (); - }); - - // Can't be local otherwise we get a memory leak :( - // See https://gitlab.gnome.org/GNOME/vala/-/issues/1548 - current_focus_window = display.focus_window; - track_focus_window (current_focus_window); - display.notify["focus-window"].connect (() => { - untrack_focus_window (current_focus_window); - current_focus_window = display.focus_window; - track_focus_window (current_focus_window); - }); - - display.window_created.connect ((window) => { - schedule_update (); - window.unmanaged.connect (schedule_update); }); #if HAS_MUTTER48 @@ -77,11 +54,23 @@ public class Gala.HideTracker : Object { if (hovered != has_pointer) { hovered = has_pointer; - schedule_update (); + check_trigger_conditions (); } }); - display.get_workspace_manager ().active_workspace_changed.connect (schedule_update); + display.window_created.connect ((new_window) => { + InternalUtils.wait_for_window_actor (new_window, (new_window_actor) => { + if (panel.window.is_ancestor_of_transient (new_window_actor.meta_window)) { + num_transients++; + check_trigger_conditions (); + + new_window_actor.meta_window.unmanaged.connect (() => { + num_transients = uint.max (num_transients - 1, 0); + check_trigger_conditions (); + }); + } + }); + }); pan_action = new Clutter.PanAction () { n_touch_points = 1, @@ -101,146 +90,21 @@ public class Gala.HideTracker : Object { 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 (); } - - private void track_focus_window (Meta.Window? window) { - if (window == null) { - return; - } - - window.position_changed.connect (schedule_update); - window.size_changed.connect (schedule_update); - schedule_update (); - } - - private void untrack_focus_window (Meta.Window? window) { - if (window == null) { - return; - } - - window.position_changed.disconnect (schedule_update); - window.size_changed.disconnect (schedule_update); - schedule_update (); - } - - public void schedule_update () { - if (update_timeout_id != 0) { - return; - } - - update_timeout_id = Timeout.add (UPDATE_TIMEOUT, () => { - update_overlap (); - update_timeout_id = 0; - return Source.REMOVE; - }); - } - - private void reset_update_timeout () { - if (update_timeout_id != 0) { - Source.remove (update_timeout_id); - update_timeout_id = 0; - } - } - - public void update_overlap () { - overlap = false; - focus_overlap = false; - focus_maximized_overlap = false; - fullscreen_overlap = display.get_monitor_in_fullscreen (panel.window.get_monitor ()); - - 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; - } - - if (window.minimized) { - continue; - } - - var type = window.get_window_type (); - if (type == DESKTOP || type == DOCK || type == MENU || type == SPLASHSCREEN) { - continue; - } - - if (!panel.get_custom_window_rect ().overlap (window.get_frame_rect ())) { - continue; - } - - overlap = true; - - if (window != normal_mru_window && window != any_mru_window) { - continue; - } - - focus_overlap = true; - focus_maximized_overlap = window.maximized_vertically; - } - - update_hidden (); - } - - private void update_hidden () { - switch (hide_mode) { - case MAXIMIZED_FOCUS_WINDOW: - toggle_display (focus_maximized_overlap); - break; - - case OVERLAPPING_FOCUS_WINDOW: - toggle_display (focus_overlap); - break; - - case OVERLAPPING_WINDOW: - toggle_display (overlap); - break; - - case ALWAYS: - toggle_display (true); - break; - - case NEVER: - toggle_display (fullscreen_overlap); - break; - } - } - - private void toggle_display (bool should_hide) { - hovered = panel.window.has_pointer (); - - // Showing panels in fullscreen is broken in X11 - if (should_hide && !hovered && !panel.window.has_focus () || InternalUtils.get_x11_in_fullscreen (display)) { - trigger_hide (); - } else { + private void check_trigger_conditions () { + if (hovered || has_transients) { trigger_show (); + } else { + trigger_hide (); } } private void trigger_hide () { - if (hide_timeout_id != 0) { - return; - } - - // Don't hide if we have transients, e.g. an open popover, dialog, etc. - var has_transients = false; - panel.window.foreach_transient (() => { - has_transients = true; - return false; - }); - - if (has_transients) { - reset_hide_timeout (); - - return; - } + reset_hide_timeout (); hide_timeout_id = Timeout.add_once (HIDE_DELAY, () => { hide (); @@ -349,9 +213,10 @@ public class Gala.HideTracker : Object { return; } - if (hide_mode != NEVER || behavior_settings.get_boolean ("enable-hotcorners-in-fullscreen")) { + if (!display.get_monitor_in_fullscreen (panel.window.get_monitor ()) || + behavior_settings.get_boolean ("enable-hotcorners-in-fullscreen") + ) { trigger_show (); - schedule_update (); } } } diff --git a/src/ShellClients/PanelWindow.vala b/src/ShellClients/PanelWindow.vala index 3008fe9a5..96057781a 100644 --- a/src/ShellClients/PanelWindow.vala +++ b/src/ShellClients/PanelWindow.vala @@ -1,45 +1,16 @@ /* - * Copyright 2024 elementary, Inc. (https://elementary.io) + * Copyright 2024-2025 elementary, Inc. (https://elementary.io) * SPDX-License-Identifier: GPL-3.0-or-later * * Authored by: Leonhard Kargl */ -public class Gala.PanelWindow : ShellWindow, RootTarget { - private static HashTable window_struts = new HashTable (null, null); - - public WindowManager wm { get; construct; } - public Pantheon.Desktop.Anchor anchor { get; construct set; } - - public Pantheon.Desktop.HideMode hide_mode { - get { - return hide_tracker.hide_mode; - } - set { - hide_tracker.hide_mode = value; - - if (value == NEVER) { - make_exclusive (); - } else { - unmake_exclusive (); - } - } - } - - private GestureController gesture_controller; - private HideTracker hide_tracker; - +public class Gala.PanelWindow : ShellWindow { public PanelWindow (WindowManager wm, Meta.Window window, Pantheon.Desktop.Anchor anchor) { Object (wm: wm, anchor: anchor, window: window, position: Position.from_anchor (anchor)); } construct { - window.unmanaging.connect (() => { - if (window_struts.remove (window)) { - update_struts (); - } - }); - notify["anchor"].connect (() => position = Position.from_anchor (anchor)); unowned var workspace_manager = window.display.get_workspace_manager (); @@ -50,81 +21,10 @@ public class Gala.PanelWindow : ShellWindow, RootTarget { window.position_changed.connect (update_strut); notify["width"].connect (update_strut); notify["height"].connect (update_strut); - - gesture_controller = new GestureController (CUSTOM, wm); - add_gesture_controller (gesture_controller); - - hide_tracker = new HideTracker (wm.get_display (), this); - hide_tracker.hide.connect (hide); - 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); - } - - private void show () { - gesture_controller.goto (0); - } - - private void make_exclusive () { - update_strut (); - } - - private void update_strut () { - if (hide_mode != NEVER) { - return; - } - - var rect = get_custom_window_rect (); - - Meta.Strut strut = { - rect, - side_from_anchor (anchor) - }; - - window_struts[window] = strut; - - update_struts (); - } - - private void update_struts () { - var list = new SList (); - - foreach (var window_strut in window_struts.get_values ()) { - list.append (window_strut); - } - - foreach (var workspace in wm.get_display ().get_workspace_manager ().get_workspaces ()) { - workspace.set_builtin_struts (list); - } - } - - private void unmake_exclusive () { - if (window in window_struts) { - window_struts.remove (window); - 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/ShellWindow.vala b/src/ShellClients/ShellWindow.vala index 586627ade..b8fdc7a4d 100644 --- a/src/ShellClients/ShellWindow.vala +++ b/src/ShellClients/ShellWindow.vala @@ -5,19 +5,47 @@ * Authored by: Leonhard Kargl */ -public class Gala.ShellWindow : PositionedWindow, GestureTarget { +public class Gala.ShellWindow : PositionedWindow, RootTarget, GestureTarget { + public WindowManager wm { get; construct; } 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; } + public Pantheon.Desktop.Anchor anchor { get; construct set; } + + private Pantheon.Desktop.HideMode _hide_mode; + public Pantheon.Desktop.HideMode hide_mode { + get { + return _hide_mode; + } + set { + _hide_mode = value; + + if (value == NEVER) { + make_exclusive (); + } else { + unmake_exclusive (); + } + + workspace_hide_tracker.recalculate_all_workspaces (); + } + } + + private static HashTable window_struts = new HashTable (null, null); private Meta.WindowActor window_actor; private double custom_progress = 0; private double multitasking_view_progress = 0; + private double workspace_reveal_progress = 0; private int animations_ongoing = 0; private PropertyTarget property_target; + private GestureController custom_gesture_controller; + private GestureController workspace_gesture_controller; + private HideTracker hide_tracker; + private WorkspaceHideTracker workspace_hide_tracker; + public ShellWindow (Meta.Window window, Position position, Variant? position_data = null) { base (window, position, position_data); } @@ -25,11 +53,34 @@ public class Gala.ShellWindow : PositionedWindow, GestureTarget { construct { window_actor = (Meta.WindowActor) window.get_compositor_private (); + custom_gesture_controller = new GestureController (CUSTOM, wm) { + progress = 1.0 + }; + add_gesture_controller (custom_gesture_controller); + + workspace_gesture_controller = new GestureController (CUSTOM_2, wm); + add_gesture_controller (workspace_gesture_controller); + + hide_tracker = new HideTracker (window.display, this); + hide_tracker.hide.connect (() => custom_gesture_controller.goto (1)); + hide_tracker.show.connect (() => custom_gesture_controller.goto (0)); + + workspace_hide_tracker = new WorkspaceHideTracker (window.display, actor); + workspace_hide_tracker.compute_progress.connect (update_overlap); + workspace_hide_tracker.switching_workspace_progress_updated.connect ((value) => workspace_gesture_controller.progress = value); + workspace_hide_tracker.window_state_changed_progress_updated.connect (workspace_gesture_controller.goto); + window_actor.notify["width"].connect (update_clip); window_actor.notify["height"].connect (update_clip); window_actor.notify["translation-y"].connect (update_clip); notify["position"].connect (update_clip); + window.unmanaging.connect (() => { + if (window_struts.remove (window)) { + update_struts (); + } + }); + window.size_changed.connect (update_target); notify["position"].connect (update_target); update_target (); @@ -43,13 +94,15 @@ public class Gala.ShellWindow : PositionedWindow, GestureTarget { calculate_value (false), calculate_value (true) ); + + workspace_hide_tracker.recalculate_all_workspaces (); } private double get_hidden_progress () { if (visible_in_multitasking_view) { - return double.min (custom_progress, 1 - multitasking_view_progress); + return double.min (double.min (custom_progress, workspace_reveal_progress), 1 - multitasking_view_progress); } else { - return double.max (custom_progress, multitasking_view_progress); + return double.max (double.min (custom_progress, workspace_reveal_progress), multitasking_view_progress); } } @@ -58,6 +111,8 @@ public class Gala.ShellWindow : PositionedWindow, GestureTarget { } public override void propagate (UpdateType update_type, GestureAction action, double progress) { + workspace_hide_tracker.propagate (update_type, action, progress); + switch (update_type) { case START: animations_ongoing++; @@ -88,6 +143,10 @@ public class Gala.ShellWindow : PositionedWindow, GestureTarget { custom_progress = progress; break; + case CUSTOM_2: + workspace_reveal_progress = progress; + break; + default: break; } @@ -188,4 +247,117 @@ public class Gala.ShellWindow : PositionedWindow, GestureTarget { window_actor.remove_clip (); } } + + private double update_overlap (Meta.Workspace workspace) { + var overlap = false; + var focus_overlap = false; + var focus_maximized_overlap = false; + var fullscreen_overlap = window.display.get_monitor_in_fullscreen (window.get_monitor ()); + + Meta.Window? normal_mru_window, any_mru_window; + normal_mru_window = InternalUtils.get_mru_window (workspace, out any_mru_window); + + foreach (var window in workspace.list_windows ()) { + if (window == this.window) { + continue; + } + + if (window.minimized) { + continue; + } + + var type = window.get_window_type (); + if (type == DESKTOP || type == DOCK || type == MENU || type == SPLASHSCREEN) { + continue; + } + + if (!get_custom_window_rect ().overlap (window.get_frame_rect ())) { + continue; + } + + overlap = true; + + if (window != normal_mru_window && window != any_mru_window) { + continue; + } + + focus_overlap = true; + focus_maximized_overlap = window.maximized_vertically; + } + + switch (hide_mode) { + case MAXIMIZED_FOCUS_WINDOW: + return focus_maximized_overlap ? 1.0 : 0.0; + + case OVERLAPPING_FOCUS_WINDOW: + return focus_overlap ? 1.0 : 0.0; + + case OVERLAPPING_WINDOW: + return overlap ? 1.0 : 0.0; + + case ALWAYS: + return 1.0; + + case NEVER: + return fullscreen_overlap ? 1.0 : 0.0; + } + + return 0.0; + } + + private void make_exclusive () { + update_strut (); + } + + internal void update_strut () { + if (hide_mode != NEVER) { + return; + } + + var rect = get_custom_window_rect (); + + Meta.Strut strut = { + rect, + side_from_anchor (anchor) + }; + + window_struts[window] = strut; + + update_struts (); + } + + internal void update_struts () { + var list = new SList (); + + foreach (var window_strut in window_struts.get_values ()) { + list.append (window_strut); + } + + foreach (var workspace in wm.get_display ().get_workspace_manager ().get_workspaces ()) { + workspace.set_builtin_struts (list); + } + } + + private void unmake_exclusive () { + if (window in window_struts) { + window_struts.remove (window); + 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/WindowManager.vala b/src/WindowManager.vala index 3a2fd7983..bc0ce200a 100644 --- a/src/WindowManager.vala +++ b/src/WindowManager.vala @@ -1727,6 +1727,14 @@ namespace Gala { return modal_stack.peek_head ().filter_action (action); } + public void add_multitasking_view_target (GestureTarget target) { + multitasking_view.add_target (target); + + if (window_overview is WindowOverview) { + ((WindowOverview) window_overview).add_target (target); + } + } + public override void confirm_display_change () { unowned var monitor_manager = get_display ().get_context ().get_backend ().get_monitor_manager (); var timeout = monitor_manager.get_display_configuration_timeout ();