From 97e979d05bfd1e3a67f378af4d9e6f1665d84df4 Mon Sep 17 00:00:00 2001 From: lenemter Date: Sat, 4 Oct 2025 23:19:33 +0300 Subject: [PATCH 1/2] Wrap buttons when labels are too long --- data/application.css | 13 +-- meson.build | 1 + src/Bubble.vala | 13 ++- src/ButtonsContainer.vala | 166 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 176 insertions(+), 17 deletions(-) create mode 100644 src/ButtonsContainer.vala diff --git a/data/application.css b/data/application.css index 37a6d38..68871e1 100644 --- a/data/application.css +++ b/data/application.css @@ -56,20 +56,13 @@ window, -gtk-icon-style: regular; } -.notification .buttonbox { +buttonbox { margin-top: 12px; } -.notification .buttonbox button { +buttonbox button { min-width: 65px; -} - -.notification:dir(ltr) .buttonbox button + button { - margin-left: 6px; -} - -.notification:dir(rtl) .buttonbox button + button { - margin-right: 6px; + margin: 0; } .urgent image { diff --git a/meson.build b/meson.build index 41b6299..3967df3 100644 --- a/meson.build +++ b/meson.build @@ -22,6 +22,7 @@ executable( 'src/AbstractBubble.vala', 'src/Application.vala', 'src/Bubble.vala', + 'src/ButtonsContainer.vala', 'src/CanberraGtk4.vala', 'src/Confirmation.vala', 'src/DBus.vala', diff --git a/src/Bubble.vala b/src/Bubble.vala index 4570f6b..68ab057 100644 --- a/src/Bubble.vala +++ b/src/Bubble.vala @@ -139,15 +139,14 @@ public class Notifications.Bubble : AbstractBubble { attach (body_label, 1, 1); if (notification.buttons.length > 0) { - var action_area = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0) { - halign = Gtk.Align.END, - homogeneous = true - }; - action_area.add_css_class ("buttonbox"); + var action_area = new ButtonsContainer (); foreach (var button in notification.buttons) { - action_area.append (new Gtk.Button.with_label (button.label) { - action_name = button.action_name + action_area.append (new Gtk.Button () { + action_name = button.action_name, + child = new Gtk.Label (button.label) { + ellipsize = END + } }); } diff --git a/src/ButtonsContainer.vala b/src/ButtonsContainer.vala new file mode 100644 index 0000000..8a5fe43 --- /dev/null +++ b/src/ButtonsContainer.vala @@ -0,0 +1,166 @@ +/* + * Copyright 2025 elementary, Inc. (https://elementary.io) + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +public class Notifications.ButtonsContainer : Gtk.Widget { + private const int SPACING = 6; + + class construct { + set_css_name ("buttonbox"); + } + + construct { + layout_manager = new Gtk.CustomLayout ( + (widget) => HEIGHT_FOR_WIDTH, + (widget, orientation, for_size, out minimum, out natural, out minimum_baseline, out natural_baseline) => { + minimum_baseline = -1; + natural_baseline = -1; + + if (orientation == HORIZONTAL) { + var max_min_w = 0, sum_nat_w = 0, visible_count = 0; + + for (unowned var child = widget.get_first_child (); child != null; child = child.get_next_sibling ()) { + if (!child.visible) { + continue; + } + + visible_count++; + + int child_min, child_nat; + child.measure (orientation, for_size, out child_min, out child_nat, null, null); + + max_min_w = int.max (max_min_w, child_min); + sum_nat_w += child_nat; + } + + sum_nat_w += SPACING * (visible_count - 1); + + minimum = max_min_w; + natural = sum_nat_w; + } else { + // We need to compute total height after wrapping + // 'for_size' here is the available width for wrapping + minimum = compute_wrapped_size_height (widget, for_size, false); + natural = compute_wrapped_size_height (widget, for_size, true); + } + }, + (widget, width, height, baseline) => { + var row_y = 0, row_height = 0, row_used_width = 0; + var row = new GLib.List (); + + for (unowned var child = widget.get_first_child (); child != null; child = child.get_next_sibling ()) { + if (!child.visible) { + continue; + } + + int child_min_w, child_nat_w, child_min_h, child_nat_h; + child.measure (HORIZONTAL, -1, out child_min_w, out child_nat_w, null, null); + child.measure (VERTICAL, -1, out child_min_h, out child_nat_h, null, null); + + if (row.length () > 0 && row_used_width + child_nat_w > width) { + var total_row_width = 0; + foreach (var row_child in row) { + int nat_w; + row_child.measure (HORIZONTAL, -1, null, out nat_w, null, null); + total_row_width += nat_w <= width ? nat_w : width; + } + total_row_width += SPACING * ((int) row.length() - 1); + + // Set starting x so the row is right-aligned + var row_x = width - total_row_width; + + // Allocate children left-to-right + foreach (var row_child in row) { + int nat_w, nat_h; + row_child.measure (HORIZONTAL, -1, null, out nat_w, null, null); + row_child.measure (VERTICAL, -1, null, out nat_h, null, null); + + var w = nat_w <= total_row_width ? nat_w : total_row_width; + + row_child.allocate (w, nat_h, baseline, new Gsk.Transform ().translate({ row_x, row_y })); + + row_x += w + SPACING; + } + + row_y += row_height + SPACING; + row_height = 0; + row_used_width = 0; + row = new GLib.List (); + } + + row.append (child); + row_used_width += child_nat_w; + row_height = int.max (row_height, child_nat_h); + } + + if (row.length () > 0) { + var total_row_width = 0; + foreach (var row_child in row) { + int nat_w; + row_child.measure (HORIZONTAL, -1, null, out nat_w, null, null); + total_row_width += nat_w <= width ? nat_w : width; + } + total_row_width += SPACING * ((int) row.length() - 1); + + // Set starting x so the row is right-aligned + var row_x = width - total_row_width; + + // Allocate children left-to-right + foreach (var row_child in row) { + int nat_w, nat_h; + row_child.measure (HORIZONTAL, -1, null, out nat_w, null, null); + row_child.measure (VERTICAL, -1, null, out nat_h, null, null); + + var w = nat_w <= width ? nat_w : width; + + row_child.allocate(w, nat_h, baseline, new Gsk.Transform().translate({ row_x, row_y })); + + row_x += w + SPACING; + } + } + } + ); + } + + public void append (Gtk.Widget child) { + child.insert_after (this, get_last_child ()); + } + + private static int compute_wrapped_size_height (Gtk.Widget widget, int width, bool natural) { + var row_used = 0, row_height = 0, total_height = 0; + for (unowned var child = widget.get_first_child (); child != null; child = child.get_next_sibling ()) { + if (!child.visible) { + continue; + } + + int child_min_w, child_nat_w, child_min_h, child_nat_h; + child.measure (HORIZONTAL, -1, out child_min_w, out child_nat_w, null, null); + child.measure (VERTICAL, -1, out child_min_h, out child_nat_h, null, null); + + var cw = natural ? child_nat_w : child_min_w; + var ch = natural ? child_nat_h : child_min_h; + + var prospective = row_used == 0 ? cw : row_used + SPACING + cw; + + // If the child doesn't fit in the current row and it's not the first in row -> wrap + if (row_used > 0 && prospective > width) { + total_height += row_height + SPACING; + row_used = 0; + row_height = 0; + } + + if (row_used == 0) { + row_used = cw; + } else { + row_used += SPACING + cw; + } + + row_height = int.max (row_height, ch); + } + + total_height += row_height; + + return total_height; + } +} From 197145e56864a62bbdab135c7941020a40bc022e Mon Sep 17 00:00:00 2001 From: lenemter Date: Sat, 4 Oct 2025 23:29:03 +0300 Subject: [PATCH 2/2] DRY, fix lint and edge case --- src/ButtonsContainer.vala | 90 +++++++++++++++------------------------ 1 file changed, 35 insertions(+), 55 deletions(-) diff --git a/src/ButtonsContainer.vala b/src/ButtonsContainer.vala index 8a5fe43..dad4602 100644 --- a/src/ButtonsContainer.vala +++ b/src/ButtonsContainer.vala @@ -47,78 +47,32 @@ public class Notifications.ButtonsContainer : Gtk.Widget { }, (widget, width, height, baseline) => { var row_y = 0, row_height = 0, row_used_width = 0; - var row = new GLib.List (); + var row = new GLib.List (); for (unowned var child = widget.get_first_child (); child != null; child = child.get_next_sibling ()) { if (!child.visible) { continue; } - int child_min_w, child_nat_w, child_min_h, child_nat_h; - child.measure (HORIZONTAL, -1, out child_min_w, out child_nat_w, null, null); - child.measure (VERTICAL, -1, out child_min_h, out child_nat_h, null, null); + int child_nat_w, child_nat_h; + child.measure (HORIZONTAL, -1, null, out child_nat_w, null, null); + child.measure (VERTICAL, -1, null, out child_nat_h, null, null); - if (row.length () > 0 && row_used_width + child_nat_w > width) { - var total_row_width = 0; - foreach (var row_child in row) { - int nat_w; - row_child.measure (HORIZONTAL, -1, null, out nat_w, null, null); - total_row_width += nat_w <= width ? nat_w : width; - } - total_row_width += SPACING * ((int) row.length() - 1); - - // Set starting x so the row is right-aligned - var row_x = width - total_row_width; - - // Allocate children left-to-right - foreach (var row_child in row) { - int nat_w, nat_h; - row_child.measure (HORIZONTAL, -1, null, out nat_w, null, null); - row_child.measure (VERTICAL, -1, null, out nat_h, null, null); - - var w = nat_w <= total_row_width ? nat_w : total_row_width; - - row_child.allocate (w, nat_h, baseline, new Gsk.Transform ().translate({ row_x, row_y })); - - row_x += w + SPACING; - } + if (row_used_width + child_nat_w + SPACING * (row.length () - 1) > width) { + allocate_row (row, width, row_y, baseline); row_y += row_height + SPACING; row_height = 0; row_used_width = 0; - row = new GLib.List (); + row = new GLib.List (); } row.append (child); - row_used_width += child_nat_w; + row_used_width += child_nat_w + SPACING; row_height = int.max (row_height, child_nat_h); } - if (row.length () > 0) { - var total_row_width = 0; - foreach (var row_child in row) { - int nat_w; - row_child.measure (HORIZONTAL, -1, null, out nat_w, null, null); - total_row_width += nat_w <= width ? nat_w : width; - } - total_row_width += SPACING * ((int) row.length() - 1); - - // Set starting x so the row is right-aligned - var row_x = width - total_row_width; - - // Allocate children left-to-right - foreach (var row_child in row) { - int nat_w, nat_h; - row_child.measure (HORIZONTAL, -1, null, out nat_w, null, null); - row_child.measure (VERTICAL, -1, null, out nat_h, null, null); - - var w = nat_w <= width ? nat_w : width; - - row_child.allocate(w, nat_h, baseline, new Gsk.Transform().translate({ row_x, row_y })); - - row_x += w + SPACING; - } - } + allocate_row (row, width, row_y, baseline); } ); } @@ -163,4 +117,30 @@ public class Notifications.ButtonsContainer : Gtk.Widget { return total_height; } + + private static void allocate_row (GLib.List row, int width, int row_y, int baseline) { + var total_row_width = 0; + foreach (var row_child in row) { + int nat_w; + row_child.measure (HORIZONTAL, -1, null, out nat_w, null, null); + total_row_width += nat_w <= width ? nat_w : width; + } + total_row_width += SPACING * ((int) row.length () - 1); + + // Set starting x so the row is right-aligned + var row_x = width - total_row_width; + + // Allocate children left-to-right + foreach (var row_child in row) { + int nat_w, nat_h; + row_child.measure (HORIZONTAL, -1, null, out nat_w, null, null); + row_child.measure (VERTICAL, -1, null, out nat_h, null, null); + + var w = nat_w <= total_row_width ? nat_w : total_row_width; + + row_child.allocate (w, nat_h, baseline, new Gsk.Transform ().translate ({ row_x, row_y })); + + row_x += w + SPACING; + } + } }