diff --git a/lib/Constants.vala b/lib/Constants.vala index 5b2a1ae0c..362252640 100644 --- a/lib/Constants.vala +++ b/lib/Constants.vala @@ -42,6 +42,7 @@ namespace Gala { MULTITASKING_VIEW, DOCK, ZOOM, + CLOSE_WINDOW, N_ACTIONS } diff --git a/src/Gestures/ScrollBackend.vala b/src/Gestures/ScrollBackend.vala index 90476687f..25081233b 100644 --- a/src/Gestures/ScrollBackend.vala +++ b/src/Gestures/ScrollBackend.vala @@ -1,5 +1,5 @@ /* - * Copyright 2021 elementary, Inc (https://elementary.io) + * Copyright 2021-2025 elementary, Inc (https://elementary.io) * 2021 José Expósito * * This program is free software: you can redistribute it and/or modify @@ -48,7 +48,8 @@ public class Gala.ScrollBackend : Object, GestureBackend { public ScrollBackend (Clutter.Actor actor, Clutter.Orientation orientation, GestureSettings settings) { Object (actor: actor, orientation: orientation, settings: settings); - actor.scroll_event.connect (on_scroll_event); + actor.captured_event.connect (on_scroll_event); + actor.leave_event.connect (on_leave_event); // When the actor is turned invisible, we don't receive a scroll finish event which would cause // us to ignore the first new scroll event if we're currently ignoring. actor.notify["visible"].connect (() => ignoring = false); @@ -56,14 +57,14 @@ public class Gala.ScrollBackend : Object, GestureBackend { private bool on_scroll_event (Clutter.Event event) { if (!can_handle_event (event)) { - return false; + return Clutter.EVENT_PROPAGATE; } if (ignoring) { if (event.get_scroll_finish_flags () != NONE) { ignoring = false; } - return false; + return Clutter.EVENT_PROPAGATE; } var time = event.get_time (); @@ -84,6 +85,14 @@ public class Gala.ScrollBackend : Object, GestureBackend { if (!started) { if (delta_x != 0 || delta_y != 0) { + if (delta_x.abs () > delta_y.abs () && orientation != HORIZONTAL || + delta_y.abs () > delta_x.abs () && orientation != VERTICAL + ) { + ignoring = true; + reset (); + return Clutter.EVENT_PROPAGATE; + } + float origin_x, origin_y; event.get_coords (out origin_x, out origin_y); Gesture gesture = build_gesture (origin_x, origin_y, delta_x, delta_y, orientation, time); @@ -104,7 +113,19 @@ public class Gala.ScrollBackend : Object, GestureBackend { } } - return true; + return Clutter.EVENT_STOP; + } + + private bool on_leave_event (Clutter.Event event) requires (event.get_type () == LEAVE) { + if (!started) { + return Clutter.EVENT_PROPAGATE; + } + + double delta = calculate_delta (delta_x, delta_y, direction); + on_end (delta, event.get_time ()); + reset (); + + return Clutter.EVENT_PROPAGATE; } private static bool can_handle_event (Clutter.Event event) { diff --git a/src/Widgets/MultitaskingView/MonitorClone.vala b/src/Widgets/MultitaskingView/MonitorClone.vala index 3e82f4e7b..c2a9429ec 100644 --- a/src/Widgets/MultitaskingView/MonitorClone.vala +++ b/src/Widgets/MultitaskingView/MonitorClone.vala @@ -14,24 +14,26 @@ public class Gala.MonitorClone : ActorTarget { public signal void window_selected (Meta.Window window); - public Meta.Display display { get; construct; } + public WindowManager wm { get; construct; } public int monitor { get; construct; } private WindowCloneContainer window_container; private BackgroundManager background; - public MonitorClone (Meta.Display display, int monitor) { - Object (display: display, monitor: monitor); + public MonitorClone (WindowManager wm, int monitor) { + Object (wm: wm, monitor: monitor); } construct { reactive = true; + unowned var display = wm.get_display (); + background = new BackgroundManager (display, monitor, false); var scale = display.get_monitor_scale (monitor); - window_container = new WindowCloneContainer (display, scale); + window_container = new WindowCloneContainer (wm, scale); window_container.window_selected.connect ((w) => { window_selected (w); }); display.window_entered_monitor.connect (window_entered); @@ -58,6 +60,7 @@ public class Gala.MonitorClone : ActorTarget { } ~MonitorClone () { + unowned var display = wm.get_display (); display.window_entered_monitor.disconnect (window_entered); display.window_left_monitor.disconnect (window_left); } @@ -66,6 +69,8 @@ public class Gala.MonitorClone : ActorTarget { * Make sure the MonitorClone is at the location of the monitor on the stage */ public void update_allocation () { + unowned var display = wm.get_display (); + var monitor_geometry = display.get_monitor_geometry (monitor); set_position (monitor_geometry.x, monitor_geometry.y); diff --git a/src/Widgets/MultitaskingView/MultitaskingView.vala b/src/Widgets/MultitaskingView/MultitaskingView.vala index 3113f07ae..b1b71f9b8 100644 --- a/src/Widgets/MultitaskingView/MultitaskingView.vala +++ b/src/Widgets/MultitaskingView/MultitaskingView.vala @@ -148,7 +148,7 @@ public class Gala.MultitaskingView : ActorTarget, ActivatableComponent { continue; } - var monitor_clone = new MonitorClone (display, monitor); + var monitor_clone = new MonitorClone (wm, monitor); monitor_clone.window_selected.connect (window_selected); monitor_clone.visible = opened; @@ -351,7 +351,7 @@ public class Gala.MultitaskingView : ActorTarget, ActivatableComponent { unowned var manager = display.get_workspace_manager (); var scale = display.get_monitor_scale (display.get_primary_monitor ()); - var workspace = new WorkspaceClone (manager.get_workspace_by_index (num), scale); + 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); diff --git a/src/Widgets/MultitaskingView/WindowClone.vala b/src/Widgets/MultitaskingView/WindowClone.vala index 1a875a223..44ff2a088 100644 --- a/src/Widgets/MultitaskingView/WindowClone.vala +++ b/src/Widgets/MultitaskingView/WindowClone.vala @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: GPL-3.0-or-later - * SPDX-FileCopyrightText: 2022-2023 elementary, Inc. (https://elementary.io) + * SPDX-FileCopyrightText: 2022-2025 elementary, Inc. (https://elementary.io) * 2014 Tom Beckmann */ @@ -13,6 +13,7 @@ public class Gala.WindowClone : ActorTarget { private const int ACTIVE_SHAPE_SIZE = 12; private const int FADE_ANIMATION_DURATION = 200; private const int TITLE_MAX_WIDTH_MARGIN = 60; + private const int CLOSE_TRANSLATION = 600; /** * The window was selected. The MultitaskingView should consider activating @@ -26,8 +27,7 @@ public class Gala.WindowClone : ActorTarget { */ public signal void request_reposition (); - public Meta.Display display { get; construct; } - + public WindowManager wm { get; construct; } public Meta.Window window { get; construct; } /** @@ -84,14 +84,17 @@ public class Gala.WindowClone : ActorTarget { private ulong check_confirm_dialog_cb = 0; private bool in_slot_animation = false; + private Clutter.Actor clone_container; private Gala.CloseButton close_button; private ActiveShape active_shape; private Clutter.Actor window_icon; private Tooltip window_title; - public WindowClone (Meta.Display display, Meta.Window window, float scale, bool overview_mode = false) { + private GestureController gesture_controller; + + public WindowClone (WindowManager wm, Meta.Window window, float scale, bool overview_mode = false) { Object ( - display: display, + wm: wm, window: window, monitor_scale_factor: scale, overview_mode: overview_mode @@ -101,6 +104,9 @@ public class Gala.WindowClone : ActorTarget { construct { reactive = true; + gesture_controller = new GestureController (CLOSE_WINDOW, this, wm); + gesture_controller.enable_scroll (this, VERTICAL); + window.unmanaged.connect (unmanaged); window.notify["fullscreen"].connect (check_shadow_requirements); window.notify["maximized-horizontally"].connect (check_shadow_requirements); @@ -126,13 +132,18 @@ public class Gala.WindowClone : ActorTarget { add_action (drag_action); } - window_title = new Tooltip (); - window_title.opacity = 0; - active_shape = new ActiveShape (); active_shape.opacity = 0; + clone_container = new Clutter.Actor () { + pivot_point = { 0.5f, 0.5f } + }; + + window_title = new Tooltip (); + window_title.opacity = 0; + add_child (active_shape); + add_child (clone_container); add_child (window_title); reallocate (); @@ -183,13 +194,7 @@ public class Gala.WindowClone : ActorTarget { } clone = new Clutter.Clone (actor); - clone.set_content_scaling_filters (TRILINEAR, TRILINEAR); - add_child (clone); - - set_child_below_sibling (active_shape, clone); - set_child_above_sibling (close_button, clone); - set_child_above_sibling (window_icon, clone); - set_child_above_sibling (window_title, clone); + clone_container.add_child (clone); check_shadow_requirements (); } @@ -269,31 +274,61 @@ public class Gala.WindowClone : ActorTarget { update_hover_widgets (true); } + public override void update_progress (Gala.GestureAction action, double progress) { + if (action != CLOSE_WINDOW || slot == null || !Meta.Prefs.get_gnome_animations ()) { + return; + } + + var target_translation_y = (float) (-CLOSE_TRANSLATION * monitor_scale_factor * progress); + var target_opacity = (uint) (255 * (1 - progress)); + + clone_container.translation_y = target_translation_y; + clone_container.opacity = target_opacity; + + window_icon.translation_y = target_translation_y; + window_icon.opacity = target_opacity; + + window_title.translation_y = target_translation_y; + window_title.opacity = target_opacity; + + close_button.translation_y = target_translation_y; + close_button.opacity = target_opacity; + } + public override void end_progress (GestureAction action) { update_hover_widgets (false); + + if (action == CLOSE_WINDOW && get_current_commit (CLOSE_WINDOW) > 0.5 && Meta.Prefs.get_gnome_animations ()) { + close_window (Meta.CURRENT_TIME); + } } public override void allocate (Clutter.ActorBox box) { base.allocate (box); + var input_rect = window.get_buffer_rect (); + var outer_rect = window.get_frame_rect (); + var clone_scale_factor = width / outer_rect.width; + + // Compensate for invisible borders of the texture + float clone_x = (input_rect.x - outer_rect.x) * clone_scale_factor; + float clone_y = (input_rect.y - outer_rect.y) * clone_scale_factor; + + var clone_container_alloc = InternalUtils.actor_box_from_rect (clone_x, clone_y, input_rect.width * clone_scale_factor, input_rect.height * clone_scale_factor); + clone_container.allocate (clone_container_alloc); + if (clone == null || (drag_action != null && drag_action.dragging)) { return; } - var input_rect = window.get_buffer_rect (); - var outer_rect = window.get_frame_rect (); - var clone_scale_factor = width / outer_rect.width; + unowned var display = wm.get_display (); clone.set_scale (clone_scale_factor, clone_scale_factor); float clone_width, clone_height; clone.get_preferred_size (null, null, out clone_width, out clone_height); - // Compensate for invisible borders of the texture - float clone_x = (input_rect.x - outer_rect.x) * clone_scale_factor; - float clone_y = (input_rect.y - outer_rect.y) * clone_scale_factor; - - var clone_alloc = InternalUtils.actor_box_from_rect (clone_x, clone_y, clone_width, clone_height); + var clone_alloc = InternalUtils.actor_box_from_rect (0, 0, clone_width, clone_height); clone.allocate (clone_alloc); Clutter.ActorBox shape_alloc = { @@ -380,15 +415,17 @@ public class Gala.WindowClone : ActorTarget { } private void check_confirm_dialog (int monitor, Meta.Window new_window) { - if (new_window.get_transient_for () == window) { - Idle.add (() => { + Idle.add (() => { + if (new_window.get_transient_for () == window) { + gesture_controller.goto (0.0); selected (); - return Source.REMOVE; - }); - SignalHandler.disconnect (window.get_display (), check_confirm_dialog_cb); - check_confirm_dialog_cb = 0; - } + SignalHandler.disconnect (window.get_display (), check_confirm_dialog_cb); + check_confirm_dialog_cb = 0; + } + + return Source.REMOVE; + }); } /** @@ -417,7 +454,7 @@ public class Gala.WindowClone : ActorTarget { if (button == Clutter.Button.PRIMARY) { selected (); } else if (button == Clutter.Button.MIDDLE && device_type == POINTER_DEVICE) { - close_window (display.get_current_time ()); + close_window (wm.get_display ().get_current_time ()); } } @@ -476,7 +513,7 @@ public class Gala.WindowClone : ActorTarget { close_button.opacity = 0; window_title.opacity = 0; - display.set_cursor (Meta.Cursor.DND_IN_DRAG); + wm.get_display ().set_cursor (Meta.Cursor.DND_IN_DRAG); return this; } @@ -529,7 +566,7 @@ public class Gala.WindowClone : ActorTarget { } } - display.set_cursor (hovered ? Meta.Cursor.DND_MOVE: Meta.Cursor.DND_IN_DRAG); + wm.get_display ().set_cursor (hovered ? Meta.Cursor.DND_MOVE: Meta.Cursor.DND_IN_DRAG); } /** @@ -538,8 +575,10 @@ public class Gala.WindowClone : ActorTarget { * otherwise we cancel the drag and animate back to our old place. */ private void drag_end (Clutter.Actor destination) { + unowned var display = wm.get_display (); + Meta.Workspace workspace = null; - var primary = window.get_display ().get_primary_monitor (); + var primary = display.get_primary_monitor (); active_shape.show (); @@ -628,7 +667,7 @@ public class Gala.WindowClone : ActorTarget { request_reposition (); - display.set_cursor (Meta.Cursor.DEFAULT); + wm.get_display ().set_cursor (Meta.Cursor.DEFAULT); if (duration > 0) { ulong handler = 0; diff --git a/src/Widgets/MultitaskingView/WindowCloneContainer.vala b/src/Widgets/MultitaskingView/WindowCloneContainer.vala index 66add5862..190609a20 100644 --- a/src/Widgets/MultitaskingView/WindowCloneContainer.vala +++ b/src/Widgets/MultitaskingView/WindowCloneContainer.vala @@ -16,7 +16,7 @@ public class Gala.WindowCloneContainer : ActorTarget { public int padding_right { get; set; default = 12; } public int padding_bottom { get; set; default = 12; } - public Meta.Display display { get; construct; } + public WindowManager wm { get; construct; } public bool overview_mode { get; construct; } private float _monitor_scale = 1.0f; @@ -40,8 +40,8 @@ public class Gala.WindowCloneContainer : ActorTarget { */ private unowned WindowClone? current_window = null; - public WindowCloneContainer (Meta.Display display, float scale, bool overview_mode = false) { - Object (display: display, monitor_scale: scale, overview_mode: overview_mode); + public WindowCloneContainer (WindowManager wm, float scale, bool overview_mode = false) { + Object (wm: wm, monitor_scale: scale, overview_mode: overview_mode); } private void reallocate () { @@ -64,9 +64,10 @@ public class Gala.WindowCloneContainer : ActorTarget { } windows.append (window); + unowned var display = wm.get_display (); var windows_ordered = InternalUtils.sort_windows (display, windows); - var new_window = new WindowClone (display, window, monitor_scale, overview_mode); + var new_window = new WindowClone (wm, window, monitor_scale, overview_mode); new_window.selected.connect ((clone) => window_selected (clone.window)); new_window.destroy.connect ((_new_window) => { @@ -127,6 +128,8 @@ public class Gala.WindowCloneContainer : ActorTarget { * during animations correct. */ private void restack_windows () { + unowned var display = wm.get_display (); + var children = get_children (); var windows = new List (); @@ -240,6 +243,8 @@ public class Gala.WindowCloneContainer : ActorTarget { return; } + unowned var display = wm.get_display (); + WindowClone? closest = null; if (current_window == null) { @@ -352,6 +357,8 @@ public class Gala.WindowCloneContainer : ActorTarget { if (!opened) { opened = true; + unowned var display = wm.get_display (); + if (current_window != null) { current_window.active = false; } diff --git a/src/Widgets/MultitaskingView/WorkspaceClone.vala b/src/Widgets/MultitaskingView/WorkspaceClone.vala index d1e8bf9e5..a98685f1c 100644 --- a/src/Widgets/MultitaskingView/WorkspaceClone.vala +++ b/src/Widgets/MultitaskingView/WorkspaceClone.vala @@ -137,6 +137,7 @@ public class Gala.WorkspaceClone : ActorTarget { */ public signal void selected (bool close_view); + public WindowManager wm { get; construct; } public Meta.Workspace workspace { get; construct; } public IconGroup icon_group { get; private set; } public WindowCloneContainer window_container { get; private set; } @@ -160,8 +161,8 @@ public class Gala.WorkspaceClone : ActorTarget { private uint hover_activate_timeout = 0; - public WorkspaceClone (Meta.Workspace workspace, float scale) { - Object (workspace: workspace, scale_factor: scale); + public WorkspaceClone (WindowManager wm, Meta.Workspace workspace, float scale) { + Object (wm: wm, workspace: workspace, scale_factor: scale); } construct { @@ -178,7 +179,7 @@ public class Gala.WorkspaceClone : ActorTarget { background = new FramedBackground (display); background.add_action (background_click_action); - window_container = new WindowCloneContainer (display, scale_factor) { + window_container = new WindowCloneContainer (wm, scale_factor) { width = monitor_geometry.width, height = monitor_geometry.height, }; diff --git a/src/Widgets/WindowOverview.vala b/src/Widgets/WindowOverview.vala index fcffd630e..5fe797101 100644 --- a/src/Widgets/WindowOverview.vala +++ b/src/Widgets/WindowOverview.vala @@ -121,7 +121,7 @@ public class Gala.WindowOverview : ActorTarget, ActivatableComponent { var geometry = display.get_monitor_geometry (i); var scale = display.get_monitor_scale (i); - window_clone_container = new WindowCloneContainer (display, scale, true) { + window_clone_container = new WindowCloneContainer (wm, scale, true) { padding_top = TOP_GAP, padding_left = BORDER, padding_right = BORDER,