From 2a43921c56abab6b12708f8755756467db5b3978 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 29 Apr 2025 14:36:47 +0200 Subject: [PATCH] Add `min_size` method to `PaneGrid` --- widget/src/pane_grid.rs | 48 ++++++++++++++++++++++++++--------- widget/src/pane_grid/axis.rs | 33 +++++++++++++++++------- widget/src/pane_grid/node.rs | 34 +++++++++++++++---------- widget/src/pane_grid/state.rs | 9 ++++--- 4 files changed, 86 insertions(+), 38 deletions(-) diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs index 743eec0a..357def75 100644 --- a/widget/src/pane_grid.rs +++ b/widget/src/pane_grid.rs @@ -163,6 +163,7 @@ pub struct PaneGrid< width: Length, height: Length, spacing: f32, + min_size: f32, on_click: Option Message + 'a>>, on_drag: Option Message + 'a>>, on_resize: Option<(f32, Box Message + 'a>)>, @@ -200,6 +201,7 @@ where width: Length::Fill, height: Length::Fill, spacing: 0.0, + min_size: 50.0, on_click: None, on_drag: None, on_resize: None, @@ -226,6 +228,12 @@ where self } + /// Sets the minimum size of a [`Pane`] in the [`PaneGrid`] on both axes. + pub fn min_size(mut self, min_size: impl Into) -> Self { + self.min_size = min_size.into().0; + self + } + /// Sets the message that will be produced when a [`Pane`] of the /// [`PaneGrid`] is clicked. pub fn on_click(mut self, f: F) -> Self @@ -315,8 +323,11 @@ where let cursor_position = cursor.position()?; let bounds = layout.bounds(); - let splits = - node.split_regions(self.spacing, bounds.size()); + let splits = node.split_regions( + self.spacing, + self.min_size, + bounds.size(), + ); let relative_cursor = Point::new( cursor_position.x - bounds.x, @@ -412,8 +423,12 @@ where renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - let size = limits.resolve(self.width, self.height, Size::ZERO); - let regions = self.internal.layout().pane_regions(self.spacing, size); + let bounds = limits.resolve(self.width, self.height, Size::ZERO); + let regions = self.internal.layout().pane_regions( + self.spacing, + self.min_size, + bounds, + ); let children = self .panes @@ -443,7 +458,7 @@ where }) .collect(); - layout::Node::with_children(size, children) + layout::Node::with_children(bounds, children) } fn operate( @@ -531,7 +546,8 @@ where let splits = node.split_regions( self.spacing, - Size::new(bounds.width, bounds.height), + self.min_size, + bounds.size(), ); let clicked_split = hovered_split( @@ -640,7 +656,8 @@ where let splits = node.split_regions( self.spacing, - Size::new(bounds.width, bounds.height), + self.min_size, + bounds.size(), ); if let Some((axis, rectangle, _)) = splits.get(&split) { @@ -652,7 +669,7 @@ where - rectangle.y; (position / rectangle.height) - .clamp(0.1, 0.9) + .clamp(0.0, 1.0) } Axis::Vertical => { let position = cursor_position.x @@ -660,7 +677,7 @@ where - rectangle.x; (position / rectangle.width) - .clamp(0.1, 0.9) + .clamp(0.0, 1.0) } }; @@ -781,7 +798,11 @@ where .and_then(|(split, axis)| { let bounds = layout.bounds(); - let splits = node.split_regions(self.spacing, bounds.size()); + let splits = node.split_regions( + self.spacing, + self.min_size, + bounds.size(), + ); let (_axis, region, ratio) = splits.get(&split)?; @@ -800,8 +821,11 @@ where cursor_position.y - bounds.y, ); - let splits = - node.split_regions(self.spacing, bounds.size()); + let splits = node.split_regions( + self.spacing, + self.min_size, + bounds.size(), + ); let (_split, axis, region) = hovered_split( splits.iter(), diff --git a/widget/src/pane_grid/axis.rs b/widget/src/pane_grid/axis.rs index a3049230..9aacd8ef 100644 --- a/widget/src/pane_grid/axis.rs +++ b/widget/src/pane_grid/axis.rs @@ -17,12 +17,19 @@ impl Axis { rectangle: &Rectangle, ratio: f32, spacing: f32, - ) -> (Rectangle, Rectangle) { + min_size: f32, + ) -> (Rectangle, Rectangle, f32) { match self { Axis::Horizontal => { - let height_top = - (rectangle.height * ratio - spacing / 2.0).round(); - let height_bottom = rectangle.height - height_top - spacing; + let height_top = (rectangle.height * ratio - spacing / 2.0) + .round() + .max(min_size) + .min(rectangle.height - min_size); + + let height_bottom = + (rectangle.height - height_top - spacing).max(min_size); + + let ratio = (height_top + spacing / 2.0) / rectangle.height; ( Rectangle { @@ -34,12 +41,19 @@ impl Axis { height: height_bottom, ..*rectangle }, + ratio, ) } Axis::Vertical => { - let width_left = - (rectangle.width * ratio - spacing / 2.0).round(); - let width_right = rectangle.width - width_left - spacing; + let width_left = (rectangle.width * ratio - spacing / 2.0) + .round() + .max(min_size) + .min(rectangle.width - min_size); + + let width_right = + (rectangle.width - width_left - spacing).max(min_size); + + let ratio = (width_left + spacing / 2.0) / rectangle.width; ( Rectangle { @@ -51,6 +65,7 @@ impl Axis { width: width_right, ..*rectangle }, + ratio, ) } } @@ -187,7 +202,7 @@ mod tests { width: 10.0, height: overall_height, }; - let (top, bottom) = a.split(&r, 0.5, spacing); + let (top, bottom, _ratio) = a.split(&r, 0.5, spacing, 0.0); assert_eq!( top, Rectangle { @@ -218,7 +233,7 @@ mod tests { width: overall_width, height: 10.0, }; - let (left, right) = a.split(&r, 0.5, spacing); + let (left, right, _ratio) = a.split(&r, 0.5, spacing, 0.0); assert_eq!( left, Rectangle { diff --git a/widget/src/pane_grid/node.rs b/widget/src/pane_grid/node.rs index 1f568f95..38440731 100644 --- a/widget/src/pane_grid/node.rs +++ b/widget/src/pane_grid/node.rs @@ -53,17 +53,19 @@ impl Node { pub fn pane_regions( &self, spacing: f32, - size: Size, + min_size: f32, + bounds: Size, ) -> BTreeMap { let mut regions = BTreeMap::new(); self.compute_regions( spacing, + min_size, &Rectangle { x: 0.0, y: 0.0, - width: size.width, - height: size.height, + width: bounds.width, + height: bounds.height, }, &mut regions, ); @@ -77,17 +79,19 @@ impl Node { pub fn split_regions( &self, spacing: f32, - size: Size, + min_size: f32, + bounds: Size, ) -> BTreeMap { let mut splits = BTreeMap::new(); self.compute_splits( spacing, + min_size, &Rectangle { x: 0.0, y: 0.0, - width: size.width, - height: size.height, + width: bounds.width, + height: bounds.height, }, &mut splits, ); @@ -192,6 +196,7 @@ impl Node { fn compute_regions( &self, spacing: f32, + min_size: f32, current: &Rectangle, regions: &mut BTreeMap, ) { @@ -199,10 +204,11 @@ impl Node { Node::Split { axis, ratio, a, b, .. } => { - let (region_a, region_b) = axis.split(current, *ratio, spacing); + let (region_a, region_b, _ratio) = + axis.split(current, *ratio, spacing, min_size); - a.compute_regions(spacing, ®ion_a, regions); - b.compute_regions(spacing, ®ion_b, regions); + a.compute_regions(spacing, min_size, ®ion_a, regions); + b.compute_regions(spacing, min_size, ®ion_b, regions); } Node::Pane(pane) => { let _ = regions.insert(*pane, *current); @@ -213,6 +219,7 @@ impl Node { fn compute_splits( &self, spacing: f32, + min_size: f32, current: &Rectangle, splits: &mut BTreeMap, ) { @@ -224,12 +231,13 @@ impl Node { b, id, } => { - let (region_a, region_b) = axis.split(current, *ratio, spacing); + let (region_a, region_b, ratio) = + axis.split(current, *ratio, spacing, min_size); - let _ = splits.insert(*id, (*axis, *current, *ratio)); + let _ = splits.insert(*id, (*axis, *current, ratio)); - a.compute_splits(spacing, ®ion_a, splits); - b.compute_splits(spacing, ®ion_b, splits); + a.compute_splits(spacing, min_size, ®ion_a, splits); + b.compute_splits(spacing, min_size, ®ion_b, splits); } Node::Pane(_) => {} } diff --git a/widget/src/pane_grid/state.rs b/widget/src/pane_grid/state.rs index 2f8a64ea..d999434f 100644 --- a/widget/src/pane_grid/state.rs +++ b/widget/src/pane_grid/state.rs @@ -97,10 +97,11 @@ impl State { /// Returns the adjacent [`Pane`] of another [`Pane`] in the given /// direction, if there is one. pub fn adjacent(&self, pane: Pane, direction: Direction) -> Option { - let regions = self - .internal - .layout - .pane_regions(0.0, Size::new(4096.0, 4096.0)); + let regions = self.internal.layout.pane_regions( + 0.0, + 0.0, + Size::new(4096.0, 4096.0), + ); let current_region = regions.get(&pane)?;