Add min_size method to PaneGrid

This commit is contained in:
Héctor Ramón Jiménez 2025-04-29 14:36:47 +02:00
parent aa633f6832
commit 2a43921c56
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
4 changed files with 86 additions and 38 deletions

View file

@ -163,6 +163,7 @@ pub struct PaneGrid<
width: Length,
height: Length,
spacing: f32,
min_size: f32,
on_click: Option<Box<dyn Fn(Pane) -> Message + 'a>>,
on_drag: Option<Box<dyn Fn(DragEvent) -> Message + 'a>>,
on_resize: Option<(f32, Box<dyn Fn(ResizeEvent) -> 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<Pixels>) -> 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<F>(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(),

View file

@ -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 {

View file

@ -53,17 +53,19 @@ impl Node {
pub fn pane_regions(
&self,
spacing: f32,
size: Size,
min_size: f32,
bounds: Size,
) -> BTreeMap<Pane, Rectangle> {
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<Split, (Axis, Rectangle, f32)> {
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<Pane, Rectangle>,
) {
@ -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, &region_a, regions);
b.compute_regions(spacing, &region_b, regions);
a.compute_regions(spacing, min_size, &region_a, regions);
b.compute_regions(spacing, min_size, &region_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<Split, (Axis, Rectangle, f32)>,
) {
@ -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, &region_a, splits);
b.compute_splits(spacing, &region_b, splits);
a.compute_splits(spacing, min_size, &region_a, splits);
b.compute_splits(spacing, min_size, &region_b, splits);
}
Node::Pane(_) => {}
}

View file

@ -97,10 +97,11 @@ impl<T> State<T> {
/// Returns the adjacent [`Pane`] of another [`Pane`] in the given
/// direction, if there is one.
pub fn adjacent(&self, pane: Pane, direction: Direction) -> Option<Pane> {
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)?;