From f46cc07007919eb082e5734754f01a7c39b9be2c Mon Sep 17 00:00:00 2001 From: Roman Makarov Date: Tue, 8 Sep 2026 22:41:32 +0200 Subject: [PATCH] implement MasonryLayout via LayoutManager --- lact-gui/src/app/pages/oc_page.rs | 66 ++-------- .../src/app/pages/oc_page/masonry_layout.rs | 116 ++++++++++++++++++ 2 files changed, 123 insertions(+), 59 deletions(-) create mode 100644 lact-gui/src/app/pages/oc_page/masonry_layout.rs diff --git a/lact-gui/src/app/pages/oc_page.rs b/lact-gui/src/app/pages/oc_page.rs index 55245c83..8617e747 100644 --- a/lact-gui/src/app/pages/oc_page.rs +++ b/lact-gui/src/app/pages/oc_page.rs @@ -1,4 +1,5 @@ mod clocks_frame; +mod masonry_layout; mod performance_frame; mod power_frame; mod power_states; @@ -80,47 +81,25 @@ impl relm4::Component for OcPage { model.stats_section.widget(), - gtk::FlowBox { - set_orientation: gtk::Orientation::Horizontal, - set_selection_mode: gtk::SelectionMode::None, - set_min_children_per_line: 1, - set_max_children_per_line: 2, - set_column_spacing: 10, - set_row_spacing: 10, - set_homogeneous: false, + gtk::Box { + set_layout_manager: Some(masonry_layout::MasonryLayout::default()), set_valign: gtk::Align::Start, set_hexpand: true, - append: gpu_clocks_child = >k::FlowBoxChild { + model.gpu_clocks_frame.widget() { add_css_class: "oc-page-section", - set_valign: gtk::Align::Start, - set_hexpand: true, - - model.gpu_clocks_frame.widget(), }, - append: vram_clocks_child = >k::FlowBoxChild { + model.vram_clocks_frame.widget() { add_css_class: "oc-page-section", - set_valign: gtk::Align::Start, - set_hexpand: true, - - model.vram_clocks_frame.widget(), }, - append: power_child = >k::FlowBoxChild { + model.power_frame.widget() { add_css_class: "oc-page-section", - set_valign: gtk::Align::Start, - set_hexpand: true, - - model.power_frame.widget(), }, - append: power_states_child = >k::FlowBoxChild { + model.power_states_frame.widget() { add_css_class: "oc-page-section", - set_valign: gtk::Align::Start, - set_hexpand: true, - - model.power_states_frame.widget(), }, }, }, @@ -181,37 +160,6 @@ impl relm4::Component for OcPage { let widgets = view_output!(); - let section_size_group = gtk::SizeGroup::new(gtk::SizeGroupMode::Horizontal); - section_size_group.add_widget(&widgets.gpu_clocks_child); - section_size_group.add_widget(&widgets.vram_clocks_child); - section_size_group.add_widget(&widgets.power_child); - section_size_group.add_widget(&widgets.power_states_child); - - model - .gpu_clocks_frame - .widget() - .bind_property("visible", &widgets.gpu_clocks_child, "visible") - .sync_create() - .build(); - model - .vram_clocks_frame - .widget() - .bind_property("visible", &widgets.vram_clocks_child, "visible") - .sync_create() - .build(); - model - .power_frame - .widget() - .bind_property("visible", &widgets.power_child, "visible") - .sync_create() - .build(); - model - .power_states_frame - .widget() - .bind_property("visible", &widgets.power_states_child, "visible") - .sync_create() - .build(); - ComponentParts { model, widgets } } diff --git a/lact-gui/src/app/pages/oc_page/masonry_layout.rs b/lact-gui/src/app/pages/oc_page/masonry_layout.rs new file mode 100644 index 00000000..4d021259 --- /dev/null +++ b/lact-gui/src/app/pages/oc_page/masonry_layout.rs @@ -0,0 +1,116 @@ +use gtk::{glib, prelude::*}; + +glib::wrapper! { + pub struct MasonryLayout(ObjectSubclass) + @extends gtk::LayoutManager; +} + +impl Default for MasonryLayout { + fn default() -> Self { + glib::Object::new() + } +} + +mod imp { + use super::*; + use gtk::subclass::prelude::*; + + #[derive(Default)] + pub struct MasonryLayout; + + #[glib::object_subclass] + impl ObjectSubclass for MasonryLayout { + const NAME: &'static str = "MasonryLayout"; + type Type = super::MasonryLayout; + type ParentType = gtk::LayoutManager; + } + + impl ObjectImpl for MasonryLayout {} + + impl LayoutManagerImpl for MasonryLayout { + fn request_mode(&self, _widget: >k::Widget) -> gtk::SizeRequestMode { + gtk::SizeRequestMode::HeightForWidth + } + + // returns minimum, natural, minimum_baseline, natural_baseline + fn measure( + &self, + widget: >k::Widget, + orientation: gtk::Orientation, + for_size: i32, + ) -> (i32, i32, i32, i32) { + let (minimum, natural) = measure_width(widget); + if orientation == gtk::Orientation::Horizontal { + (minimum, natural, -1, -1) + } else { + let width = if for_size < 0 { natural } else { for_size }; + let height = layout(widget, width, |_, _| {}); + (height, height, -1, -1) + } + } + + fn allocate(&self, widget: >k::Widget, width: i32, _height: i32, _baseline: i32) { + layout(widget, width, |child, allocation| { + child.size_allocate(allocation, -1); + }); + } + } +} + +const SPACING: i32 = 10; + +fn children(widget: >k::Widget) -> impl Iterator { + std::iter::successors(widget.first_child(), |child| child.next_sibling()) + .filter(|child| child.should_layout()) +} + +fn measure_width(widget: >k::Widget) -> (i32, i32) { + let mut minimum = 0; + let mut natural = 0; + let mut count = 0; + for child in children(widget) { + let (child_minimum, child_natural, _, _) = child.measure(gtk::Orientation::Horizontal, -1); + minimum = minimum.max(child_minimum); + natural = natural.max(child_natural); + count += 1; + } + if count > 1 { + natural = natural * 2 + SPACING; + } + (minimum, natural) +} + +// Measurement and allocation use the same packing so the scroller gets the full height. +fn layout( + widget: >k::Widget, + width: i32, + mut place: impl FnMut(>k::Widget, >k::Allocation), +) -> i32 { + let (minimum, _) = measure_width(widget); + let columns = if children(widget).count() > 1 && width >= minimum * 2 + SPACING { + 2 + } else { + 1 + }; + let column_width = ((width - SPACING * (columns as i32 - 1)) / columns as i32).max(0); + let mut heights = [0, 0]; + for child in children(widget) { + let column = usize::from(columns == 2 && heights[1] < heights[0]); + let x = column as i32 * (column_width + SPACING); + let child_width = if column == columns - 1 { + width - x + } else { + column_width + }; + let (_, height, _, _) = child.measure(gtk::Orientation::Vertical, child_width); + let y = heights[column]; + let x = if widget.direction() == gtk::TextDirection::Rtl { + width - x - child_width + } else { + x + }; + place(&child, >k::Allocation::new(x, y, child_width, height)); + heights[column] = y + height + SPACING; + } + (heights[0].max(heights[1]) - SPACING).max(0) +}