diff --git a/src/InternalUtils.vala b/src/InternalUtils.vala index 05eb6c05e..6fa27d2fc 100644 --- a/src/InternalUtils.vala +++ b/src/InternalUtils.vala @@ -91,179 +91,6 @@ namespace Gala { workspace_manager.thaw_remove (); } - // Code ported from KWin present windows effect - // https://projects.kde.org/projects/kde/kde-workspace/repository/revisions/master/entry/kwin/effects/presentwindows/presentwindows.cpp - - // some math utilities - private static int squared_distance (Gdk.Point a, Gdk.Point b) { - var k1 = b.x - a.x; - var k2 = b.y - a.y; - - return k1 * k1 + k2 * k2; - } - - private static Mtk.Rectangle rect_adjusted (Mtk.Rectangle rect, int dx1, int dy1, int dx2, int dy2) { - return {rect.x + dx1, rect.y + dy1, rect.width + (-dx1 + dx2), rect.height + (-dy1 + dy2)}; - } - - private static Gdk.Point rect_center (Mtk.Rectangle rect) { - return {rect.x + rect.width / 2, rect.y + rect.height / 2}; - } - - public struct TilableWindow { - Mtk.Rectangle rect; - unowned WindowClone id; - } - - /** - * Careful: List windows will be modified in place and shouldn't be used afterwards. - */ - public static List calculate_grid_placement (Mtk.Rectangle area, List windows) { - uint window_count = windows.length (); - int columns = (int)Math.ceil (Math.sqrt (window_count)); - int rows = (int)Math.ceil (window_count / (double)columns); - - // Assign slots - int slot_width = area.width / columns; - int slot_height = area.height / rows; - - TilableWindow?[] taken_slots = {}; - taken_slots.resize (rows * columns); - - // precalculate all slot centers - Gdk.Point[] slot_centers = {}; - slot_centers.resize (rows * columns); - for (int x = 0; x < columns; x++) { - for (int y = 0; y < rows; y++) { - slot_centers[x + y * columns] = { - area.x + slot_width * x + slot_width / 2, - area.y + slot_height * y + slot_height / 2 - }; - } - } - - // Assign each window to the closest available slot - while (windows.length () > 0) { - unowned List link = windows.nth (0); - var window = link.data; - var rect = window.rect; - - var slot_candidate = -1; - var slot_candidate_distance = int.MAX; - var pos = rect_center (rect); - - // all slots - for (int i = 0; i < columns * rows; i++) { - if (i > window_count - 1) - break; - - var dist = squared_distance (pos, slot_centers[i]); - - if (dist < slot_candidate_distance) { - // window is interested in this slot - var occupier = taken_slots[i]; - if (occupier == window) - continue; - - if (occupier == null || dist < squared_distance (rect_center (occupier.rect), slot_centers[i])) { - // either nobody lives here, or we're better - takeover the slot if it's our best - slot_candidate = i; - slot_candidate_distance = dist; - } - } - } - - if (slot_candidate == -1) - continue; - - if (taken_slots[slot_candidate] != null) - windows.prepend (taken_slots[slot_candidate]); - - windows.remove_link (link); - taken_slots[slot_candidate] = window; - } - - var result = new List (); - - // see how many windows we have on the last row - int left_over = (int)window_count - columns * (rows - 1); - - for (int slot = 0; slot < columns * rows; slot++) { - var window = taken_slots[slot]; - // some slots might be empty - if (window == null) - continue; - - var rect = window.rect; - - // Work out where the slot is - Mtk.Rectangle target = { - area.x + (slot % columns) * slot_width, - area.y + (slot / columns) * slot_height, - slot_width, - slot_height - }; - target = rect_adjusted (target, 10, 10, -10, -10); - - float scale; - if (target.width / (double)rect.width < target.height / (double)rect.height) { - // Center vertically - scale = target.width / (float)rect.width; - target.y += (target.height - (int)(rect.height * scale)) / 2; - target.height = (int)Math.floorf (rect.height * scale); - } else { - // Center horizontally - scale = target.height / (float)rect.height; - target.x += (target.width - (int)(rect.width * scale)) / 2; - target.width = (int)Math.floorf (rect.width * scale); - } - - // Don't scale the windows too much - if (scale > 1.0) { - scale = 1.0f; - target = {rect_center (target).x - (int)Math.floorf (rect.width * scale) / 2, - rect_center (target).y - (int)Math.floorf (rect.height * scale) / 2, - (int)Math.floorf (scale * rect.width), - (int)Math.floorf (scale * rect.height)}; - } - - // put the last row in the center, if necessary - if (left_over != columns && slot >= columns * (rows - 1)) - target.x += (columns - left_over) * slot_width / 2; - - result.prepend ({ target, window.id }); - } - - result.reverse (); - return result; - } - - /* - * Sorts the windows by stacking order so that the window on active workspaces come first. - */ - public static SList sort_windows (Meta.Display display, List windows) { - var windows_on_active_workspace = new SList (); - var windows_on_other_workspaces = new SList (); - unowned var active_workspace = display.get_workspace_manager ().get_active_workspace (); - foreach (unowned var window in windows) { - if (window.get_workspace () == active_workspace) { - windows_on_active_workspace.append (window); - } else { - windows_on_other_workspaces.append (window); - } - } - - var sorted_windows = new SList (); - var windows_on_active_workspace_sorted = display.sort_windows_by_stacking (windows_on_active_workspace); - windows_on_active_workspace_sorted.reverse (); - var windows_on_other_workspaces_sorted = display.sort_windows_by_stacking (windows_on_other_workspaces); - windows_on_other_workspaces_sorted.reverse (); - sorted_windows.concat ((owned) windows_on_active_workspace_sorted); - sorted_windows.concat ((owned) windows_on_other_workspaces_sorted); - - return sorted_windows; - } - public static inline bool get_window_is_normal (Meta.Window window) { switch (window.window_type) { case Meta.WindowType.NORMAL: diff --git a/src/Widgets/MultitaskingView/WindowCloneContainer.vala b/src/Widgets/MultitaskingView/WindowCloneContainer.vala index 9e9a67270..8adac200a 100644 --- a/src/Widgets/MultitaskingView/WindowCloneContainer.vala +++ b/src/Widgets/MultitaskingView/WindowCloneContainer.vala @@ -18,15 +18,14 @@ public class Gala.WindowCloneContainer : ActorTarget { public int padding_bottom { get; set; default = 12; } public WindowManager wm { get; construct; } - public bool overview_mode { get; construct; } - public float monitor_scale { get; construct set; } + public bool overview_mode { get; construct; } private bool opened = false; /** - * The window that is currently selected via keyboard shortcuts. It is not - * necessarily the same as the active window. + * The window that is currently selected via keyboard shortcuts. + * It is not necessarily the same as the active window. */ private unowned WindowClone? current_window = null; @@ -35,24 +34,20 @@ public class Gala.WindowCloneContainer : ActorTarget { } /** - * Create a WindowClone for a MetaWindow and add it to the group + * Create a WindowClone for a Meta.Window and add it to the group * * @param window The window for which to create the WindowClone for */ public void add_window (Meta.Window window) { var windows = new List (); - foreach (unowned var child in get_children ()) { - unowned var clone = (WindowClone) child; + windows.append (window); + foreach (unowned var clone in (GLib.List) get_children ()) { windows.append (clone.window); } - windows.append (window); - - unowned var display = wm.get_display (); - var windows_ordered = InternalUtils.sort_windows (display, windows); var new_window = new WindowClone (wm, window, monitor_scale, overview_mode); - - new_window.selected.connect ((clone) => window_selected (clone.window)); + new_window.selected.connect ((_new_window) => window_selected (_new_window.window)); + new_window.request_reposition.connect (() => reflow (false)); new_window.destroy.connect ((_new_window) => { // make sure to release reference if the window is selected if (_new_window == current_window) { @@ -66,12 +61,10 @@ public class Gala.WindowCloneContainer : ActorTarget { reflow (false); }); - new_window.request_reposition.connect (() => reflow (false)); - bind_property ("monitor-scale", new_window, "monitor-scale"); unowned Meta.Window? target = null; - foreach (unowned var w in windows_ordered) { + foreach (unowned var w in sort_windows (windows)) { if (w != window) { target = w; continue; @@ -84,8 +77,7 @@ public class Gala.WindowCloneContainer : ActorTarget { add_child (new_window); } - foreach (unowned var child in get_children ()) { - unowned var clone = (WindowClone) child; + foreach (unowned var clone in (GLib.List) get_children ()) { if (target == clone.window) { insert_child_below (new_window, clone); break; @@ -99,9 +91,9 @@ public class Gala.WindowCloneContainer : ActorTarget { * Find and remove the WindowClone for a MetaWindow */ public void remove_window (Meta.Window window) { - foreach (unowned var child in get_children ()) { - if (((WindowClone) child).window == window) { - remove_child (child); + foreach (unowned var clone in (GLib.List) get_children ()) { + if (clone.window == window) { + remove_child (clone); reflow (false); break; } @@ -112,29 +104,65 @@ public class Gala.WindowCloneContainer : ActorTarget { } } + public override void start_progress (GestureAction action) { + if (!opened) { + opened = true; + + if (current_window != null) { + current_window.active = false; + } + + unowned var focus_window = wm.get_display ().focus_window; + foreach (unowned var clone in (GLib.List) get_children ()) { + if (clone.window == focus_window) { + current_window = clone; + break; + } + } + + restack_windows (); + reflow (true); + } else if (action == MULTITASKING_VIEW) { // If we are open we only want to restack when we close + restack_windows (); + } + } + + public override void commit_progress (GestureAction action, double to) { + switch (action) { + case MULTITASKING_VIEW: + opened = to > 0.5; + break; + + case SWITCH_WORKSPACE: + opened = get_current_commit (MULTITASKING_VIEW) > 0.5; + break; + + default: + break; + } + } + /** * Sort the windows z-order by their actual stacking to make intersections * during animations correct. */ private void restack_windows () { - unowned var display = wm.get_display (); + var children = (GLib.List) get_children (); - var children = get_children (); - - var windows = new List (); - foreach (unowned Clutter.Actor child in children) { - windows.prepend (((WindowClone) child).window); + var windows = new GLib.List (); + foreach (unowned var clone in children) { + windows.prepend (clone.window); } - var windows_ordered = InternalUtils.sort_windows (display, windows); + var windows_ordered = sort_windows (windows); windows_ordered.reverse (); var i = 0; foreach (unowned var window in windows_ordered) { - foreach (unowned var child in children) { - if (((WindowClone) child).window == window) { - set_child_at_index (child, i); - children.remove (child); + foreach (unowned var clone in children) { + if (clone.window == window) { + set_child_at_index (clone, i); + children.remove (clone); i++; break; } @@ -143,45 +171,36 @@ public class Gala.WindowCloneContainer : ActorTarget { } /** - * Recalculate the tiling positions of the windows and animate them to - * the resulting spots. + * Recalculate the tiling positions of the windows and animate them to the resulting spots. */ private void reflow (bool view_toggle) { - if (!opened) { + if (!opened || get_n_children () == 0) { return; } - var windows = new List (); - foreach (unowned var child in get_children ()) { - unowned var clone = (WindowClone) child; - windows.prepend ({ clone.window.get_frame_rect (), clone }); - } - - if (windows.is_empty ()) { - return; + var windows = new GLib.List (); + foreach (unowned var clone in (GLib.List) get_children ()) { + windows.prepend ({ clone, clone.window.get_frame_rect () }); } // make sure the windows are always in the same order so the algorithm // doesn't give us different slots based on stacking order, which can lead // to windows flying around weirdly windows.sort ((a, b) => { - var seq_a = ((WindowClone) a.id).window.get_stable_sequence (); - var seq_b = ((WindowClone) b.id).window.get_stable_sequence (); + var seq_a = ((WindowClone) a.clone).window.get_stable_sequence (); + var seq_b = ((WindowClone) b.clone).window.get_stable_sequence (); return (int) (seq_b - seq_a); }); Mtk.Rectangle area = { padding_left, padding_top, - (int)width - padding_left - padding_right, - (int)height - padding_top - padding_bottom + (int) width - padding_left - padding_right, + (int) height - padding_top - padding_bottom }; - var window_positions = InternalUtils.calculate_grid_placement (area, windows); - - foreach (var tilable in window_positions) { - unowned var clone = (WindowClone) tilable.id; - clone.take_slot (tilable.rect, !view_toggle); + foreach (var tilable in calculate_grid_placement (area, windows)) { + tilable.clone.take_slot (tilable.rect, !view_toggle); } } @@ -212,8 +231,10 @@ public class Gala.WindowCloneContainer : ActorTarget { break; case Clutter.Key.Return: case Clutter.Key.KP_Enter: - if (!activate_selected_window ()) { + if (current_window == null) { requested_close (); + } else { + window_selected (current_window.window); } break; } @@ -222,18 +243,17 @@ public class Gala.WindowCloneContainer : ActorTarget { } /** - * Look for the next window in a direction and make this window the - * new current_window. Used for keyboard navigation. + * Look for the next window in a direction and make this window the new current_window. + * Used for keyboard navigation. * - * @param direction The MetaMotionDirection in which to search for windows for. + * @param direction The MetaMotionDirection in which to search for windows for. + * @param user_action Whether we must select a window and, if failed, play a bell sound. */ public void select_next_window (Meta.MotionDirection direction, bool user_action) { - if (get_n_children () < 1) { + if (get_n_children () == 0) { return; } - unowned var display = wm.get_display (); - WindowClone? closest = null; if (current_window == null) { @@ -241,12 +261,12 @@ public class Gala.WindowCloneContainer : ActorTarget { } else { var current_rect = current_window.slot; - foreach (unowned var child in get_children ()) { - if (child == current_window) { + foreach (unowned var clone in (GLib.List) get_children ()) { + if (clone == current_window) { continue; } - var window_rect = ((WindowClone) child).slot; + var window_rect = clone.slot; if (window_rect == null) { continue; @@ -262,7 +282,7 @@ public class Gala.WindowCloneContainer : ActorTarget { && window_rect.y < current_rect.y + current_rect.height) { if (closest == null || closest.slot.x < window_rect.x) { - closest = (WindowClone) child; + closest = clone; } } } else if (direction == RIGHT) { @@ -275,7 +295,7 @@ public class Gala.WindowCloneContainer : ActorTarget { && window_rect.y < current_rect.y + current_rect.height) { if (closest == null || closest.slot.x > window_rect.x) { - closest = (WindowClone) child; + closest = clone; } } } else if (direction == UP) { @@ -288,7 +308,7 @@ public class Gala.WindowCloneContainer : ActorTarget { && window_rect.x < current_rect.x + current_rect.width) { if (closest == null || closest.slot.y < window_rect.y) { - closest = (WindowClone) child; + closest = clone; } } } else if (direction == DOWN) { @@ -301,7 +321,7 @@ public class Gala.WindowCloneContainer : ActorTarget { && window_rect.x < current_rect.x + current_rect.width) { if (closest == null || closest.slot.y > window_rect.y) { - closest = (WindowClone) child; + closest = clone; } } } else { @@ -313,7 +333,7 @@ public class Gala.WindowCloneContainer : ActorTarget { if (closest == null) { if (current_window != null && user_action) { - InternalUtils.bell_notify (display); + InternalUtils.bell_notify (wm.get_display ()); current_window.active = true; } return; @@ -331,54 +351,180 @@ public class Gala.WindowCloneContainer : ActorTarget { } /** - * Emit the selected signal for the current_window. + * Sorts the windows by stacking order so that the window on active workspaces come first. */ - public bool activate_selected_window () { - if (current_window != null) { - window_selected (current_window.window); - return true; + private GLib.SList sort_windows (GLib.List windows) { + unowned var display = wm.get_display (); + + var windows_on_active_workspace = new GLib.SList (); + var windows_on_other_workspaces = new GLib.SList (); + unowned var active_workspace = display.get_workspace_manager ().get_active_workspace (); + foreach (unowned var window in windows) { + if (window.get_workspace () == active_workspace) { + windows_on_active_workspace.append (window); + } else { + windows_on_other_workspaces.append (window); + } } - return false; + var sorted_windows = new GLib.SList (); + var windows_on_active_workspace_sorted = display.sort_windows_by_stacking (windows_on_active_workspace); + windows_on_active_workspace_sorted.reverse (); + var windows_on_other_workspaces_sorted = display.sort_windows_by_stacking (windows_on_other_workspaces); + windows_on_other_workspaces_sorted.reverse (); + sorted_windows.concat ((owned) windows_on_active_workspace_sorted); + sorted_windows.concat ((owned) windows_on_other_workspaces_sorted); + + return sorted_windows; } - public override void start_progress (GestureAction action) { - if (!opened) { - opened = true; - unowned var display = wm.get_display (); + // Code ported from KWin present windows effect + // https://projects.kde.org/projects/kde/kde-workspace/repository/revisions/master/entry/kwin/effects/presentwindows/presentwindows.cpp - if (current_window != null) { - current_window.active = false; + // some math utilities + private static int squared_distance (Gdk.Point a, Gdk.Point b) { + var k1 = b.x - a.x; + var k2 = b.y - a.y; + + return k1 * k1 + k2 * k2; + } + + private static Mtk.Rectangle rect_adjusted (Mtk.Rectangle rect, int dx1, int dy1, int dx2, int dy2) { + return {rect.x + dx1, rect.y + dy1, rect.width + (-dx1 + dx2), rect.height + (-dy1 + dy2)}; + } + + private static Gdk.Point rect_center (Mtk.Rectangle rect) { + return {rect.x + rect.width / 2, rect.y + rect.height / 2}; + } + + private struct TilableWindow { + unowned WindowClone clone; + Mtk.Rectangle rect; + } + + /** + * Careful: List windows will be modified in place and shouldn't be used afterwards. + */ + private static GLib.List calculate_grid_placement (Mtk.Rectangle area, GLib.List windows) { + uint window_count = windows.length (); + int columns = (int) Math.ceil (Math.sqrt (window_count)); + int rows = (int) Math.ceil (window_count / (double) columns); + + // Assign slots + int slot_width = area.width / columns; + int slot_height = area.height / rows; + + TilableWindow?[] taken_slots = {}; + taken_slots.resize (rows * columns); + + // precalculate all slot centers + Gdk.Point[] slot_centers = {}; + slot_centers.resize (rows * columns); + for (int x = 0; x < columns; x++) { + for (int y = 0; y < rows; y++) { + slot_centers[x + y * columns] = { + area.x + slot_width * x + slot_width / 2, + area.y + slot_height * y + slot_height / 2 + }; } + } - for (var child = get_first_child (); child != null; child = child.get_next_sibling ()) { - unowned var clone = (WindowClone) child; - if (clone.window == display.focus_window) { - current_window = clone; + // Assign each window to the closest available slot + while (windows.length () > 0) { + unowned var link = windows.nth (0); + var window = link.data; + var rect = window.rect; + + var slot_candidate = -1; + var slot_candidate_distance = int.MAX; + var pos = rect_center (rect); + + // all slots + for (int i = 0; i < columns * rows; i++) { + if (i > window_count - 1) break; + + var dist = squared_distance (pos, slot_centers[i]); + + if (dist < slot_candidate_distance) { + // window is interested in this slot + var occupier = taken_slots[i]; + if (occupier == window) + continue; + + if (occupier == null || dist < squared_distance (rect_center (occupier.rect), slot_centers[i])) { + // either nobody lives here, or we're better - takeover the slot if it's our best + slot_candidate = i; + slot_candidate_distance = dist; + } } } - restack_windows (); - reflow (true); - } else if (action == MULTITASKING_VIEW) { // If we are open we only want to restack when we close - restack_windows (); + if (slot_candidate == -1) + continue; + + if (taken_slots[slot_candidate] != null) + windows.prepend (taken_slots[slot_candidate]); + + windows.remove_link (link); + taken_slots[slot_candidate] = window; } - } - public override void commit_progress (GestureAction action, double to) { - switch (action) { - case MULTITASKING_VIEW: - opened = to > 0.5; - break; + var result = new GLib.List (); - case SWITCH_WORKSPACE: - opened = get_current_commit (MULTITASKING_VIEW) > 0.5; - break; + // see how many windows we have on the last row + int left_over = (int) window_count - columns * (rows - 1); - default: - break; + for (int slot = 0; slot < columns * rows; slot++) { + var window = taken_slots[slot]; + // some slots might be empty + if (window == null) + continue; + + var rect = window.rect; + + // Work out where the slot is + Mtk.Rectangle target = { + area.x + (slot % columns) * slot_width, + area.y + (slot / columns) * slot_height, + slot_width, + slot_height + }; + target = rect_adjusted (target, 10, 10, -10, -10); + + float scale; + if (target.width / (double) rect.width < target.height / (double) rect.height) { + // Center vertically + scale = target.width / (float) rect.width; + target.y += (target.height - (int) (rect.height * scale)) / 2; + target.height = (int) Math.floorf (rect.height * scale); + } else { + // Center horizontally + scale = target.height / (float) rect.height; + target.x += (target.width - (int) (rect.width * scale)) / 2; + target.width = (int) Math.floorf (rect.width * scale); + } + + // Don't scale the windows too much + if (scale > 1.0) { + scale = 1.0f; + target = { + rect_center (target).x - (int) Math.floorf (rect.width * scale) / 2, + rect_center (target).y - (int) Math.floorf (rect.height * scale) / 2, + (int) Math.floorf (scale * rect.width), + (int) Math.floorf (scale * rect.height) + }; + } + + // put the last row in the center, if necessary + if (left_over != columns && slot >= columns * (rows - 1)) + target.x += (columns - left_over) * slot_width / 2; + + result.prepend ({ window.clone, target }); } + + result.reverse (); + return result; } }