Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions data/Application.css
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ bottom-margin {
min-height: 9px;
}

horizontal-margin {
min-width: 9px;
}

launcher {
padding: 6px;
padding-bottom: 0px;
Expand Down
11 changes: 10 additions & 1 deletion src/AppSystem/Background/BackgroundMonitor.vala
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,21 @@ public class Dock.BackgroundMonitor : Object {
}

private void update_background_apps () {
var app_id_set = new GLib.GenericSet<unowned string> (GLib.str_hash, GLib.str_equal, null);
BackgroundApp[] apps = {};

foreach (var table in proxy.background_apps) {
DesktopAppInfo? app_info = null;
if ("app_id" in table) {
app_info = new DesktopAppInfo ((string) table["app_id"] + ".desktop");
unowned var app_id = table["app_id"].get_string ();

if (app_id in app_id_set) {
// avoid duplicate entries
continue;
}

app_info = new DesktopAppInfo (app_id + ".desktop");
app_id_set.add (app_id);
}

if (app_info == null) {
Expand Down
30 changes: 30 additions & 0 deletions src/HorizontalMargin.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* SPDX-License-Identifier: GPL-3.0
* SPDX-FileCopyrightText: 2026 elementary, Inc. (https://elementary.io)
*/

public class HorizontalMargin : Gtk.Widget {
private static GLib.List<unowned HorizontalMargin> instances = new GLib.List<unowned HorizontalMargin> ();

class construct {
set_css_name ("horizontal-margin");
}

construct {
instances.append (this);
}

~HorizontalMargin () {
instances.remove (this);
}

public new static int get_size () {
foreach (var instance in instances) {
if (instance.get_realized ()) {
return instance.get_width ();
}
}

return 0;
}
}
27 changes: 2 additions & 25 deletions src/ItemManager.vala
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,6 @@

private DynamicWorkspaceIcon dynamic_workspace_item;

#if WORKSPACE_SWITCHER
private Gtk.Separator separator;
#endif

static construct {
settings = new Settings ("io.elementary.dock");
}
Expand All @@ -24,23 +20,9 @@
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);
#endif

append (app_group);
append (background_group);
#if WORKSPACE_SWITCHER
append (separator);
append (new ItemGroup (WorkspaceSystem.get_default ().workspaces, (obj) => new WorkspaceIconGroup ((Workspace) obj)));
append (dynamic_workspace_item);
#endif

overflow = VISIBLE;

var drop_target_file = new Gtk.DropTarget (typeof (File), COPY) {
Expand Down Expand Up @@ -143,19 +125,14 @@
map.connect (() => {
AppSystem.get_default ().load.begin ();
background_item.load ();
#if WORKSPACE_SWITCHER
WorkspaceSystem.get_default ().load.begin ();
#endif
});
}

public void move_launcher_after (BaseItem source, int target_index) {
if (source is Launcher) {
AppSystem.get_default ().reorder_app (source.app, target_index);
} else if (source is WorkspaceIconGroup) {
WorkspaceSystem.get_default ().reorder_workspace (source.workspace, target_index);
} else {
warning ("Tried to move neither launcher nor icon group");
info ("Tried to move not a launcher");
}
}
}
81 changes: 63 additions & 18 deletions src/MainWindow.vala
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,11 @@ public class Dock.MainWindow : Gtk.ApplicationWindow {
private Pantheon.Desktop.Panel? panel;

private ItemManager item_manager;
private WorkspaceManager workspace_manager;
private WindowDragManager window_drag_manager;
private bool initialized_blur = false;
private bool realized_item_manager = false;
private bool realized_workspace_manager = false;
private int border_radius = 0;

class construct {
Expand All @@ -36,32 +39,64 @@ public class Dock.MainWindow : Gtk.ApplicationWindow {
overflow = VISIBLE;
resizable = false;
titlebar = new Gtk.Label ("") { visible = false };
item_manager = new ItemManager ();
workspace_manager = new WorkspaceManager ();

var dock_box = new Gtk.Box (VERTICAL, 0);
dock_box.append (new Container ());
dock_box.append (new BottomMargin ());
/* Launchers */

item_manager = new ItemManager ();
var launcher_container = new Gtk.Box (VERTICAL, 0);
launcher_container.append (new Container ());
launcher_container.append (new BottomMargin ());

// // Don't clip launchers to dock background https://github.com/elementary/dock/issues/275
var launcher_overlay = new Gtk.Overlay () {
child = launcher_container
};
launcher_overlay.add_overlay (item_manager);

var launcher_size_group = new Gtk.SizeGroup (Gtk.SizeGroupMode.BOTH);
launcher_size_group.add_widget (launcher_container);
launcher_size_group.add_widget (item_manager);

/* Workspaces */

var workspace_container = new Gtk.Box (VERTICAL, 0);
workspace_container.append (new Container ());
workspace_container.append (new BottomMargin ());

// Don't clip launchers to dock background https://github.com/elementary/dock/issues/275
var overlay = new Gtk.Overlay () {
child = dock_box
var workspace_overlay = new Gtk.Overlay () {
child = workspace_container
};
overlay.add_overlay (item_manager);
workspace_overlay.add_overlay (workspace_manager);

var size_group = new Gtk.SizeGroup (Gtk.SizeGroupMode.BOTH);
size_group.add_widget (dock_box);
size_group.add_widget (item_manager);
var workspace_size_group = new Gtk.SizeGroup (Gtk.SizeGroupMode.BOTH);
workspace_size_group.add_widget (workspace_container);
workspace_size_group.add_widget (workspace_manager);

child = overlay;
/* Full dock */

var docks_box = new Gtk.Box (HORIZONTAL, 0);
docks_box.append (launcher_overlay);
docks_box.append (new HorizontalMargin ());
docks_box.append (workspace_overlay);

// child = launcher_overlay;
child = docks_box;

remove_css_class ("background");

// Fixes DnD reordering of launchers failing on a very small line between two launchers
var drop_target_launcher = new Gtk.DropTarget (typeof (Launcher), MOVE);
item_manager.add_controller (drop_target_launcher);

item_manager.realize.connect (init_panel);
item_manager.realize.connect (() => {
realized_item_manager = true;
try_init_panel ();
});
workspace_manager.realize.connect (() => {
realized_workspace_manager = true;
try_init_panel ();
});

settings.changed["autohide-mode"].connect (() => {
if (panel != null) {
Expand All @@ -86,7 +121,6 @@ public class Dock.MainWindow : Gtk.ApplicationWindow {
remove_css_class ("reduce-transparency");
} else {
add_css_class ("reduce-transparency");

}
}

Expand All @@ -107,14 +141,22 @@ public class Dock.MainWindow : Gtk.ApplicationWindow {
}

private static Wl.RegistryListener registry_listener;
private void init_panel () {
private void try_init_panel () {
// Wait for both parts of the dock to be realized to setup the surface
if (!realized_item_manager || !realized_workspace_manager) {
return;
}

unowned var surface = (Gdk.Toplevel) get_surface ();

surface.compute_size.connect ((surface, size) => {
// manually set shadow width since the additional margin we add to avoid icons clipping when
// 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 hztl_margin_width = HorizontalMargin.get_size ();
var workspace_manager_width = workspace_manager.get_width ();
// var shadow_size = (surface.width - item_manager_width) / 2;
var shadow_size = (surface.width - item_manager_width - hztl_margin_width - workspace_manager_width) / 2;
var top_margin = TOP_MARGIN + shadow_size - 1;
size.set_shadow_width (shadow_size, shadow_size, top_margin, shadow_size);
});
Expand All @@ -123,12 +165,15 @@ public class Dock.MainWindow : Gtk.ApplicationWindow {
// manually set input region since container's shadow are is the content of the window
// and it still gets window events
var item_manager_width = item_manager.get_width ();
var shadow_size = (width - item_manager_width) / 2;
var hztl_margin_width = HorizontalMargin.get_size ();
var workspace_manager_width = workspace_manager.get_width ();
// var shadow_size = (width - item_manager_width) / 2;
var shadow_size = (width - item_manager_width - hztl_margin_width - workspace_manager_width) / 2;
var top_margin = TOP_MARGIN + shadow_size;
surface.set_input_region (new Cairo.Region.rectangle ({
shadow_size,
top_margin,
item_manager_width,
item_manager_width + workspace_manager_width,
height - top_margin
}));

Expand Down
69 changes: 69 additions & 0 deletions src/WorkspaceManager.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* SPDX-License-Identifier: GPL-3.0
* SPDX-FileCopyrightText: 2026 elementary, Inc. (https://elementary.io)
*/

public class Dock.WorkspaceManager : Gtk.Box {
private static Settings settings;

private DynamicWorkspaceIcon dynamic_workspace_item;

static construct {
settings = new Settings ("io.elementary.dock");
}

construct {
dynamic_workspace_item = new DynamicWorkspaceIcon ();

append (new ItemGroup (WorkspaceSystem.get_default ().workspaces, (obj) => new WorkspaceIconGroup ((Workspace) obj)));
append (dynamic_workspace_item);

overflow = VISIBLE;

var drop_target_file = new Gtk.DropTarget (typeof (File), COPY) {
preload = true
};
add_controller (drop_target_file);

double drop_x, drop_y;
drop_target_file.enter.connect ((x, y) => {
drop_x = x;
drop_y = y;
return COPY;
});

drop_target_file.notify["value"].connect (() => {
if (drop_target_file.get_value () == null) {
return;
}

if (drop_target_file.get_value ().get_object () == null) {
return;
}

if (!(drop_target_file.get_value ().get_object () is File)) {
return;
}

var file = (File) drop_target_file.get_value ().get_object ();
var app_info = new DesktopAppInfo.from_filename (file.get_path ());

if (app_info == null) {
return;
}
});

map.connect (() => {
WorkspaceSystem.get_default ().load.begin ();
});
}

public void move_launcher_after (BaseItem source, int target_index) {
if (source is WorkspaceIconGroup) {
WorkspaceSystem.get_default ().reorder_workspace (source.workspace, target_index);
} else {
info ("Tried to move not an icon group");
return;
}
}
}
2 changes: 2 additions & 0 deletions src/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ sources = [
'BaseIconGroup.vala',
'BaseItem.vala',
'BottomMargin.vala',
'HorizontalMargin.vala',
'ContainerItem.vala',
'ItemGroup.vala',
'ItemManager.vala',
'WorkspaceManager.vala',
'MainWindow.vala',
'RenderNodeWalker.vala',
'AppSystem' / 'App.vala',
Expand Down