implement MasonryLayout via LayoutManager

This commit is contained in:
Roman Makarov committed 2026-09-08 22:41:32 +02:00
1 parent 76afd1ba33
commit f46cc07007
2 files changed
+123 -59

No files matched your search

+7 -59
View File
@@ -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 = &gtk::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 = &gtk::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 = &gtk::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 = &gtk::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 }
}
@@ -0,0 +1,116 @@
use gtk::{glib, prelude::*};
glib::wrapper! {
pub struct MasonryLayout(ObjectSubclass<imp::MasonryLayout>)
@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: &gtk::Widget) -> gtk::SizeRequestMode {
gtk::SizeRequestMode::HeightForWidth
}
// returns minimum, natural, minimum_baseline, natural_baseline
fn measure(
&self,
widget: &gtk::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: &gtk::Widget, width: i32, _height: i32, _baseline: i32) {
layout(widget, width, |child, allocation| {
child.size_allocate(allocation, -1);
});
}
}
}
const SPACING: i32 = 10;
fn children(widget: &gtk::Widget) -> impl Iterator<Item = gtk::Widget> {
std::iter::successors(widget.first_child(), |child| child.next_sibling())
.filter(|child| child.should_layout())
}
fn measure_width(widget: &gtk::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: &gtk::Widget,
width: i32,
mut place: impl FnMut(&gtk::Widget, &gtk::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, &gtk::Allocation::new(x, y, child_width, height));
heights[column] = y + height + SPACING;
}
(heights[0].max(heights[1]) - SPACING).max(0)
}