From 1ce9a80d3053b59a817ce189b7e839ab0085df45 Mon Sep 17 00:00:00 2001 From: Leonhard Kargl Date: Fri, 14 Nov 2025 19:20:28 +0100 Subject: [PATCH 1/9] Introduce Focusable --- lib/Focusable/FocusController.vala | 73 ++++++++++++++++++++ lib/Focusable/FocusUtils.vala | 83 ++++++++++++++++++++++ lib/Focusable/Focusable.vala | 106 +++++++++++++++++++++++++++++ lib/meson.build | 3 + 4 files changed, 265 insertions(+) create mode 100644 lib/Focusable/FocusController.vala create mode 100644 lib/Focusable/FocusUtils.vala create mode 100644 lib/Focusable/Focusable.vala diff --git a/lib/Focusable/FocusController.vala b/lib/Focusable/FocusController.vala new file mode 100644 index 000000000..1202904c5 --- /dev/null +++ b/lib/Focusable/FocusController.vala @@ -0,0 +1,73 @@ +/* + * Copyright 2025 elementary, Inc. (https://elementary.io) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Authored by: Leonhard Kargl + */ + +public class Gala.FocusController : Clutter.Action { + internal static Quark focus_visible_quark = Quark.from_string ("gala-focus-visible"); + + private uint timeout_id = 0; + + public Clutter.Stage stage { get; construct; } + + public FocusController (Clutter.Stage stage) { + Object (stage: stage); + } + + construct { + // In the case the key focus moves out of our focusable tree by some other means + // make sure we can recapture it + stage.key_press_event.connect (check_focus); + } + + public override bool handle_event (Clutter.Event event) { + return event.get_type () != KEY_PRESS ? Clutter.EVENT_PROPAGATE : check_focus (event); + } + + private bool check_focus (Clutter.Event event) requires ( + actor is Focusable && !(actor.get_parent () is Focusable) // Make sure we are only attached to root focusables + ) { + var direction = FocusDirection.get_for_event (event); + + if (direction == null) { + return Clutter.EVENT_PROPAGATE; + } + + if (!((Focusable) actor).focus (direction)) { +#if HAS_MUTTER47 + stage.context.get_backend ().get_default_seat ().bell_notify (); +#else + Clutter.get_default_backend ().get_default_seat ().bell_notify (); +#endif + + if (!(stage.key_focus in actor)) { + stage.key_focus = actor; + } + } + + show_focus (); + + return Clutter.EVENT_STOP; + } + + private void show_focus () { + if (timeout_id != 0) { + Source.remove (timeout_id); + } else { + set_focus_visible (true); + } + + timeout_id = Timeout.add_seconds (5, () => { + set_focus_visible (false); + timeout_id = 0; + return Source.REMOVE; + }); + } + + private void set_focus_visible (bool visible) { + actor.set_qdata (focus_visible_quark, visible); + (stage.key_focus as Focusable)?.focus_changed (); + } +} diff --git a/lib/Focusable/FocusUtils.vala b/lib/Focusable/FocusUtils.vala new file mode 100644 index 000000000..72ea30498 --- /dev/null +++ b/lib/Focusable/FocusUtils.vala @@ -0,0 +1,83 @@ +/* + * Copyright 2025 elementary, Inc. (https://elementary.io) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Authored by: Leonhard Kargl + */ + +public enum Gala.FocusDirection { + UP, + DOWN, + LEFT, + RIGHT; + + public bool is_forward () { + return this == DOWN || this == RIGHT; + } + + public static FocusDirection? get_for_event (Clutter.Event event) { + switch (event.get_key_symbol ()) { + case Clutter.Key.Up: return UP; + case Clutter.Key.Down: return DOWN; + case Clutter.Key.Left: return LEFT; + case Clutter.Key.Right: return RIGHT; + } + + return null; + } +} + +namespace Gala.FocusUtils { + public void filter_children_for_direction (Gee.List children, Clutter.Actor focus_actor, FocusDirection direction) { + Focusable? focus_child = null; + foreach (var child in children) { + if (focus_actor in child) { + focus_child = (Focusable) child; + break; + } + } + + var to_retain = new Gee.LinkedList (); + to_retain.add_all_iterator (children.filter ((c) => { + if (focus_child == null || c == focus_child) { + return true; + } + + var focus_rect = get_allocation_rect (focus_child); + var rect = get_allocation_rect (c); + + if ((direction == UP || direction == DOWN) && !rect.horiz_overlap (focus_rect) || + (direction == LEFT || direction == RIGHT) && !rect.vert_overlap (focus_rect) + ) { + return false; + } + + return ( + direction == UP && rect.y + rect.height <= focus_rect.y || + direction == DOWN && rect.y >= focus_rect.y + focus_rect.height || + direction == LEFT && rect.x + rect.width <= focus_rect.x || + direction == RIGHT && rect.x >= focus_rect.x + focus_rect.width + ); + })); + + children.retain_all (to_retain); + } + + private inline Mtk.Rectangle get_allocation_rect (Clutter.Actor actor) { + return {(int) actor.x, (int) actor.y, (int) actor.width, (int) actor.height}; + } + + public void sort_children_for_direction (Gee.List children, FocusDirection direction) { + children.sort ((a, b) => { + if (direction == UP && a.y + a.height > b.y + b.height || + direction == DOWN && a.y < b.y || + direction == LEFT && a.x + a.width > b.x + b.width || + direction == RIGHT && a.x < b.x + ) { + return -1; + } + + return 1; + }); + } +} diff --git a/lib/Focusable/Focusable.vala b/lib/Focusable/Focusable.vala new file mode 100644 index 000000000..480e751e2 --- /dev/null +++ b/lib/Focusable/Focusable.vala @@ -0,0 +1,106 @@ +/* + * Copyright 2025 elementary, Inc. (https://elementary.io) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Authored by: Leonhard Kargl + */ + +public class Gala.Focusable : Clutter.Actor { + public bool can_focus { get; set; default = false; } + public bool has_visible_focus { get; private set; default = false; } + + construct { + key_focus_in.connect (focus_changed); + key_focus_out.connect (focus_changed); + } + + internal void focus_changed () { + has_visible_focus = has_key_focus () && get_root ().get_qdata (FocusController.focus_visible_quark); + } + + private Focusable get_root () { + var parent = get_parent (); + if (parent is Focusable) { + return parent.get_root (); + } + + return this; + } + + public bool focus (FocusDirection direction) { + var focus_actor = get_stage ().get_key_focus (); + + // We have focus so try to move it to a child + if (focus_actor == this) { + if (direction.is_forward ()) { + return move_focus (direction); + } + + return false; + } + + // A child of us (or subchild) has focus, try to move it to the next one. + // If that doesn't work and we are moving backwards focus us + if (focus_actor != null && focus_actor is Focusable && focus_actor in this) { + if (move_focus (direction)) { + return true; + } + + if (direction.is_forward ()) { + return false; + } else { + return grab_focus (); + } + } + + // Focus is outside of us, try to take it + if (direction.is_forward ()) { + if (grab_focus ()) { + return true; + } + + return move_focus (direction); + } else { + if (move_focus (direction)) { + return true; + } + + return grab_focus (); + } + } + + private bool grab_focus () { + if (!can_focus) { + return false; + } + + grab_key_focus (); + + return true; + } + + protected virtual bool move_focus (FocusDirection direction) { + var children = get_focusable_children (); + + FocusUtils.filter_children_for_direction (children, get_stage ().key_focus, direction); + FocusUtils.sort_children_for_direction (children, direction); + + foreach (var child in children) { + if (child.focus (direction)) { + return true; + } + } + + return false; + } + + private Gee.List get_focusable_children () { + var focusable_children = new Gee.ArrayList (); + for (var child = get_first_child (); child != null; child = child.get_next_sibling ()) { + if (child is Focusable && child.visible) { + focusable_children.add ((Focusable) child); + } + } + return focusable_children; + } +} diff --git a/lib/meson.build b/lib/meson.build index 737db46f1..2a5fa2a1c 100644 --- a/lib/meson.build +++ b/lib/meson.build @@ -26,6 +26,9 @@ gala_lib_sources = files( 'Utils.vala', 'WindowIcon.vala', 'WindowManager.vala', + 'Focusable/Focusable.vala', + 'Focusable/FocusController.vala', + 'Focusable/FocusUtils.vala', 'Gestures/ActorTarget.vala', 'Gestures/Gesture.vala', 'Gestures/GestureBackend.vala', From e414215cfd08a10560819d5238298a1e7dc5cf68 Mon Sep 17 00:00:00 2001 From: Leonhard Kargl Date: Thu, 20 Nov 2025 19:16:07 +0100 Subject: [PATCH 2/9] ActorTarget: Inherit from Focusable --- lib/Gestures/ActorTarget.vala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Gestures/ActorTarget.vala b/lib/Gestures/ActorTarget.vala index d847221b2..43e25984e 100644 --- a/lib/Gestures/ActorTarget.vala +++ b/lib/Gestures/ActorTarget.vala @@ -10,7 +10,7 @@ * It will propagate gesture events to all direct descendants that are also {@link ActorTarget}s. * If a new child (or target via {@link add_target}) is added, its progress will be synced. */ -public class Gala.ActorTarget : Clutter.Actor, GestureTarget { +public class Gala.ActorTarget : Focusable, GestureTarget { public bool animating { get { return ongoing_animations > 0; } } private double[] current_progress; From 56cd4beaf9d9a1cfdd093309bc1f2737ed5f6613 Mon Sep 17 00:00:00 2001 From: Leonhard Kargl Date: Thu, 20 Nov 2025 19:23:37 +0100 Subject: [PATCH 3/9] WindowClone(Container): Rely on Focusable --- src/Widgets/MultitaskingView/WindowClone.vala | 23 ++- .../WindowCloneContainer.vala | 168 +----------------- 2 files changed, 11 insertions(+), 180 deletions(-) diff --git a/src/Widgets/MultitaskingView/WindowClone.vala b/src/Widgets/MultitaskingView/WindowClone.vala index 951a8720a..bc180fcd0 100644 --- a/src/Widgets/MultitaskingView/WindowClone.vala +++ b/src/Widgets/MultitaskingView/WindowClone.vala @@ -42,19 +42,6 @@ public class Gala.WindowClone : ActorTarget, RootTarget { */ public Mtk.Rectangle? slot { get; private set; default = null; } - /** - * When active fades a white border around the window in. Used for the visually - * indicating the WindowCloneContainer's current_window. - */ - public bool active { - set { - active_shape.save_easing_state (); - active_shape.set_easing_duration (Utils.get_animation_duration (FADE_ANIMATION_DURATION)); - active_shape.opacity = value ? 255 : 0; - active_shape.restore_easing_state (); - } - } - public Mode mode { get; construct; } public float monitor_scale { get; construct set; } @@ -98,6 +85,9 @@ public class Gala.WindowClone : ActorTarget, RootTarget { construct { reactive = true; + can_focus = true; + + notify["has-visible-focus"].connect (on_visible_focus_changed); gesture_controller = new GestureController (CUSTOM); gesture_controller.add_trigger (new SwipeTrigger (this, VERTICAL)); @@ -179,6 +169,13 @@ public class Gala.WindowClone : ActorTarget, RootTarget { finish_drag (); } + private void on_visible_focus_changed () { + active_shape.save_easing_state (); + active_shape.set_easing_duration (Utils.get_animation_duration (FADE_ANIMATION_DURATION)); + active_shape.opacity = has_visible_focus ? 255 : 0; + active_shape.restore_easing_state (); + } + private void reallocate () { window_icon = new WindowIcon (window, WINDOW_ICON_SIZE, (int)Math.round (monitor_scale)) { visible = mode != SINGLE_APP_OVERVIEW diff --git a/src/Widgets/MultitaskingView/WindowCloneContainer.vala b/src/Widgets/MultitaskingView/WindowCloneContainer.vala index 1e9791cda..59c5f3daf 100644 --- a/src/Widgets/MultitaskingView/WindowCloneContainer.vala +++ b/src/Widgets/MultitaskingView/WindowCloneContainer.vala @@ -23,12 +23,6 @@ public class Gala.WindowCloneContainer : ActorTarget { private bool opened = false; - /** - * 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; - public WindowCloneContainer ( WindowManager wm, WindowListModel windows, float monitor_scale, WindowClone.Mode window_clone_mode = MULTITASKING_VIEW @@ -67,16 +61,6 @@ public class Gala.WindowCloneContainer : ActorTarget { insert_child_at_index (clone, i); } - // Make sure we release the reference on the window - if (current_window != null && current_window.window in to_remove) { - select_next_window (RIGHT, false); - - // There is no next window so select nothing - if (current_window.window in to_remove) { - current_window = null; - } - } - // Don't reflow if only the sorting changed if (to_remove.size () > 0 || added != removed) { reflow (false); @@ -87,14 +71,10 @@ public class Gala.WindowCloneContainer : ActorTarget { 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; + clone.grab_key_focus (); break; } } @@ -155,152 +135,6 @@ public class Gala.WindowCloneContainer : ActorTarget { } } - /** - * Collect key events, mainly for redirecting them to the WindowCloneContainers to - * select the active window. - */ - public override bool key_press_event (Clutter.Event event) { - if (!opened) { - return Clutter.EVENT_PROPAGATE; - } - - switch (event.get_key_symbol ()) { - case Clutter.Key.Escape: - requested_close (); - break; - case Clutter.Key.Down: - select_next_window (Meta.MotionDirection.DOWN, true); - break; - case Clutter.Key.Up: - select_next_window (Meta.MotionDirection.UP, true); - break; - case Clutter.Key.Left: - select_next_window (Meta.MotionDirection.LEFT, true); - break; - case Clutter.Key.Right: - select_next_window (Meta.MotionDirection.RIGHT, true); - break; - case Clutter.Key.Return: - case Clutter.Key.KP_Enter: - if (current_window == null) { - requested_close (); - } else { - window_selected (current_window.window); - } - break; - } - - return Clutter.EVENT_STOP; - } - - /** - * 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 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 () == 0) { - return; - } - - WindowClone? closest = null; - - if (current_window == null) { - closest = (WindowClone) get_child_at_index (0); - } else { - var current_rect = current_window.slot; - - foreach (unowned var clone in (GLib.List) get_children ()) { - if (clone == current_window) { - continue; - } - - var window_rect = clone.slot; - - if (window_rect == null) { - continue; - } - - if (direction == LEFT) { - if (window_rect.x > current_rect.x) { - continue; - } - - // test for vertical intersection - if (window_rect.y + window_rect.height > current_rect.y - && window_rect.y < current_rect.y + current_rect.height) { - - if (closest == null || closest.slot.x < window_rect.x) { - closest = clone; - } - } - } else if (direction == RIGHT) { - if (window_rect.x < current_rect.x) { - continue; - } - - // test for vertical intersection - if (window_rect.y + window_rect.height > current_rect.y - && window_rect.y < current_rect.y + current_rect.height) { - - if (closest == null || closest.slot.x > window_rect.x) { - closest = clone; - } - } - } else if (direction == UP) { - if (window_rect.y > current_rect.y) { - continue; - } - - // test for horizontal intersection - if (window_rect.x + window_rect.width > current_rect.x - && window_rect.x < current_rect.x + current_rect.width) { - - if (closest == null || closest.slot.y < window_rect.y) { - closest = clone; - } - } - } else if (direction == DOWN) { - if (window_rect.y < current_rect.y) { - continue; - } - - // test for horizontal intersection - if (window_rect.x + window_rect.width > current_rect.x - && window_rect.x < current_rect.x + current_rect.width) { - - if (closest == null || closest.slot.y > window_rect.y) { - closest = clone; - } - } - } else { - warning ("Invalid direction"); - break; - } - } - } - - if (closest == null) { - if (current_window != null && user_action) { - InternalUtils.bell_notify (wm.get_display ()); - current_window.active = true; - } - return; - } - - if (current_window != null) { - current_window.active = false; - } - - if (user_action) { - closest.active = true; - } - - current_window = closest; - } - // Code ported from KWin present windows effect // https://projects.kde.org/projects/kde/kde-workspace/repository/revisions/master/entry/kwin/effects/presentwindows/presentwindows.cpp From 06a8e821355855770a07cec5ee625113c17c8b61 Mon Sep 17 00:00:00 2001 From: Leonhard Kargl Date: Mon, 17 Nov 2025 14:12:48 +0100 Subject: [PATCH 4/9] Select WindowClone on enter --- src/Widgets/MultitaskingView/WindowClone.vala | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Widgets/MultitaskingView/WindowClone.vala b/src/Widgets/MultitaskingView/WindowClone.vala index bc180fcd0..aba26a83b 100644 --- a/src/Widgets/MultitaskingView/WindowClone.vala +++ b/src/Widgets/MultitaskingView/WindowClone.vala @@ -372,6 +372,15 @@ public class Gala.WindowClone : ActorTarget, RootTarget { window_title.allocate (window_title_alloc); } + public override bool key_press_event (Clutter.Event event) { + if (event.get_key_symbol () == Clutter.Key.Return || event.get_key_symbol () == Clutter.Key.KP_Enter) { + selected (); + return Clutter.EVENT_STOP; + } + + return Clutter.EVENT_PROPAGATE; + } + /** * Send the window the delete signal and listen for new windows to be added * to the window's workspace, in which case we check if the new window is a From d2f4da92c137362a512abd9e9beb58f0f76fbde2 Mon Sep 17 00:00:00 2001 From: Leonhard Kargl Date: Thu, 20 Nov 2025 19:18:26 +0100 Subject: [PATCH 5/9] MultitaskingView: Use FocusController --- .../MultitaskingView/MultitaskingView.vala | 37 +++++-------------- 1 file changed, 10 insertions(+), 27 deletions(-) diff --git a/src/Widgets/MultitaskingView/MultitaskingView.vala b/src/Widgets/MultitaskingView/MultitaskingView.vala index ac0a76642..630964551 100644 --- a/src/Widgets/MultitaskingView/MultitaskingView.vala +++ b/src/Widgets/MultitaskingView/MultitaskingView.vala @@ -59,6 +59,8 @@ public class Gala.MultitaskingView : ActorTarget, RootTarget, ActivatableCompone opened = false; display = wm.get_display (); + add_action (new FocusController (wm.stage)); + multitasking_gesture_controller = new GestureController (MULTITASKING_VIEW); multitasking_gesture_controller.add_trigger (new GlobalTrigger (MULTITASKING_VIEW, wm)); add_gesture_controller (multitasking_gesture_controller); @@ -249,7 +251,6 @@ public class Gala.MultitaskingView : ActorTarget, RootTarget, ActivatableCompone wm.window_group.hide (); wm.top_window_group.hide (); show (); - grab_key_focus (); modal_proxy = wm.push_modal (get_stage (), false); modal_proxy.allow_actions (MULTITASKING_VIEW | SWITCH_WORKSPACE | ZOOM | LOCATE_POINTER | MEDIA_KEYS | SCREENSHOT | SCREENSHOT_AREA); @@ -370,34 +371,16 @@ public class Gala.MultitaskingView : ActorTarget, RootTarget, ActivatableCompone } } - /** - * Collect key events, mainly for redirecting them to the WindowCloneContainers to - * select the active window. - */ public override bool key_press_event (Clutter.Event event) { - if (!opened) { - return Clutter.EVENT_PROPAGATE; - } - - return get_active_window_clone_container ().key_press_event (event); - } - - /** - * Finds the active WorkspaceClone - * - * @return The active WorkspaceClone - */ - private WindowCloneContainer get_active_window_clone_container () { - unowned var manager = display.get_workspace_manager (); - unowned var active_workspace = manager.get_active_workspace (); - foreach (unowned var child in workspaces.get_children ()) { - unowned var workspace_clone = (WorkspaceClone) child; - if (workspace_clone.workspace == active_workspace) { - return workspace_clone.window_container; - } + switch (event.get_key_symbol ()) { + case Clutter.Key.Escape: + case Clutter.Key.Return: + case Clutter.Key.KP_Enter: + close (); + return Clutter.EVENT_STOP; + default: + return Clutter.EVENT_PROPAGATE; } - - assert_not_reached (); } private void window_selected (Meta.Window window) { From 9bb8108ed243875786e44596350b2d76ce907e82 Mon Sep 17 00:00:00 2001 From: Leonhard Kargl Date: Thu, 20 Nov 2025 19:12:33 +0100 Subject: [PATCH 6/9] WindowOverview: Use FocusController --- src/Widgets/WindowOverview.vala | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/Widgets/WindowOverview.vala b/src/Widgets/WindowOverview.vala index 128157729..ec3710cb9 100644 --- a/src/Widgets/WindowOverview.vala +++ b/src/Widgets/WindowOverview.vala @@ -18,7 +18,6 @@ public class Gala.WindowOverview : ActorTarget, RootTarget, ActivatableComponent private ModalProxy modal_proxy; // the workspaces which we expose right now private List workspaces; - private WindowCloneContainer window_clone_container; private uint64[]? window_ids = null; @@ -33,15 +32,20 @@ public class Gala.WindowOverview : ActorTarget, RootTarget, ActivatableComponent enabled = false }; add_gesture_controller (gesture_controller); - } + add_action (new FocusController (wm.stage)); + } public override bool key_press_event (Clutter.Event event) { - if (!is_opened ()) { - return Clutter.EVENT_PROPAGATE; + switch (event.get_key_symbol ()) { + case Clutter.Key.Escape: + case Clutter.Key.Return: + case Clutter.Key.KP_Enter: + close (); + return Clutter.EVENT_STOP; + default: + return Clutter.EVENT_PROPAGATE; } - - return window_clone_container.key_press_event (event); } public override bool button_release_event (Clutter.Event event) { @@ -120,7 +124,7 @@ public class Gala.WindowOverview : ActorTarget, RootTarget, ActivatableComponent var model = new WindowListModel (display, STACKING, true, i, null, custom_filter); model.items_changed.connect (on_items_changed); - window_clone_container = new WindowCloneContainer (wm, model, scale, mode) { + var window_clone_container = new WindowCloneContainer (wm, model, scale, mode) { padding_top = TOP_GAP, padding_left = BORDER, padding_right = BORDER, From 0f0df94c5caadb302ea38dd289b4b45503ed223c Mon Sep 17 00:00:00 2001 From: Leonhard Kargl Date: Thu, 20 Nov 2025 19:30:51 +0100 Subject: [PATCH 7/9] WorkspaceRow: Allow focus only within currently active ws --- src/Widgets/MultitaskingView/WorkspaceRow.vala | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Widgets/MultitaskingView/WorkspaceRow.vala b/src/Widgets/MultitaskingView/WorkspaceRow.vala index 1398a2361..4d7668cac 100644 --- a/src/Widgets/MultitaskingView/WorkspaceRow.vala +++ b/src/Widgets/MultitaskingView/WorkspaceRow.vala @@ -42,6 +42,11 @@ public class Gala.WorkspaceRow : ActorTarget { } } + public override bool move_focus (FocusDirection direction) { + var focusable = (Focusable) get_child_at_index ((int) (-get_current_commit (SWITCH_WORKSPACE))); + return focusable.focus (direction); + } + private void update_order () { for (var child = get_first_child (); child != null; child = child.get_next_sibling ()) { unowned var workspace_clone = (WorkspaceClone) child; From 9eea7a1071d95cf0ebe5a27203c368e80dfbcb30 Mon Sep 17 00:00:00 2001 From: Leonhard Kargl Date: Thu, 20 Nov 2025 19:32:35 +0100 Subject: [PATCH 8/9] WindowCloneContainer: Drop requested_close --- src/Widgets/MultitaskingView/WindowCloneContainer.vala | 1 - src/Widgets/MultitaskingView/WorkspaceClone.vala | 1 - src/Widgets/WindowOverview.vala | 1 - 3 files changed, 3 deletions(-) diff --git a/src/Widgets/MultitaskingView/WindowCloneContainer.vala b/src/Widgets/MultitaskingView/WindowCloneContainer.vala index 59c5f3daf..da8320fe2 100644 --- a/src/Widgets/MultitaskingView/WindowCloneContainer.vala +++ b/src/Widgets/MultitaskingView/WindowCloneContainer.vala @@ -9,7 +9,6 @@ */ public class Gala.WindowCloneContainer : ActorTarget { public signal void window_selected (Meta.Window window); - public signal void requested_close (); public int padding_top { get; set; default = 12; } public int padding_left { get; set; default = 12; } diff --git a/src/Widgets/MultitaskingView/WorkspaceClone.vala b/src/Widgets/MultitaskingView/WorkspaceClone.vala index d57b17d1c..f46a259bf 100644 --- a/src/Widgets/MultitaskingView/WorkspaceClone.vala +++ b/src/Widgets/MultitaskingView/WorkspaceClone.vala @@ -149,7 +149,6 @@ public class Gala.WorkspaceClone : ActorTarget { height = monitor_geometry.height, }; window_container.window_selected.connect ((window) => window_selected (window)); - window_container.requested_close.connect (() => activate (true)); bind_property ("monitor-scale", window_container, "monitor-scale"); var background_drop_action = new DragDropAction (DragDropActionType.DESTINATION, "multitaskingview-window"); diff --git a/src/Widgets/WindowOverview.vala b/src/Widgets/WindowOverview.vala index ec3710cb9..8a50f6f9e 100644 --- a/src/Widgets/WindowOverview.vala +++ b/src/Widgets/WindowOverview.vala @@ -135,7 +135,6 @@ public class Gala.WindowOverview : ActorTarget, RootTarget, ActivatableComponent y = geometry.y, }; window_clone_container.window_selected.connect (thumb_selected); - window_clone_container.requested_close.connect (() => close ()); add_child (window_clone_container); } From 31a0d8f1976f541ce64d078222487008a808ba77 Mon Sep 17 00:00:00 2001 From: Leonhard Kargl Date: Fri, 30 Jan 2026 19:54:57 +0100 Subject: [PATCH 9/9] Rename Focusable to Widget --- lib/Gestures/ActorTarget.vala | 2 +- .../FocusController.vala | 8 ++++---- lib/{Focusable => Widget}/FocusUtils.vala | 10 +++++----- .../Focusable.vala => Widget/Widget.vala} | 20 +++++++++---------- lib/meson.build | 8 ++++---- .../MultitaskingView/MonitorClone.vala | 2 +- .../MultitaskingView/MultitaskingView.vala | 6 +++--- .../MultitaskingView/StaticWindowClone.vala | 2 +- .../StaticWindowContainer.vala | 2 +- src/Widgets/MultitaskingView/WindowClone.vala | 2 +- .../WindowCloneContainer.vala | 2 +- .../MultitaskingView/WorkspaceClone.vala | 2 +- .../MultitaskingView/WorkspaceRow.vala | 6 +++--- src/Widgets/WindowOverview.vala | 2 +- 14 files changed, 37 insertions(+), 37 deletions(-) rename lib/{Focusable => Widget}/FocusController.vala (85%) rename lib/{Focusable => Widget}/FocusUtils.vala (85%) rename lib/{Focusable/Focusable.vala => Widget/Widget.vala} (82%) diff --git a/lib/Gestures/ActorTarget.vala b/lib/Gestures/ActorTarget.vala index 43e25984e..d847221b2 100644 --- a/lib/Gestures/ActorTarget.vala +++ b/lib/Gestures/ActorTarget.vala @@ -10,7 +10,7 @@ * It will propagate gesture events to all direct descendants that are also {@link ActorTarget}s. * If a new child (or target via {@link add_target}) is added, its progress will be synced. */ -public class Gala.ActorTarget : Focusable, GestureTarget { +public class Gala.ActorTarget : Clutter.Actor, GestureTarget { public bool animating { get { return ongoing_animations > 0; } } private double[] current_progress; diff --git a/lib/Focusable/FocusController.vala b/lib/Widget/FocusController.vala similarity index 85% rename from lib/Focusable/FocusController.vala rename to lib/Widget/FocusController.vala index 1202904c5..7bd96dbeb 100644 --- a/lib/Focusable/FocusController.vala +++ b/lib/Widget/FocusController.vala @@ -17,7 +17,7 @@ public class Gala.FocusController : Clutter.Action { } construct { - // In the case the key focus moves out of our focusable tree by some other means + // In the case the key focus moves out of our widget tree by some other means // make sure we can recapture it stage.key_press_event.connect (check_focus); } @@ -27,7 +27,7 @@ public class Gala.FocusController : Clutter.Action { } private bool check_focus (Clutter.Event event) requires ( - actor is Focusable && !(actor.get_parent () is Focusable) // Make sure we are only attached to root focusables + actor is Widget && !(actor.get_parent () is Widget) // Make sure we are only attached to root widgets ) { var direction = FocusDirection.get_for_event (event); @@ -35,7 +35,7 @@ public class Gala.FocusController : Clutter.Action { return Clutter.EVENT_PROPAGATE; } - if (!((Focusable) actor).focus (direction)) { + if (!((Widget) actor).focus (direction)) { #if HAS_MUTTER47 stage.context.get_backend ().get_default_seat ().bell_notify (); #else @@ -68,6 +68,6 @@ public class Gala.FocusController : Clutter.Action { private void set_focus_visible (bool visible) { actor.set_qdata (focus_visible_quark, visible); - (stage.key_focus as Focusable)?.focus_changed (); + (stage.key_focus as Widget)?.focus_changed (); } } diff --git a/lib/Focusable/FocusUtils.vala b/lib/Widget/FocusUtils.vala similarity index 85% rename from lib/Focusable/FocusUtils.vala rename to lib/Widget/FocusUtils.vala index 72ea30498..19c43d7b0 100644 --- a/lib/Focusable/FocusUtils.vala +++ b/lib/Widget/FocusUtils.vala @@ -28,16 +28,16 @@ public enum Gala.FocusDirection { } namespace Gala.FocusUtils { - public void filter_children_for_direction (Gee.List children, Clutter.Actor focus_actor, FocusDirection direction) { - Focusable? focus_child = null; + public void filter_children_for_direction (Gee.List children, Clutter.Actor focus_actor, FocusDirection direction) { + Widget? focus_child = null; foreach (var child in children) { if (focus_actor in child) { - focus_child = (Focusable) child; + focus_child = (Widget) child; break; } } - var to_retain = new Gee.LinkedList (); + var to_retain = new Gee.LinkedList (); to_retain.add_all_iterator (children.filter ((c) => { if (focus_child == null || c == focus_child) { return true; @@ -67,7 +67,7 @@ namespace Gala.FocusUtils { return {(int) actor.x, (int) actor.y, (int) actor.width, (int) actor.height}; } - public void sort_children_for_direction (Gee.List children, FocusDirection direction) { + public void sort_children_for_direction (Gee.List children, FocusDirection direction) { children.sort ((a, b) => { if (direction == UP && a.y + a.height > b.y + b.height || direction == DOWN && a.y < b.y || diff --git a/lib/Focusable/Focusable.vala b/lib/Widget/Widget.vala similarity index 82% rename from lib/Focusable/Focusable.vala rename to lib/Widget/Widget.vala index 480e751e2..5303a8cf9 100644 --- a/lib/Focusable/Focusable.vala +++ b/lib/Widget/Widget.vala @@ -5,7 +5,7 @@ * Authored by: Leonhard Kargl */ -public class Gala.Focusable : Clutter.Actor { +public class Gala.Widget : ActorTarget { public bool can_focus { get; set; default = false; } public bool has_visible_focus { get; private set; default = false; } @@ -18,9 +18,9 @@ public class Gala.Focusable : Clutter.Actor { has_visible_focus = has_key_focus () && get_root ().get_qdata (FocusController.focus_visible_quark); } - private Focusable get_root () { + private Widget get_root () { var parent = get_parent (); - if (parent is Focusable) { + if (parent is Widget) { return parent.get_root (); } @@ -41,7 +41,7 @@ public class Gala.Focusable : Clutter.Actor { // A child of us (or subchild) has focus, try to move it to the next one. // If that doesn't work and we are moving backwards focus us - if (focus_actor != null && focus_actor is Focusable && focus_actor in this) { + if (focus_actor != null && focus_actor is Widget && focus_actor in this) { if (move_focus (direction)) { return true; } @@ -80,7 +80,7 @@ public class Gala.Focusable : Clutter.Actor { } protected virtual bool move_focus (FocusDirection direction) { - var children = get_focusable_children (); + var children = get_widget_children (); FocusUtils.filter_children_for_direction (children, get_stage ().key_focus, direction); FocusUtils.sort_children_for_direction (children, direction); @@ -94,13 +94,13 @@ public class Gala.Focusable : Clutter.Actor { return false; } - private Gee.List get_focusable_children () { - var focusable_children = new Gee.ArrayList (); + private Gee.List get_widget_children () { + var widget_children = new Gee.ArrayList (); for (var child = get_first_child (); child != null; child = child.get_next_sibling ()) { - if (child is Focusable && child.visible) { - focusable_children.add ((Focusable) child); + if (child is Widget && child.visible) { + widget_children.add ((Widget) child); } } - return focusable_children; + return widget_children; } } diff --git a/lib/meson.build b/lib/meson.build index 2a5fa2a1c..65895af2a 100644 --- a/lib/meson.build +++ b/lib/meson.build @@ -26,9 +26,6 @@ gala_lib_sources = files( 'Utils.vala', 'WindowIcon.vala', 'WindowManager.vala', - 'Focusable/Focusable.vala', - 'Focusable/FocusController.vala', - 'Focusable/FocusUtils.vala', 'Gestures/ActorTarget.vala', 'Gestures/Gesture.vala', 'Gestures/GestureBackend.vala', @@ -44,7 +41,10 @@ gala_lib_sources = files( 'Gestures/SwipeTrigger.vala', 'Gestures/ToucheggBackend.vala', 'Gestures/TouchpadBackend.vala', - 'Gestures/WorkspaceHideTracker.vala' + 'Gestures/WorkspaceHideTracker.vala', + 'Widget/FocusController.vala', + 'Widget/FocusUtils.vala', + 'Widget/Widget.vala', ) + gala_common_enums gala_resources = gnome.compile_resources( diff --git a/src/Widgets/MultitaskingView/MonitorClone.vala b/src/Widgets/MultitaskingView/MonitorClone.vala index ea84338ee..266b56fb2 100644 --- a/src/Widgets/MultitaskingView/MonitorClone.vala +++ b/src/Widgets/MultitaskingView/MonitorClone.vala @@ -11,7 +11,7 @@ * as the WindowGroup is hidden while the view is active. Only used when * workspaces-only-on-primary is set to true. */ -public class Gala.MonitorClone : ActorTarget { +public class Gala.MonitorClone : Widget { public signal void window_selected (Meta.Window window); public WindowManager wm { get; construct; } diff --git a/src/Widgets/MultitaskingView/MultitaskingView.vala b/src/Widgets/MultitaskingView/MultitaskingView.vala index 630964551..27954bd3b 100644 --- a/src/Widgets/MultitaskingView/MultitaskingView.vala +++ b/src/Widgets/MultitaskingView/MultitaskingView.vala @@ -20,7 +20,7 @@ * preparing the wm, opening the components and holds containers for * the icon groups, the WorkspaceClones and the MonitorClones. */ -public class Gala.MultitaskingView : ActorTarget, RootTarget, ActivatableComponent { +public class Gala.MultitaskingView : Widget, RootTarget, ActivatableComponent { public const int ANIMATION_DURATION = 250; private GestureController workspaces_gesture_controller; @@ -35,7 +35,7 @@ public class Gala.MultitaskingView : ActorTarget, RootTarget, ActivatableCompone private List window_containers_monitors; - private ActorTarget workspaces; + private Widget workspaces; private Clutter.Actor primary_monitor_container; private Clutter.BrightnessContrastEffect brightness_effect; private BackgroundManager? blurred_bg = null; @@ -84,7 +84,7 @@ public class Gala.MultitaskingView : ActorTarget, RootTarget, ActivatableCompone // 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 = new Widget (); primary_monitor_container.add_child (workspaces); add_child (primary_monitor_container); diff --git a/src/Widgets/MultitaskingView/StaticWindowClone.vala b/src/Widgets/MultitaskingView/StaticWindowClone.vala index 56b0c744d..b616a60b0 100644 --- a/src/Widgets/MultitaskingView/StaticWindowClone.vala +++ b/src/Widgets/MultitaskingView/StaticWindowClone.vala @@ -10,7 +10,7 @@ * windows (e.g. on all workspaces or moving) and fades out while the multitasking view * is being opened. */ -public class Gala.StaticWindowClone : ActorTarget { +public class Gala.StaticWindowClone : Widget { public Meta.Window window { get; construct; } public StaticWindowClone (Meta.Window window) { diff --git a/src/Widgets/MultitaskingView/StaticWindowContainer.vala b/src/Widgets/MultitaskingView/StaticWindowContainer.vala index 28c16a121..047dff01f 100644 --- a/src/Widgets/MultitaskingView/StaticWindowContainer.vala +++ b/src/Widgets/MultitaskingView/StaticWindowContainer.vala @@ -9,7 +9,7 @@ * The window container use this to know whether a window became static (they shouldn't show it anymore) * or isn't static anymore (they have to show it now). */ -public class Gala.StaticWindowContainer : ActorTarget { +public class Gala.StaticWindowContainer : Widget { private static GLib.Once instance; public static StaticWindowContainer get_instance (Meta.Display display) { return instance.once (() => new StaticWindowContainer (display)); diff --git a/src/Widgets/MultitaskingView/WindowClone.vala b/src/Widgets/MultitaskingView/WindowClone.vala index aba26a83b..506439c5b 100644 --- a/src/Widgets/MultitaskingView/WindowClone.vala +++ b/src/Widgets/MultitaskingView/WindowClone.vala @@ -8,7 +8,7 @@ * A container for a clone of the texture of a MetaWindow, a WindowIcon, a Tooltip with the title, * a close button and a shadow. Used together with the WindowCloneContainer. */ -public class Gala.WindowClone : ActorTarget, RootTarget { +public class Gala.WindowClone : Widget, RootTarget { public enum Mode { MULTITASKING_VIEW, OVERVIEW, diff --git a/src/Widgets/MultitaskingView/WindowCloneContainer.vala b/src/Widgets/MultitaskingView/WindowCloneContainer.vala index da8320fe2..23e6f4f5c 100644 --- a/src/Widgets/MultitaskingView/WindowCloneContainer.vala +++ b/src/Widgets/MultitaskingView/WindowCloneContainer.vala @@ -7,7 +7,7 @@ /** * Container which controls the layout of a set of WindowClones. */ -public class Gala.WindowCloneContainer : ActorTarget { +public class Gala.WindowCloneContainer : Widget { public signal void window_selected (Meta.Window window); public int padding_top { get; set; default = 12; } diff --git a/src/Widgets/MultitaskingView/WorkspaceClone.vala b/src/Widgets/MultitaskingView/WorkspaceClone.vala index f46a259bf..eadc5ab53 100644 --- a/src/Widgets/MultitaskingView/WorkspaceClone.vala +++ b/src/Widgets/MultitaskingView/WorkspaceClone.vala @@ -91,7 +91,7 @@ private class Gala.FramedBackground : BackgroundManager { * The latter is not added to the WorkspaceClone itself though but to a container * of the MultitaskingView. */ -public class Gala.WorkspaceClone : ActorTarget { +public class Gala.WorkspaceClone : Widget { /** * The offset of the scaled background to the bottom of the monitor bounds */ diff --git a/src/Widgets/MultitaskingView/WorkspaceRow.vala b/src/Widgets/MultitaskingView/WorkspaceRow.vala index 4d7668cac..d9e746ab0 100644 --- a/src/Widgets/MultitaskingView/WorkspaceRow.vala +++ b/src/Widgets/MultitaskingView/WorkspaceRow.vala @@ -5,7 +5,7 @@ * Authored by: Leonhard Kargl */ -public class Gala.WorkspaceRow : ActorTarget { +public class Gala.WorkspaceRow : Widget { public const int WORKSPACE_GAP = 24; public Meta.Display display { get; construct; } @@ -43,8 +43,8 @@ public class Gala.WorkspaceRow : ActorTarget { } public override bool move_focus (FocusDirection direction) { - var focusable = (Focusable) get_child_at_index ((int) (-get_current_commit (SWITCH_WORKSPACE))); - return focusable.focus (direction); + var widget = (Widget) get_child_at_index ((int) (-get_current_commit (SWITCH_WORKSPACE))); + return widget.focus (direction); } private void update_order () { diff --git a/src/Widgets/WindowOverview.vala b/src/Widgets/WindowOverview.vala index 8a50f6f9e..7081d8f0b 100644 --- a/src/Widgets/WindowOverview.vala +++ b/src/Widgets/WindowOverview.vala @@ -5,7 +5,7 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -public class Gala.WindowOverview : ActorTarget, RootTarget, ActivatableComponent { +public class Gala.WindowOverview : Widget, RootTarget, ActivatableComponent { private const int BORDER = 10; private const int TOP_GAP = 30; private const int BOTTOM_GAP = 100;