From 0f8b6ee8076d27c192131848000b017c7cf42876 Mon Sep 17 00:00:00 2001 From: lenemter Date: Wed, 12 Aug 2026 17:10:55 +0300 Subject: [PATCH 1/5] Make dock scrollable instead of extending off screen --- src/ItemManager.vala | 30 +++++++++++++++++++++++------- src/MainWindow.vala | 30 ++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/src/ItemManager.vala b/src/ItemManager.vala index 56408603..956e243a 100644 --- a/src/ItemManager.vala +++ b/src/ItemManager.vala @@ -8,10 +8,9 @@ public Launcher? added_launcher { get; set; default = null; } - private DynamicWorkspaceIcon dynamic_workspace_item; - #if WORKSPACE_SWITCHER private Gtk.Separator separator; + private DynamicWorkspaceIcon dynamic_workspace_item; #endif static construct { @@ -21,25 +20,42 @@ construct { var app_group = new ItemGroup (AppSystem.get_default ().apps, (obj) => new Launcher ((App) obj)); + var app_group_scrolled = new Gtk.ScrolledWindow () { + child = app_group, + vscrollbar_policy = NEVER, + propagate_natural_width = true + }; + var background_item = new BackgroundItem (); var background_group = new ItemGroup (background_item.group_model, (obj) => (BackgroundItem) obj); #if WORKSPACE_SWITCHER - dynamic_workspace_item = new DynamicWorkspaceIcon (); - separator = new Gtk.Separator (VERTICAL) { valign = START, margin_top = Launcher.PADDING, }; settings.bind ("icon-size", separator, "height-request", GET); + + var workspace_group = new ItemGroup (WorkspaceSystem.get_default ().workspaces, (obj) => new WorkspaceIconGroup ((Workspace) obj)); + + dynamic_workspace_item = new DynamicWorkspaceIcon (); + + var workspace_box = new Gtk.Box (HORIZONTAL, 0); + workspace_box.append (workspace_group); + workspace_box.append (dynamic_workspace_item); + + var workspace_group_scrolled = new Gtk.ScrolledWindow () { + child = workspace_box, + vscrollbar_policy = NEVER, + propagate_natural_width = true + }; #endif - append (app_group); + append (app_group_scrolled); append (background_group); #if WORKSPACE_SWITCHER append (separator); - append (new ItemGroup (WorkspaceSystem.get_default ().workspaces, (obj) => new WorkspaceIconGroup ((Workspace) obj))); - append (dynamic_workspace_item); + append (workspace_group_scrolled); #endif overflow = VISIBLE; diff --git a/src/MainWindow.vala b/src/MainWindow.vala index 6642a679..42525109 100644 --- a/src/MainWindow.vala +++ b/src/MainWindow.vala @@ -190,4 +190,34 @@ public class Dock.MainWindow : Gtk.ApplicationWindow { xdisplay.change_property (window, prop, X.XA_STRING, 8, 0, (uchar[]) value, value.length); } } + + public override void measure ( + Gtk.Orientation orientation, + int for_size, + out int minimum, + out int natural, + out int minimum_baseline, + out int natural_baseline + ) { + if (orientation != HORIZONTAL) { + base.measure (orientation, for_size, out minimum, out natural, out minimum_baseline, out natural_baseline); + return; + } + + minimum = minimum_baseline = natural_baseline = -1; + + var monitor_width = int.MAX; + unowned var surface = get_surface (); + if (surface != null) { + unowned var monitor = Gdk.Display.get_default ().get_monitor_at_surface (surface); + if (monitor != null) { + monitor_width = monitor.geometry.width; + } + } + + int item_manager_natural_width; + item_manager.measure (HORIZONTAL, -1, null, out item_manager_natural_width, null, null); + + natural = int.min (item_manager_natural_width, monitor_width - 2 * BottomMargin.get_size ()); + } } From 607529f0b83e95f39506aec7b6db4d4974d736e8 Mon Sep 17 00:00:00 2001 From: lenemter Date: Wed, 12 Aug 2026 19:32:02 +0300 Subject: [PATCH 2/5] Change the way we deal with icon clipping --- data/Application.css | 5 +++-- src/BaseItem.vala | 3 ++- src/BottomMargin.vala | 2 +- src/ItemManager.vala | 6 +++++- src/MainWindow.vala | 8 +++----- src/TopMargin.vala | 12 ++++++++++++ src/meson.build | 1 + 7 files changed, 27 insertions(+), 10 deletions(-) create mode 100644 src/TopMargin.vala diff --git a/data/Application.css b/data/Application.css index bfd5c8a1..7ff08e60 100644 --- a/data/Application.css +++ b/data/Application.css @@ -32,8 +32,9 @@ dock-window:not(.reduce-transparency) separator.vertical { border-right-color: alpha(@highlight_color, 0.15); } -dock-window { - margin-top: 64px; /* Keep enough room so that icons don't clip when bouncing */ +/* Keep enough room so that icons don't clip when bouncing */ +top-margin { + min-height: 64px; } bottom-margin { diff --git a/src/BaseItem.vala b/src/BaseItem.vala index d27f212d..ef92f109 100644 --- a/src/BaseItem.vala +++ b/src/BaseItem.vala @@ -94,6 +94,7 @@ public class Dock.BaseItem : Gtk.Box { child = overlay }; + append (new TopMargin ()); append (bin); append (new BottomMargin ()); @@ -137,7 +138,7 @@ public class Dock.BaseItem : Gtk.Box { Granite.TRANSITION_DURATION_OPEN, new Adw.CallbackAnimationTarget ((val) => { bin.allocate (icon_size, icon_size, -1, - new Gsk.Transform ().translate (Graphene.Point () { y = (float) val } + new Gsk.Transform ().translate (Graphene.Point () { y = TopMargin.SIZE + (float) val } )); }) ); diff --git a/src/BottomMargin.vala b/src/BottomMargin.vala index bf326ea0..ad2d1195 100644 --- a/src/BottomMargin.vala +++ b/src/BottomMargin.vala @@ -3,7 +3,7 @@ * SPDX-FileCopyrightText: 2025 elementary, Inc. (https://elementary.io) */ -public class BottomMargin : Gtk.Widget { +public class Dock.BottomMargin : Gtk.Widget { private static GLib.List instances = new GLib.List (); class construct { diff --git a/src/ItemManager.vala b/src/ItemManager.vala index 956e243a..5ffc22b5 100644 --- a/src/ItemManager.vala +++ b/src/ItemManager.vala @@ -36,6 +36,10 @@ }; settings.bind ("icon-size", separator, "height-request", GET); + var separator_box = new Gtk.Box (VERTICAL, 0); + separator_box.append (new TopMargin ()); + separator_box.append (separator); + var workspace_group = new ItemGroup (WorkspaceSystem.get_default ().workspaces, (obj) => new WorkspaceIconGroup ((Workspace) obj)); dynamic_workspace_item = new DynamicWorkspaceIcon (); @@ -54,7 +58,7 @@ append (app_group_scrolled); append (background_group); #if WORKSPACE_SWITCHER - append (separator); + append (separator_box); append (workspace_group_scrolled); #endif overflow = VISIBLE; diff --git a/src/MainWindow.vala b/src/MainWindow.vala index 42525109..f7a227fa 100644 --- a/src/MainWindow.vala +++ b/src/MainWindow.vala @@ -14,9 +14,6 @@ public class Dock.MainWindow : Gtk.ApplicationWindow { } } - // Matches top margin in Launcher.css - private const int TOP_MARGIN = 64; - private Settings transparency_settings; private static Settings settings = new Settings ("io.elementary.dock"); @@ -38,6 +35,7 @@ public class Dock.MainWindow : Gtk.ApplicationWindow { titlebar = new Gtk.Label ("") { visible = false }; var dock_box = new Gtk.Box (VERTICAL, 0); + dock_box.append (new TopMargin ()); dock_box.append (new Container ()); dock_box.append (new BottomMargin ()); @@ -115,7 +113,7 @@ public class Dock.MainWindow : Gtk.ApplicationWindow { // bouncing isn't added by default and instead counts to the frame var item_manager_width = item_manager.get_width (); var shadow_size = (surface.width - item_manager_width) / 2; - var top_margin = TOP_MARGIN + shadow_size - 1; + var top_margin = TopMargin.SIZE + shadow_size - 1; size.set_shadow_width (shadow_size, shadow_size, top_margin, shadow_size); }); @@ -124,7 +122,7 @@ public class Dock.MainWindow : Gtk.ApplicationWindow { // and it still gets window events var item_manager_width = item_manager.get_width (); var shadow_size = (width - item_manager_width) / 2; - var top_margin = TOP_MARGIN + shadow_size; + var top_margin = TopMargin.SIZE + shadow_size; surface.set_input_region (new Cairo.Region.rectangle ({ shadow_size, top_margin, diff --git a/src/TopMargin.vala b/src/TopMargin.vala new file mode 100644 index 00000000..451502e8 --- /dev/null +++ b/src/TopMargin.vala @@ -0,0 +1,12 @@ +/* + * SPDX-License-Identifier: GPL-3.0 + * SPDX-FileCopyrightText: 2026 elementary, Inc. (https://elementary.io) + */ + +public class Dock.TopMargin : Gtk.Widget { + public const int SIZE = 64; + + construct { + height_request = SIZE; + } +} diff --git a/src/meson.build b/src/meson.build index 4e860dee..bc8ad9de 100644 --- a/src/meson.build +++ b/src/meson.build @@ -8,6 +8,7 @@ sources = [ 'ItemManager.vala', 'MainWindow.vala', 'RenderNodeWalker.vala', + 'TopMargin.vala', 'AppSystem' / 'App.vala', 'AppSystem' / 'AppSystem.vala', 'AppSystem' / 'Launcher.vala', From 004414a5c96b324292e00b1033825a4f123057a5 Mon Sep 17 00:00:00 2001 From: lenemter Date: Wed, 12 Aug 2026 19:35:52 +0300 Subject: [PATCH 3/5] Put new workspace button outside the scroll window --- src/ItemManager.vala | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/ItemManager.vala b/src/ItemManager.vala index 5ffc22b5..128f801a 100644 --- a/src/ItemManager.vala +++ b/src/ItemManager.vala @@ -40,19 +40,13 @@ separator_box.append (new TopMargin ()); separator_box.append (separator); - var workspace_group = new ItemGroup (WorkspaceSystem.get_default ().workspaces, (obj) => new WorkspaceIconGroup ((Workspace) obj)); - - dynamic_workspace_item = new DynamicWorkspaceIcon (); - - var workspace_box = new Gtk.Box (HORIZONTAL, 0); - workspace_box.append (workspace_group); - workspace_box.append (dynamic_workspace_item); - var workspace_group_scrolled = new Gtk.ScrolledWindow () { - child = workspace_box, + child = new ItemGroup (WorkspaceSystem.get_default ().workspaces, (obj) => new WorkspaceIconGroup ((Workspace) obj)), vscrollbar_policy = NEVER, propagate_natural_width = true }; + + dynamic_workspace_item = new DynamicWorkspaceIcon (); #endif append (app_group_scrolled); @@ -60,6 +54,7 @@ #if WORKSPACE_SWITCHER append (separator_box); append (workspace_group_scrolled); + append (dynamic_workspace_item); #endif overflow = VISIBLE; From 55d285bdb10e188d2644ce64f68cff2cacbc0fe1 Mon Sep 17 00:00:00 2001 From: lenemter Date: Wed, 12 Aug 2026 21:41:55 +0300 Subject: [PATCH 4/5] Fix merge --- src/BaseItem.vala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BaseItem.vala b/src/BaseItem.vala index ea669d7a..95437092 100644 --- a/src/BaseItem.vala +++ b/src/BaseItem.vala @@ -146,7 +146,7 @@ public class Dock.BaseItem : Gtk.Box { Granite.TRANSITION_DURATION_OPEN, new Adw.CallbackAnimationTarget ((val) => { bin.allocate (icon_size, icon_size, -1, - new Gsk.Transform ().translate (Graphene.Point () { y = TopMargin.SIZE + (float) val } + new Gsk.Transform ().translate (Graphene.Point () { y = (float) val } )); }) ); From 85ac131899c543de20713a972c8d4e4385d0927f Mon Sep 17 00:00:00 2001 From: lenemter Date: Thu, 13 Aug 2026 09:27:57 +0300 Subject: [PATCH 5/5] Don't use scrollbar from ScrolledWindow --- src/ItemManager.vala | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/ItemManager.vala b/src/ItemManager.vala index 56edf2f8..7e616182 100644 --- a/src/ItemManager.vala +++ b/src/ItemManager.vala @@ -11,6 +11,7 @@ #if WORKSPACE_SWITCHER private Gtk.Separator separator; private DynamicWorkspaceIcon dynamic_workspace_item; + private Gtk.Revealer workspaces_scrollbar_revealer; #endif static construct { @@ -41,10 +42,37 @@ var workspace_group_scrolled = new Gtk.ScrolledWindow () { child = new ItemGroup (WorkspaceSystem.get_default ().workspaces, (obj) => new WorkspaceIconGroup ((Workspace) obj)), + hscrollbar_policy = EXTERNAL, vscrollbar_policy = NEVER, propagate_natural_width = true }; + /* + * Gtk.ScrolledWindow with scrollbar policy set to ALWAYS or AUTOMATIC reserves ~30px of space for scrollbar + * And it doesn't matter if the scrollbar is shown or not, the space is always reserved. + * Essentially, by default workspace_group_scrolled's minimum size is 30px, to avoid this + * we set scrollbar policy to EXTERNAL and handle scrollbar ourselves. + */ + + var workspaces_scrollbar = new Gtk.Scrollbar (HORIZONTAL, workspace_group_scrolled.hadjustment) { + vexpand = false, + valign = END + }; + + workspaces_scrollbar_revealer = new Gtk.Revealer () { + child = workspaces_scrollbar, + vexpand = false, + valign = END + }; + + update_workspaces_scrollbar_revealer (workspace_group_scrolled.hadjustment); + workspace_group_scrolled.hadjustment.changed.connect (update_workspaces_scrollbar_revealer); + + var scrollbar_overlay = new Gtk.Overlay () { + child = workspace_group_scrolled + }; + scrollbar_overlay.add_overlay (workspaces_scrollbar_revealer); + dynamic_workspace_item = new DynamicWorkspaceIcon (); #endif @@ -52,7 +80,7 @@ append (background_group); #if WORKSPACE_SWITCHER append (separator_box); - append (workspace_group_scrolled); + append (scrollbar_overlay); append (dynamic_workspace_item); #endif overflow = VISIBLE; @@ -172,4 +200,8 @@ warning ("Tried to move neither launcher nor icon group"); } } + + private void update_workspaces_scrollbar_revealer (Gtk.Adjustment adjustment) { + workspaces_scrollbar_revealer.reveal_child = adjustment.upper - adjustment.lower > adjustment.page_size; + } }