Skip to content

Commit d9713d1

Browse files
Multitasking view: swipe up to close window (#2327)
Co-authored-by: Leonhard <[email protected]>
1 parent 8cdd41c commit d9713d1

File tree

8 files changed

+128
-54
lines changed

8 files changed

+128
-54
lines changed

lib/Constants.vala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ namespace Gala {
4242
MULTITASKING_VIEW,
4343
DOCK,
4444
ZOOM,
45+
CLOSE_WINDOW,
4546
N_ACTIONS
4647
}
4748

src/Gestures/ScrollBackend.vala

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2021 elementary, Inc (https://elementary.io)
2+
* Copyright 2021-2025 elementary, Inc (https://elementary.io)
33
* 2021 José Expósito <[email protected]>
44
*
55
* This program is free software: you can redistribute it and/or modify
@@ -48,22 +48,23 @@ public class Gala.ScrollBackend : Object, GestureBackend {
4848
public ScrollBackend (Clutter.Actor actor, Clutter.Orientation orientation, GestureSettings settings) {
4949
Object (actor: actor, orientation: orientation, settings: settings);
5050

51-
actor.scroll_event.connect (on_scroll_event);
51+
actor.captured_event.connect (on_scroll_event);
52+
actor.leave_event.connect (on_leave_event);
5253
// When the actor is turned invisible, we don't receive a scroll finish event which would cause
5354
// us to ignore the first new scroll event if we're currently ignoring.
5455
actor.notify["visible"].connect (() => ignoring = false);
5556
}
5657

5758
private bool on_scroll_event (Clutter.Event event) {
5859
if (!can_handle_event (event)) {
59-
return false;
60+
return Clutter.EVENT_PROPAGATE;
6061
}
6162

6263
if (ignoring) {
6364
if (event.get_scroll_finish_flags () != NONE) {
6465
ignoring = false;
6566
}
66-
return false;
67+
return Clutter.EVENT_PROPAGATE;
6768
}
6869

6970
var time = event.get_time ();
@@ -84,6 +85,14 @@ public class Gala.ScrollBackend : Object, GestureBackend {
8485

8586
if (!started) {
8687
if (delta_x != 0 || delta_y != 0) {
88+
if (delta_x.abs () > delta_y.abs () && orientation != HORIZONTAL ||
89+
delta_y.abs () > delta_x.abs () && orientation != VERTICAL
90+
) {
91+
ignoring = true;
92+
reset ();
93+
return Clutter.EVENT_PROPAGATE;
94+
}
95+
8796
float origin_x, origin_y;
8897
event.get_coords (out origin_x, out origin_y);
8998
Gesture gesture = build_gesture (origin_x, origin_y, delta_x, delta_y, orientation, time);
@@ -104,7 +113,19 @@ public class Gala.ScrollBackend : Object, GestureBackend {
104113
}
105114
}
106115

107-
return true;
116+
return Clutter.EVENT_STOP;
117+
}
118+
119+
private bool on_leave_event (Clutter.Event event) requires (event.get_type () == LEAVE) {
120+
if (!started) {
121+
return Clutter.EVENT_PROPAGATE;
122+
}
123+
124+
double delta = calculate_delta (delta_x, delta_y, direction);
125+
on_end (delta, event.get_time ());
126+
reset ();
127+
128+
return Clutter.EVENT_PROPAGATE;
108129
}
109130

110131
private static bool can_handle_event (Clutter.Event event) {

src/Widgets/MultitaskingView/MonitorClone.vala

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,24 +14,26 @@
1414
public class Gala.MonitorClone : ActorTarget {
1515
public signal void window_selected (Meta.Window window);
1616

17-
public Meta.Display display { get; construct; }
17+
public WindowManager wm { get; construct; }
1818
public int monitor { get; construct; }
1919

2020
private WindowCloneContainer window_container;
2121
private BackgroundManager background;
2222

23-
public MonitorClone (Meta.Display display, int monitor) {
24-
Object (display: display, monitor: monitor);
23+
public MonitorClone (WindowManager wm, int monitor) {
24+
Object (wm: wm, monitor: monitor);
2525
}
2626

2727
construct {
2828
reactive = true;
2929

30+
unowned var display = wm.get_display ();
31+
3032
background = new BackgroundManager (display, monitor, false);
3133

3234
var scale = display.get_monitor_scale (monitor);
3335

34-
window_container = new WindowCloneContainer (display, scale);
36+
window_container = new WindowCloneContainer (wm, scale);
3537
window_container.window_selected.connect ((w) => { window_selected (w); });
3638

3739
display.window_entered_monitor.connect (window_entered);
@@ -58,6 +60,7 @@ public class Gala.MonitorClone : ActorTarget {
5860
}
5961

6062
~MonitorClone () {
63+
unowned var display = wm.get_display ();
6164
display.window_entered_monitor.disconnect (window_entered);
6265
display.window_left_monitor.disconnect (window_left);
6366
}
@@ -66,6 +69,8 @@ public class Gala.MonitorClone : ActorTarget {
6669
* Make sure the MonitorClone is at the location of the monitor on the stage
6770
*/
6871
public void update_allocation () {
72+
unowned var display = wm.get_display ();
73+
6974
var monitor_geometry = display.get_monitor_geometry (monitor);
7075

7176
set_position (monitor_geometry.x, monitor_geometry.y);

src/Widgets/MultitaskingView/MultitaskingView.vala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ public class Gala.MultitaskingView : ActorTarget, ActivatableComponent {
148148
continue;
149149
}
150150

151-
var monitor_clone = new MonitorClone (display, monitor);
151+
var monitor_clone = new MonitorClone (wm, monitor);
152152
monitor_clone.window_selected.connect (window_selected);
153153
monitor_clone.visible = opened;
154154

@@ -351,7 +351,7 @@ public class Gala.MultitaskingView : ActorTarget, ActivatableComponent {
351351
unowned var manager = display.get_workspace_manager ();
352352
var scale = display.get_monitor_scale (display.get_primary_monitor ());
353353

354-
var workspace = new WorkspaceClone (manager.get_workspace_by_index (num), scale);
354+
var workspace = new WorkspaceClone (wm, manager.get_workspace_by_index (num), scale);
355355
workspaces.insert_child_at_index (workspace, num);
356356
icon_groups.add_group (workspace.icon_group);
357357

src/Widgets/MultitaskingView/WindowClone.vala

Lines changed: 74 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*
22
* SPDX-License-Identifier: GPL-3.0-or-later
3-
* SPDX-FileCopyrightText: 2022-2023 elementary, Inc. (https://elementary.io)
3+
* SPDX-FileCopyrightText: 2022-2025 elementary, Inc. (https://elementary.io)
44
* 2014 Tom Beckmann
55
*/
66

@@ -13,6 +13,7 @@ public class Gala.WindowClone : ActorTarget {
1313
private const int ACTIVE_SHAPE_SIZE = 12;
1414
private const int FADE_ANIMATION_DURATION = 200;
1515
private const int TITLE_MAX_WIDTH_MARGIN = 60;
16+
private const int CLOSE_TRANSLATION = 600;
1617

1718
/**
1819
* The window was selected. The MultitaskingView should consider activating
@@ -26,8 +27,7 @@ public class Gala.WindowClone : ActorTarget {
2627
*/
2728
public signal void request_reposition ();
2829

29-
public Meta.Display display { get; construct; }
30-
30+
public WindowManager wm { get; construct; }
3131
public Meta.Window window { get; construct; }
3232

3333
/**
@@ -84,14 +84,17 @@ public class Gala.WindowClone : ActorTarget {
8484
private ulong check_confirm_dialog_cb = 0;
8585
private bool in_slot_animation = false;
8686

87+
private Clutter.Actor clone_container;
8788
private Gala.CloseButton close_button;
8889
private ActiveShape active_shape;
8990
private Clutter.Actor window_icon;
9091
private Tooltip window_title;
9192

92-
public WindowClone (Meta.Display display, Meta.Window window, float scale, bool overview_mode = false) {
93+
private GestureController gesture_controller;
94+
95+
public WindowClone (WindowManager wm, Meta.Window window, float scale, bool overview_mode = false) {
9396
Object (
94-
display: display,
97+
wm: wm,
9598
window: window,
9699
monitor_scale_factor: scale,
97100
overview_mode: overview_mode
@@ -101,6 +104,9 @@ public class Gala.WindowClone : ActorTarget {
101104
construct {
102105
reactive = true;
103106

107+
gesture_controller = new GestureController (CLOSE_WINDOW, this, wm);
108+
gesture_controller.enable_scroll (this, VERTICAL);
109+
104110
window.unmanaged.connect (unmanaged);
105111
window.notify["fullscreen"].connect (check_shadow_requirements);
106112
window.notify["maximized-horizontally"].connect (check_shadow_requirements);
@@ -126,13 +132,18 @@ public class Gala.WindowClone : ActorTarget {
126132
add_action (drag_action);
127133
}
128134

129-
window_title = new Tooltip ();
130-
window_title.opacity = 0;
131-
132135
active_shape = new ActiveShape ();
133136
active_shape.opacity = 0;
134137

138+
clone_container = new Clutter.Actor () {
139+
pivot_point = { 0.5f, 0.5f }
140+
};
141+
142+
window_title = new Tooltip ();
143+
window_title.opacity = 0;
144+
135145
add_child (active_shape);
146+
add_child (clone_container);
136147
add_child (window_title);
137148

138149
reallocate ();
@@ -183,13 +194,7 @@ public class Gala.WindowClone : ActorTarget {
183194
}
184195

185196
clone = new Clutter.Clone (actor);
186-
clone.set_content_scaling_filters (TRILINEAR, TRILINEAR);
187-
add_child (clone);
188-
189-
set_child_below_sibling (active_shape, clone);
190-
set_child_above_sibling (close_button, clone);
191-
set_child_above_sibling (window_icon, clone);
192-
set_child_above_sibling (window_title, clone);
197+
clone_container.add_child (clone);
193198

194199
check_shadow_requirements ();
195200
}
@@ -269,31 +274,61 @@ public class Gala.WindowClone : ActorTarget {
269274
update_hover_widgets (true);
270275
}
271276

277+
public override void update_progress (Gala.GestureAction action, double progress) {
278+
if (action != CLOSE_WINDOW || slot == null || !Meta.Prefs.get_gnome_animations ()) {
279+
return;
280+
}
281+
282+
var target_translation_y = (float) (-CLOSE_TRANSLATION * monitor_scale_factor * progress);
283+
var target_opacity = (uint) (255 * (1 - progress));
284+
285+
clone_container.translation_y = target_translation_y;
286+
clone_container.opacity = target_opacity;
287+
288+
window_icon.translation_y = target_translation_y;
289+
window_icon.opacity = target_opacity;
290+
291+
window_title.translation_y = target_translation_y;
292+
window_title.opacity = target_opacity;
293+
294+
close_button.translation_y = target_translation_y;
295+
close_button.opacity = target_opacity;
296+
}
297+
272298
public override void end_progress (GestureAction action) {
273299
update_hover_widgets (false);
300+
301+
if (action == CLOSE_WINDOW && get_current_commit (CLOSE_WINDOW) > 0.5 && Meta.Prefs.get_gnome_animations ()) {
302+
close_window (Meta.CURRENT_TIME);
303+
}
274304
}
275305

276306
public override void allocate (Clutter.ActorBox box) {
277307
base.allocate (box);
278308

309+
var input_rect = window.get_buffer_rect ();
310+
var outer_rect = window.get_frame_rect ();
311+
var clone_scale_factor = width / outer_rect.width;
312+
313+
// Compensate for invisible borders of the texture
314+
float clone_x = (input_rect.x - outer_rect.x) * clone_scale_factor;
315+
float clone_y = (input_rect.y - outer_rect.y) * clone_scale_factor;
316+
317+
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);
318+
clone_container.allocate (clone_container_alloc);
319+
279320
if (clone == null || (drag_action != null && drag_action.dragging)) {
280321
return;
281322
}
282323

283-
var input_rect = window.get_buffer_rect ();
284-
var outer_rect = window.get_frame_rect ();
285-
var clone_scale_factor = width / outer_rect.width;
324+
unowned var display = wm.get_display ();
286325

287326
clone.set_scale (clone_scale_factor, clone_scale_factor);
288327

289328
float clone_width, clone_height;
290329
clone.get_preferred_size (null, null, out clone_width, out clone_height);
291330

292-
// Compensate for invisible borders of the texture
293-
float clone_x = (input_rect.x - outer_rect.x) * clone_scale_factor;
294-
float clone_y = (input_rect.y - outer_rect.y) * clone_scale_factor;
295-
296-
var clone_alloc = InternalUtils.actor_box_from_rect (clone_x, clone_y, clone_width, clone_height);
331+
var clone_alloc = InternalUtils.actor_box_from_rect (0, 0, clone_width, clone_height);
297332
clone.allocate (clone_alloc);
298333

299334
Clutter.ActorBox shape_alloc = {
@@ -380,15 +415,17 @@ public class Gala.WindowClone : ActorTarget {
380415
}
381416

382417
private void check_confirm_dialog (int monitor, Meta.Window new_window) {
383-
if (new_window.get_transient_for () == window) {
384-
Idle.add (() => {
418+
Idle.add (() => {
419+
if (new_window.get_transient_for () == window) {
420+
gesture_controller.goto (0.0);
385421
selected ();
386-
return Source.REMOVE;
387-
});
388422

389-
SignalHandler.disconnect (window.get_display (), check_confirm_dialog_cb);
390-
check_confirm_dialog_cb = 0;
391-
}
423+
SignalHandler.disconnect (window.get_display (), check_confirm_dialog_cb);
424+
check_confirm_dialog_cb = 0;
425+
}
426+
427+
return Source.REMOVE;
428+
});
392429
}
393430

394431
/**
@@ -417,7 +454,7 @@ public class Gala.WindowClone : ActorTarget {
417454
if (button == Clutter.Button.PRIMARY) {
418455
selected ();
419456
} else if (button == Clutter.Button.MIDDLE && device_type == POINTER_DEVICE) {
420-
close_window (display.get_current_time ());
457+
close_window (wm.get_display ().get_current_time ());
421458
}
422459
}
423460

@@ -476,7 +513,7 @@ public class Gala.WindowClone : ActorTarget {
476513
close_button.opacity = 0;
477514
window_title.opacity = 0;
478515

479-
display.set_cursor (Meta.Cursor.DND_IN_DRAG);
516+
wm.get_display ().set_cursor (Meta.Cursor.DND_IN_DRAG);
480517

481518
return this;
482519
}
@@ -529,7 +566,7 @@ public class Gala.WindowClone : ActorTarget {
529566
}
530567
}
531568

532-
display.set_cursor (hovered ? Meta.Cursor.DND_MOVE: Meta.Cursor.DND_IN_DRAG);
569+
wm.get_display ().set_cursor (hovered ? Meta.Cursor.DND_MOVE: Meta.Cursor.DND_IN_DRAG);
533570
}
534571

535572
/**
@@ -538,8 +575,10 @@ public class Gala.WindowClone : ActorTarget {
538575
* otherwise we cancel the drag and animate back to our old place.
539576
*/
540577
private void drag_end (Clutter.Actor destination) {
578+
unowned var display = wm.get_display ();
579+
541580
Meta.Workspace workspace = null;
542-
var primary = window.get_display ().get_primary_monitor ();
581+
var primary = display.get_primary_monitor ();
543582

544583
active_shape.show ();
545584

@@ -628,7 +667,7 @@ public class Gala.WindowClone : ActorTarget {
628667

629668
request_reposition ();
630669

631-
display.set_cursor (Meta.Cursor.DEFAULT);
670+
wm.get_display ().set_cursor (Meta.Cursor.DEFAULT);
632671

633672
if (duration > 0) {
634673
ulong handler = 0;

0 commit comments

Comments
 (0)