From 95067a71ff98253ebcaa7e893717e0562c2c5d34 Mon Sep 17 00:00:00 2001 From: stephen finkel Date: Tue, 3 Nov 2026 11:18:25 -0500 Subject: [PATCH 1/5] panel: fix window-list alignment in the center section Disable window-list expansion in the center section so its buttons remain centered as other panel widgets are added or removed. Signed-off-by: stephen finkel Assisted-by: ChatGPT:GPT-5.6 Sol --- src/panel/panel.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/panel/panel.cpp b/src/panel/panel.cpp index f85d09ca..1400c216 100644 --- a/src/panel/panel.cpp +++ b/src/panel/panel.cpp @@ -364,8 +364,9 @@ class WayfirePanel::impl (panel_position.value() == PANEL_POSITION_LEFT) || (panel_position.value() == PANEL_POSITION_RIGHT); - box.set_hexpand(!vertical); - box.set_vexpand(vertical); + const bool in_center = (&box == ¢er_box); + box.set_hexpand(!vertical && !in_center); + box.set_vexpand(vertical && !in_center); } auto widget = widget_from_name(widget_name); @@ -398,7 +399,8 @@ class WayfirePanel::impl left_box.set_hexpand(has_window_list(left_widgets_opt.value())); right_box.set_hexpand(has_window_list(right_widgets_opt.value())); - center_box.set_hexpand(has_window_list(center_widgets_opt.value())); + center_box.set_hexpand(false); + center_box.set_vexpand(false); } WfOption left_widgets_opt{"panel/widgets_left"}; From 887b4c483745ac41027d7e39cc73432c18aae017 Mon Sep 17 00:00:00 2001 From: stephen finkel Date: Tue, 3 Nov 2026 11:21:44 -0500 Subject: [PATCH 2/5] window-list: size buttons to their content Measure each button individually and use window_list_size as the maximum preferred width. Account for unequal widths during allocation and request remeasurement when the size setting changes. Add right padding to window titles to balance the existing left padding. Signed-off-by: stephen finkel Assisted-by: ChatGPT:GPT-5.6 Sol --- data/css/default.css | 4 + src/panel/widgets/window-list/layout.cpp | 125 +++++++++++++----- src/panel/widgets/window-list/window-list.cpp | 2 +- 3 files changed, 96 insertions(+), 35 deletions(-) diff --git a/data/css/default.css b/data/css/default.css index ef0132c8..6021b13e 100644 --- a/data/css/default.css +++ b/data/css/default.css @@ -26,6 +26,10 @@ padding:1em; } +.wf-panel .window-button label { + padding-right: 5px; +} + .wf-panel .window-button.activated { background-color: #8883; } diff --git a/src/panel/widgets/window-list/layout.cpp b/src/panel/widgets/window-list/layout.cpp index e172cf0d..12c731dd 100644 --- a/src/panel/widgets/window-list/layout.cpp +++ b/src/panel/widgets/window-list/layout.cpp @@ -1,6 +1,31 @@ #include "toplevel.hpp" #include "window-list.hpp" +#include +#include +#include + +namespace +{ +struct button_width_t +{ + int minimum; + int natural; +}; + +button_width_t measure_button(const Gtk::Widget& child, int height, int maximum_width) +{ + int minimum, natural, minimum_baseline, natural_baseline; + child.measure(Gtk::Orientation::HORIZONTAL, height, + minimum, natural, minimum_baseline, natural_baseline); + + // Keep the existing square minimum and respect the button's CSS minimum. + minimum = std::max(minimum, std::max(height, 1)); + natural = std::max(minimum, std::min(natural, maximum_width)); + return {minimum, natural}; +} +} + WayfireWindowListLayout::WayfireWindowListLayout(WayfireWindowList *window_list) { this->window_list = window_list; @@ -10,17 +35,18 @@ void WayfireWindowListLayout::allocate_vfunc(const Gtk::Widget& widget, int width, int height, int baseline) { Gtk::Widget& widget_not_const = const_cast(widget); - int child_count = widget.get_children().size(); - if (child_count <= 0) + auto children = widget_not_const.get_children(); + if (children.empty()) { return; } const bool vertical = window_list->is_vertical(); + std::vector widths; + int minimum_total = 0; + int natural_total = 0; - int per_child; - - if (vertical) + if (!vertical) { // Keep window buttons square on a vertical panel. per_child = width; @@ -59,29 +85,63 @@ void WayfireWindowListLayout::allocate_vfunc(const Gtk::Widget& widget, { if (child == top_widget) { - if (vertical) - { - alloc.set_y(top_position); - } else - { - alloc.set_x(top_position); - } - - child->size_allocate(alloc, -1); - index++; - continue; + auto measured = measure_button(*child, height, user_size.value()); + widths.push_back(measured); + minimum_total += measured.minimum; + natural_total += measured.natural; + } + } + + const int extra_total = natural_total - minimum_total; + const int available_extra = std::max(0, + std::min(width - minimum_total, extra_total)); + int cumulative_extra = 0; + int allocated_extra = 0; + int offset = 0; + size_t index = 0; + + for (auto child : children) + { + int size = width; + if (!vertical) + { + const auto& measured = widths[index]; + cumulative_extra += measured.natural - measured.minimum; + // Share limited space in proportion to each button's content. + // Cumulative rounding ensures no pixels are lost between buttons. + int next_extra = extra_total > 0 ? + static_cast(static_cast(available_extra) * + cumulative_extra / extra_total) : 0; + size = measured.minimum + next_extra - allocated_extra; + allocated_extra = next_extra; + } + + int position = offset; + if (child == top_widget) + { + const int extent = vertical ? height : width; + position = std::max(0, std::min(top_position, extent - size)); } + auto alloc = Gtk::Allocation(); if (vertical) { - alloc.set_y(per_child * index); + alloc.set_width(width); + alloc.set_height(size); + alloc.set_x(0); + alloc.set_y(position); } else { - alloc.set_x(per_child * index); + alloc.set_width(size); + alloc.set_height(height); + alloc.set_x(position); + alloc.set_y(0); } child->size_allocate(alloc, -1); - index++; + // Reserve the dragged button's own width in its logical position. + offset += size; + ++index; } for (auto& t : window_list->toplevels) @@ -97,35 +157,32 @@ void WayfireWindowListLayout::measure_vfunc(const Gtk::Widget& widget, Gtk::Orientation orientation, int for_size, int& minimum, int& natural, int& minimum_baseline, int& natural_baseline) const { - int child_count = widget.get_children().size(); - if (window_list->is_vertical()) { if (orientation == Gtk::Orientation::VERTICAL) { int per_child = std::max(for_size, 1); - - minimum = child_count * per_child; + minimum = widget.get_children().size() * per_child; natural = minimum; } else { minimum = 1; natural = 1; } - } else + } else if (orientation == Gtk::Orientation::HORIZONTAL) { - if (orientation == Gtk::Orientation::HORIZONTAL) + minimum = 0; + natural = 0; + for (auto child : widget.get_children()) { - // Use max of user preference and ratio 1:1 - int per_child = std::max(for_size, user_size.value()); - - minimum = child_count * for_size; - natural = per_child * child_count; - } else - { - minimum = 1; - natural = 1; + auto measured = measure_button(*child, for_size, user_size.value()); + minimum += measured.minimum; + natural += measured.natural; } + } else + { + minimum = 1; + natural = 1; } minimum_baseline = -1; diff --git a/src/panel/widgets/window-list/window-list.cpp b/src/panel/widgets/window-list/window-list.cpp index 9e8cbd42..477cc66d 100644 --- a/src/panel/widgets/window-list/window-list.cpp +++ b/src/panel/widgets/window-list/window-list.cpp @@ -542,7 +542,7 @@ WayfireWindowList::WayfireWindowList(WayfireOutput *output) set_layout_manager(layout); user_size.set_callback([=] { - this->queue_allocate(); + this->queue_resize(); }); } From adf8fa830e5260726daeafb257cc460dbcbee56f Mon Sep 17 00:00:00 2001 From: Trigg Date: Sun, 8 Nov 2026 12:11:43 +0000 Subject: [PATCH 3/5] panel: window-list: simplify layout calculations with orientation in mind --- src/panel/widgets/window-list/layout.cpp | 163 +++++++++-------------- 1 file changed, 63 insertions(+), 100 deletions(-) diff --git a/src/panel/widgets/window-list/layout.cpp b/src/panel/widgets/window-list/layout.cpp index 12c731dd..44d63ee2 100644 --- a/src/panel/widgets/window-list/layout.cpp +++ b/src/panel/widgets/window-list/layout.cpp @@ -1,8 +1,10 @@ +#include "gtkmm/enums.h" #include "toplevel.hpp" #include "window-list.hpp" #include #include +#include #include namespace @@ -13,14 +15,15 @@ struct button_width_t int natural; }; -button_width_t measure_button(const Gtk::Widget& child, int height, int maximum_width) +button_width_t measure_button(const Gtk::Widget& child, Gtk::Orientation orientation, int forsize, + int maximum_width) { int minimum, natural, minimum_baseline, natural_baseline; - child.measure(Gtk::Orientation::HORIZONTAL, height, + child.measure(orientation, forsize, minimum, natural, minimum_baseline, natural_baseline); // Keep the existing square minimum and respect the button's CSS minimum. - minimum = std::max(minimum, std::max(height, 1)); + minimum = std::max(minimum, std::max(forsize, 1)); natural = std::max(minimum, std::min(natural, maximum_width)); return {minimum, natural}; } @@ -43,107 +46,54 @@ void WayfireWindowListLayout::allocate_vfunc(const Gtk::Widget& widget, const bool vertical = window_list->is_vertical(); std::vector widths; - int minimum_total = 0; - int natural_total = 0; - if (!vertical) - { - // Keep window buttons square on a vertical panel. - per_child = width; - } else if (window_list->hide_label) - { - // Use square buttons for no label mode - per_child = height; - } else - { - per_child = width / child_count; + // TODO Come back some day with rotated text labels as a vertical option + bool no_labels = window_list->hide_label.value() || vertical; - // user preference is ignored if too small - int preference = std::max(height, user_size.value()); + // width & height need to switch based on orientation + int longside = vertical ? height : width; + int shortside = vertical ? width : height; - // At minimum use ratio of 1:1, at max use user preference - per_child = std::max(per_child, height); - per_child = std::min(per_child, preference); - } - - int index = 0; - auto alloc = Gtk::Allocation(); + int per_child = 1; - if (vertical) + if (no_labels) { - alloc.set_width(width); - alloc.set_height(per_child); - alloc.set_x(0); + per_child = shortside; } else { - alloc.set_height(height); - alloc.set_width(per_child); - alloc.set_y(0); - } - - for (auto child : widget_not_const.get_children()) - { - if (child == top_widget) - { - auto measured = measure_button(*child, height, user_size.value()); - widths.push_back(measured); - minimum_total += measured.minimum; - natural_total += measured.natural; - } + per_child = longside / children.size(); + int preference = std::max(shortside, user_size.value()); + // Can't be larger than user preference + per_child = std::min(per_child, preference); + // Can't be smaller than square + per_child = std::max(per_child, shortside); } - const int extra_total = natural_total - minimum_total; - const int available_extra = std::max(0, - std::min(width - minimum_total, extra_total)); - int cumulative_extra = 0; - int allocated_extra = 0; - int offset = 0; - size_t index = 0; - + // Allocate locations and sizes + int offset = 0; for (auto child : children) { - int size = width; - if (!vertical) - { - const auto& measured = widths[index]; - cumulative_extra += measured.natural - measured.minimum; - // Share limited space in proportion to each button's content. - // Cumulative rounding ensures no pixels are lost between buttons. - int next_extra = extra_total > 0 ? - static_cast(static_cast(available_extra) * - cumulative_extra / extra_total) : 0; - size = measured.minimum + next_extra - allocated_extra; - allocated_extra = next_extra; - } - - int position = offset; - if (child == top_widget) - { - const int extent = vertical ? height : width; - position = std::max(0, std::min(top_position, extent - size)); - } - auto alloc = Gtk::Allocation(); + if (vertical) { - alloc.set_width(width); - alloc.set_height(size); + alloc.set_width(shortside); + alloc.set_height(per_child); alloc.set_x(0); - alloc.set_y(position); + alloc.set_y(offset); } else { - alloc.set_width(size); - alloc.set_height(height); - alloc.set_x(position); + alloc.set_width(per_child); + alloc.set_height(shortside); + alloc.set_x(offset); alloc.set_y(0); } child->size_allocate(alloc, -1); - // Reserve the dragged button's own width in its logical position. - offset += size; - ++index; + offset += per_child; } + // Update hints for (auto& t : window_list->toplevels) { if (t.second) @@ -157,32 +107,48 @@ void WayfireWindowListLayout::measure_vfunc(const Gtk::Widget& widget, Gtk::Orientation orientation, int for_size, int& minimum, int& natural, int& minimum_baseline, int& natural_baseline) const { - if (window_list->is_vertical()) + const bool vertical = window_list->is_vertical(); + + // TODO Come back some day with rotated text labels as a vertical option + bool no_labels = window_list->hide_label || vertical; + + // Easy mode, labels are always square, so just count + if (no_labels) { - if (orientation == Gtk::Orientation::VERTICAL) + if ((vertical && (orientation == Gtk::Orientation::VERTICAL)) || + (!vertical && (orientation == Gtk::Orientation::HORIZONTAL))) { + // Measure long side, widget count * icon size int per_child = std::max(for_size, 1); minimum = widget.get_children().size() * per_child; natural = minimum; } else { + // No specific size required minimum = 1; natural = 1; } - } else if (orientation == Gtk::Orientation::HORIZONTAL) + } else { - minimum = 0; - natural = 0; - for (auto child : widget.get_children()) + if ((vertical && (orientation == Gtk::Orientation::VERTICAL)) || + (!vertical && (orientation == Gtk::Orientation::HORIZONTAL))) { - auto measured = measure_button(*child, for_size, user_size.value()); - minimum += measured.minimum; - natural += measured.natural; + // When measuring the long side, count up what every widget wants + // This is only a suggestion that bubbles up + minimum = 0; + natural = 0; + for (auto child : widget.get_children()) + { + auto measured = measure_button(*child, orientation, for_size, user_size.value()); + minimum += measured.minimum; + natural += measured.natural; + } + } else + { + // When measuring the short side, don't suggest a size at all + minimum = 1; + natural = 1; } - } else - { - minimum = 1; - natural = 1; } minimum_baseline = -1; @@ -192,10 +158,7 @@ void WayfireWindowListLayout::measure_vfunc(const Gtk::Widget& widget, Gtk::SizeRequestMode WayfireWindowListLayout::get_request_mode_vfunc( const Gtk::Widget& widget) const { - if (window_list->is_vertical()) - { - return Gtk::SizeRequestMode::HEIGHT_FOR_WIDTH; - } - - return Gtk::SizeRequestMode::WIDTH_FOR_HEIGHT; + return (window_list->is_vertical() ? + Gtk::SizeRequestMode::HEIGHT_FOR_WIDTH : + Gtk::SizeRequestMode::WIDTH_FOR_HEIGHT); } From 4a131988cba1877b382fc2f01873bda4765fe12d Mon Sep 17 00:00:00 2001 From: Trigg Date: Sun, 8 Nov 2026 12:32:02 +0000 Subject: [PATCH 4/5] chore: remove old includes from window-list layout --- src/panel/widgets/window-list/layout.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/panel/widgets/window-list/layout.cpp b/src/panel/widgets/window-list/layout.cpp index 44d63ee2..751f2176 100644 --- a/src/panel/widgets/window-list/layout.cpp +++ b/src/panel/widgets/window-list/layout.cpp @@ -3,9 +3,6 @@ #include "window-list.hpp" #include -#include -#include -#include namespace { @@ -45,7 +42,6 @@ void WayfireWindowListLayout::allocate_vfunc(const Gtk::Widget& widget, } const bool vertical = window_list->is_vertical(); - std::vector widths; // TODO Come back some day with rotated text labels as a vertical option bool no_labels = window_list->hide_label.value() || vertical; From f26651e8ef7a1e5ae8bb640a4e886ca32520645d Mon Sep 17 00:00:00 2001 From: Trigg Date: Mon, 9 Nov 2026 10:17:32 +0000 Subject: [PATCH 5/5] panel: window-list: reapply toplevel dragging --- src/panel/widgets/window-list/layout.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/panel/widgets/window-list/layout.cpp b/src/panel/widgets/window-list/layout.cpp index 751f2176..7056f109 100644 --- a/src/panel/widgets/window-list/layout.cpp +++ b/src/panel/widgets/window-list/layout.cpp @@ -69,19 +69,25 @@ void WayfireWindowListLayout::allocate_vfunc(const Gtk::Widget& widget, int offset = 0; for (auto child : children) { - auto alloc = Gtk::Allocation(); + auto alloc = Gtk::Allocation(); + int position = offset; + // If widget is being dragged, show its drag location + if (child == top_widget) + { + position = std::max(0, std::min(top_position, longside - per_child)); + } if (vertical) { alloc.set_width(shortside); alloc.set_height(per_child); alloc.set_x(0); - alloc.set_y(offset); + alloc.set_y(position); } else { alloc.set_width(per_child); alloc.set_height(shortside); - alloc.set_x(offset); + alloc.set_x(position); alloc.set_y(0); }