diff --git a/data/Application.css b/data/Application.css index 379e8968..01559df3 100644 --- a/data/Application.css +++ b/data/Application.css @@ -172,3 +172,9 @@ backgrounditem .close-button { .running-indicator:disabled { color: @fg_color; } + +dock-button:focus-visible { + background: alpha(@accent_color, 0.5); + border-radius: 6px; + outline: 1px solid alpha(@accent_color, 0.8); +} diff --git a/src/AppSystem/Background/BackgroundItem.vala b/src/AppSystem/Background/BackgroundItem.vala index d547b964..be6b5a35 100644 --- a/src/AppSystem/Background/BackgroundItem.vala +++ b/src/AppSystem/Background/BackgroundItem.vala @@ -75,7 +75,7 @@ public class Dock.BackgroundItem : BaseIconGroup { } }); - gesture_click.released.connect (popover_menu.popup); + button.clicked.connect (popover_menu.popup); } private Gtk.Widget create_widget_func (Object obj) { diff --git a/src/AppSystem/Launcher.vala b/src/AppSystem/Launcher.vala index 05c93ae3..0ffb1396 100644 --- a/src/AppSystem/Launcher.vala +++ b/src/AppSystem/Launcher.vala @@ -126,10 +126,14 @@ public class Dock.Launcher : BaseItem { transition_type = CROSSFADE }; - overlay.child = image; + var overlay = new Gtk.Overlay () { + child = image + }; overlay.add_overlay (badge_container); overlay.add_overlay (progress_revealer); + button.child = overlay; + var running_indicator = new Gtk.Image.from_icon_name ("pager-checked-symbolic"); running_indicator.add_css_class ("running-indicator"); @@ -240,8 +244,8 @@ public class Dock.Launcher : BaseItem { easing = EASE_IN_OUT_QUAD }; - gesture_click.button = 0; - gesture_click.released.connect (on_click_released); + button.mouse_button = 0; + button.clicked.connect (on_clicked); var long_press = new Gtk.GestureLongPress () { touch_only = true @@ -320,12 +324,12 @@ public class Dock.Launcher : BaseItem { remove_dnd_cycle (); } - private void on_click_released (int n_press, double x, double y) { - var event_display = gesture_click.get_current_event ().get_display (); - var context = event_display.get_app_launch_context (); - context.set_timestamp (gesture_click.get_current_event_time ()); + private void on_clicked (uint button, uint32 timestamp) { + unowned var display = Gdk.Display.get_default (); + var context = display.get_app_launch_context (); + context.set_timestamp (timestamp); - switch (gesture_click.get_current_button ()) { + switch (button) { case Gdk.BUTTON_PRIMARY: app.launch (context); break; @@ -334,7 +338,7 @@ public class Dock.Launcher : BaseItem { animate_launch (); } else { animate_shake (); - event_display.beep (); + display.beep (); } break; case Gdk.BUTTON_SECONDARY: @@ -349,7 +353,7 @@ public class Dock.Launcher : BaseItem { return; } - bounce_up.value_to = -0.5 * overlay.get_height (); + bounce_up.value_to = -0.5 * button.get_height (); bounce_down.value_from = bounce_up.value_to; bounce_up.play (); @@ -360,7 +364,7 @@ public class Dock.Launcher : BaseItem { return; } - shake.value_to = -0.1 * overlay.get_width (); + shake.value_to = -0.1 * button.get_width (); shake.play (); int repeat_count = 0; diff --git a/src/BaseIconGroup.vala b/src/BaseIconGroup.vala index 6e086d17..89913284 100644 --- a/src/BaseIconGroup.vala +++ b/src/BaseIconGroup.vala @@ -18,6 +18,7 @@ public abstract class Dock.BaseIconGroup : ContainerItem { selection_mode = NONE, halign = CENTER, valign = CENTER, + can_focus = false, }; flow_box.bind_model (slice, create_flow_box_child); diff --git a/src/BaseItem.vala b/src/BaseItem.vala index 95437092..118aeae1 100644 --- a/src/BaseItem.vala +++ b/src/BaseItem.vala @@ -48,7 +48,7 @@ public class Dock.BaseItem : Gtk.Box { bin.height_request = -1; } - overlay.visible = !value; + button.visible = !value; } } @@ -70,8 +70,7 @@ public class Dock.BaseItem : Gtk.Box { * It's needed because top margin messes with dnd offsets and gsk transform. */ protected Gtk.Box actionable_box; - protected Gtk.Overlay overlay; - protected Gtk.GestureClick gesture_click; + protected Button button; protected Granite.Bin bin { get; private set; } @@ -92,11 +91,11 @@ public class Dock.BaseItem : Gtk.Box { construct { orientation = VERTICAL; - overlay = new Gtk.Overlay (); + button = new Button (); - // We need the bin because we need the animation to run even if the overlay is not visible + // We need the bin because we need the animation to run even if the button is not visible bin = new Granite.Bin () { - child = overlay + child = button }; actionable_box = new Gtk.Box (VERTICAL, 0); @@ -185,9 +184,6 @@ public class Dock.BaseItem : Gtk.Box { add_controller (motion_controller); - gesture_click = new Gtk.GestureClick (); - add_controller (gesture_click); - if (group == NONE) { return; } @@ -214,6 +210,10 @@ public class Dock.BaseItem : Gtk.Box { popover_tooltip.dispose (); } + public override bool grab_focus () { + return button.grab_focus (); + } + public void set_revealed (bool revealed) { fade.skip (); reveal.skip (); @@ -281,7 +281,7 @@ public class Dock.BaseItem : Gtk.Box { } private void on_drag_begin (Gtk.DragSource drag_source, Gdk.Drag drag) { - var paintable = new Gtk.WidgetPaintable (overlay); + var paintable = new Gtk.WidgetPaintable (button); drag_source.set_icon (paintable.get_current_image (), drag_offset_x, drag_offset_y); moving = true; diff --git a/src/Button.vala b/src/Button.vala new file mode 100644 index 00000000..e5bf6026 --- /dev/null +++ b/src/Button.vala @@ -0,0 +1,46 @@ +/* + * SPDX-License-Identifier: GPL-3.0 + * SPDX-FileCopyrightText: 2026 elementary, Inc. (https://elementary.io) + */ + +public class Dock.Button : Granite.Bin { + public signal void clicked (uint button, uint32 timestamp); + + public uint mouse_button { get; set; default = 1; } + + class construct { + set_accessible_role (BUTTON); + set_css_name ("dock-button"); + } + + construct { + focusable = true; + + var gesture_click = new Gtk.GestureClick (); + bind_property ("mouse-button", gesture_click, "button", SYNC_CREATE); + gesture_click.released.connect (on_released); + add_controller (gesture_click); + + var key_controller = new Gtk.EventControllerKey (); + key_controller.key_pressed.connect (on_key_pressed); + add_controller (key_controller); + } + + private void on_released (Gtk.GestureClick gesture_click, int n_press, double x, double y) { + clicked (gesture_click.get_current_button (), gesture_click.get_current_event_time ()); + } + + private bool on_key_pressed ( + Gtk.EventControllerKey key_controller, + uint keyval, + uint keycode, + Gdk.ModifierType state + ) { + if (keyval == Gdk.Key.space || keyval == Gdk.Key.Return || keyval == Gdk.Key.KP_Enter) { + clicked (Gdk.BUTTON_PRIMARY, key_controller.get_current_event_time ()); + return true; + } + + return false; + } +} diff --git a/src/ContainerItem.vala b/src/ContainerItem.vala index 4442ea74..08469538 100644 --- a/src/ContainerItem.vala +++ b/src/ContainerItem.vala @@ -18,7 +18,7 @@ public abstract class Dock.ContainerItem : BaseItem { bind_property ("icon-size", container, "width-request", SYNC_CREATE); bind_property ("icon-size", container, "height-request", SYNC_CREATE); - overlay.child = container; + button.child = container; notify["state"].connect (() => { if ((state != HIDDEN) && !moving) { diff --git a/src/ItemGroup.vala b/src/ItemGroup.vala index 1e3e5e86..ccfe9765 100644 --- a/src/ItemGroup.vala +++ b/src/ItemGroup.vala @@ -174,6 +174,10 @@ } } + public BaseItem? get_first_item () { + return (BaseItem?) current_children.get_item (0); + } + public uint get_index_for_item (BaseItem item) { uint index; if (current_children.find (item, out index)) { diff --git a/src/ItemManager.vala b/src/ItemManager.vala index f48074d4..d358ec86 100644 --- a/src/ItemManager.vala +++ b/src/ItemManager.vala @@ -8,20 +8,24 @@ public Launcher? added_launcher { get; set; default = null; } + private ItemGroup app_group; + private ItemGroup background_group; #if WORKSPACE_SWITCHER private Gtk.Separator separator; + private ItemGroup workspaces_group; private DynamicWorkspaceIcon dynamic_workspace_item; #endif + private bool focus_was_moved = false; static construct { settings = new Settings ("io.elementary.dock"); } construct { - var app_group = new ItemGroup (AppSystem.get_default ().apps, (obj) => new Launcher ((App) obj)); + app_group = new ItemGroup (AppSystem.get_default ().apps, (obj) => new Launcher ((App) obj)); var background_item = new BackgroundItem (); - var background_group = new ItemGroup (background_item.group_model, (obj) => (BackgroundItem) obj); + background_group = new ItemGroup (background_item.group_model, (obj) => (BackgroundItem) obj); #if WORKSPACE_SWITCHER separator = new Gtk.Separator (VERTICAL) { @@ -33,6 +37,8 @@ separator_box.append (new TopMargin ()); separator_box.append (separator); + workspaces_group = new ItemGroup (WorkspaceSystem.get_default ().workspaces, (obj) => new WorkspaceIconGroup ((Workspace) obj)); + dynamic_workspace_item = new DynamicWorkspaceIcon (); #endif @@ -40,11 +46,15 @@ append (background_group); #if WORKSPACE_SWITCHER append (separator_box); - append (new ItemGroup (WorkspaceSystem.get_default ().workspaces, (obj) => new WorkspaceIconGroup ((Workspace) obj))); + append (workspaces_group); append (dynamic_workspace_item); #endif overflow = VISIBLE; + app_group.items.items_changed.connect (on_group_items_changed); + background_group.items.items_changed.connect (on_group_items_changed); + workspaces_group.items.items_changed.connect (on_group_items_changed); + var drop_target_file = new Gtk.DropTarget (typeof (File), COPY) { preload = true }; @@ -151,6 +161,27 @@ }); } + private void on_group_items_changed () { + if (focus_was_moved) { + return; + } + + var item_to_focus = (BaseItem) ( + app_group.get_first_item () ?? + background_group.get_first_item () ?? + workspaces_group.get_first_item () ?? + dynamic_workspace_item + ); + + item_to_focus.grab_focus (); + } + + public override bool focus (Gtk.DirectionType direction) { + focus_was_moved = true; + + return base.focus (direction); + } + public void move_launcher_after (BaseItem source, int target_index) { if (source is Launcher) { AppSystem.get_default ().reorder_app (source.app, target_index); diff --git a/src/WorkspaceSystem/DynamicWorkspaceItem.vala b/src/WorkspaceSystem/DynamicWorkspaceItem.vala index 9cac739f..f9030693 100644 --- a/src/WorkspaceSystem/DynamicWorkspaceItem.vala +++ b/src/WorkspaceSystem/DynamicWorkspaceItem.vala @@ -43,8 +43,8 @@ public class Dock.DynamicWorkspaceIcon : ContainerItem, WorkspaceItem { null, null ); - gesture_click.button = Gdk.BUTTON_PRIMARY; - gesture_click.released.connect (switch_to_new_workspace); + button.mouse_button = Gdk.BUTTON_PRIMARY; + button.clicked.connect (switch_to_new_workspace); } private void update_active_state () { diff --git a/src/WorkspaceSystem/WorkspaceIconGroup.vala b/src/WorkspaceSystem/WorkspaceIconGroup.vala index 1243d060..ae9606b9 100644 --- a/src/WorkspaceSystem/WorkspaceIconGroup.vala +++ b/src/WorkspaceSystem/WorkspaceIconGroup.vala @@ -38,8 +38,8 @@ public class Dock.WorkspaceIconGroup : BaseIconGroup, WorkspaceItem { return true; }); - gesture_click.button = Gdk.BUTTON_PRIMARY; - gesture_click.released.connect (workspace.activate); + button.mouse_button = Gdk.BUTTON_PRIMARY; + button.clicked.connect (workspace.activate); } public void window_entered (Window window) { diff --git a/src/meson.build b/src/meson.build index 51ab51dd..6550c73d 100644 --- a/src/meson.build +++ b/src/meson.build @@ -3,6 +3,7 @@ sources = [ 'BaseIconGroup.vala', 'BaseItem.vala', 'BottomMargin.vala', + 'Button.vala', 'ContainerItem.vala', 'ItemGroup.vala', 'ItemManager.vala',