Merge pull request #2911 from iced-rs/feature/pane-grid-min-size
Add `min_size` method to `PaneGrid`
This commit is contained in:
commit
420ac0b7d1
4 changed files with 210 additions and 52 deletions
|
|
@ -163,6 +163,7 @@ pub struct PaneGrid<
|
||||||
width: Length,
|
width: Length,
|
||||||
height: Length,
|
height: Length,
|
||||||
spacing: f32,
|
spacing: f32,
|
||||||
|
min_size: f32,
|
||||||
on_click: Option<Box<dyn Fn(Pane) -> Message + 'a>>,
|
on_click: Option<Box<dyn Fn(Pane) -> Message + 'a>>,
|
||||||
on_drag: Option<Box<dyn Fn(DragEvent) -> Message + 'a>>,
|
on_drag: Option<Box<dyn Fn(DragEvent) -> Message + 'a>>,
|
||||||
on_resize: Option<(f32, Box<dyn Fn(ResizeEvent) -> Message + 'a>)>,
|
on_resize: Option<(f32, Box<dyn Fn(ResizeEvent) -> Message + 'a>)>,
|
||||||
|
|
@ -200,6 +201,7 @@ where
|
||||||
width: Length::Fill,
|
width: Length::Fill,
|
||||||
height: Length::Fill,
|
height: Length::Fill,
|
||||||
spacing: 0.0,
|
spacing: 0.0,
|
||||||
|
min_size: 50.0,
|
||||||
on_click: None,
|
on_click: None,
|
||||||
on_drag: None,
|
on_drag: None,
|
||||||
on_resize: None,
|
on_resize: None,
|
||||||
|
|
@ -226,6 +228,12 @@ where
|
||||||
self
|
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
|
/// Sets the message that will be produced when a [`Pane`] of the
|
||||||
/// [`PaneGrid`] is clicked.
|
/// [`PaneGrid`] is clicked.
|
||||||
pub fn on_click<F>(mut self, f: F) -> Self
|
pub fn on_click<F>(mut self, f: F) -> Self
|
||||||
|
|
@ -315,8 +323,11 @@ where
|
||||||
let cursor_position = cursor.position()?;
|
let cursor_position = cursor.position()?;
|
||||||
let bounds = layout.bounds();
|
let bounds = layout.bounds();
|
||||||
|
|
||||||
let splits =
|
let splits = node.split_regions(
|
||||||
node.split_regions(self.spacing, bounds.size());
|
self.spacing,
|
||||||
|
self.min_size,
|
||||||
|
bounds.size(),
|
||||||
|
);
|
||||||
|
|
||||||
let relative_cursor = Point::new(
|
let relative_cursor = Point::new(
|
||||||
cursor_position.x - bounds.x,
|
cursor_position.x - bounds.x,
|
||||||
|
|
@ -412,8 +423,12 @@ where
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
limits: &layout::Limits,
|
limits: &layout::Limits,
|
||||||
) -> layout::Node {
|
) -> layout::Node {
|
||||||
let size = limits.resolve(self.width, self.height, Size::ZERO);
|
let bounds = limits.resolve(self.width, self.height, Size::ZERO);
|
||||||
let regions = self.internal.layout().pane_regions(self.spacing, size);
|
let regions = self.internal.layout().pane_regions(
|
||||||
|
self.spacing,
|
||||||
|
self.min_size,
|
||||||
|
bounds,
|
||||||
|
);
|
||||||
|
|
||||||
let children = self
|
let children = self
|
||||||
.panes
|
.panes
|
||||||
|
|
@ -443,7 +458,7 @@ where
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
layout::Node::with_children(size, children)
|
layout::Node::with_children(bounds, children)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn operate(
|
fn operate(
|
||||||
|
|
@ -531,7 +546,8 @@ where
|
||||||
|
|
||||||
let splits = node.split_regions(
|
let splits = node.split_regions(
|
||||||
self.spacing,
|
self.spacing,
|
||||||
Size::new(bounds.width, bounds.height),
|
self.min_size,
|
||||||
|
bounds.size(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let clicked_split = hovered_split(
|
let clicked_split = hovered_split(
|
||||||
|
|
@ -640,7 +656,8 @@ where
|
||||||
|
|
||||||
let splits = node.split_regions(
|
let splits = node.split_regions(
|
||||||
self.spacing,
|
self.spacing,
|
||||||
Size::new(bounds.width, bounds.height),
|
self.min_size,
|
||||||
|
bounds.size(),
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some((axis, rectangle, _)) = splits.get(&split) {
|
if let Some((axis, rectangle, _)) = splits.get(&split) {
|
||||||
|
|
@ -652,7 +669,7 @@ where
|
||||||
- rectangle.y;
|
- rectangle.y;
|
||||||
|
|
||||||
(position / rectangle.height)
|
(position / rectangle.height)
|
||||||
.clamp(0.1, 0.9)
|
.clamp(0.0, 1.0)
|
||||||
}
|
}
|
||||||
Axis::Vertical => {
|
Axis::Vertical => {
|
||||||
let position = cursor_position.x
|
let position = cursor_position.x
|
||||||
|
|
@ -660,7 +677,7 @@ where
|
||||||
- rectangle.x;
|
- rectangle.x;
|
||||||
|
|
||||||
(position / rectangle.width)
|
(position / rectangle.width)
|
||||||
.clamp(0.1, 0.9)
|
.clamp(0.0, 1.0)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -781,7 +798,11 @@ where
|
||||||
.and_then(|(split, axis)| {
|
.and_then(|(split, axis)| {
|
||||||
let bounds = layout.bounds();
|
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)?;
|
let (_axis, region, ratio) = splits.get(&split)?;
|
||||||
|
|
||||||
|
|
@ -800,8 +821,11 @@ where
|
||||||
cursor_position.y - bounds.y,
|
cursor_position.y - bounds.y,
|
||||||
);
|
);
|
||||||
|
|
||||||
let splits =
|
let splits = node.split_regions(
|
||||||
node.split_regions(self.spacing, bounds.size());
|
self.spacing,
|
||||||
|
self.min_size,
|
||||||
|
bounds.size(),
|
||||||
|
);
|
||||||
|
|
||||||
let (_split, axis, region) = hovered_split(
|
let (_split, axis, region) = hovered_split(
|
||||||
splits.iter(),
|
splits.iter(),
|
||||||
|
|
|
||||||
|
|
@ -17,12 +17,20 @@ impl Axis {
|
||||||
rectangle: &Rectangle,
|
rectangle: &Rectangle,
|
||||||
ratio: f32,
|
ratio: f32,
|
||||||
spacing: f32,
|
spacing: f32,
|
||||||
) -> (Rectangle, Rectangle) {
|
min_size_a: f32,
|
||||||
|
min_size_b: f32,
|
||||||
|
) -> (Rectangle, Rectangle, f32) {
|
||||||
match self {
|
match self {
|
||||||
Axis::Horizontal => {
|
Axis::Horizontal => {
|
||||||
let height_top =
|
let height_top = (rectangle.height * ratio - spacing / 2.0)
|
||||||
(rectangle.height * ratio - spacing / 2.0).round();
|
.round()
|
||||||
let height_bottom = rectangle.height - height_top - spacing;
|
.max(min_size_a)
|
||||||
|
.min(rectangle.height - min_size_b - spacing);
|
||||||
|
|
||||||
|
let height_bottom =
|
||||||
|
(rectangle.height - height_top - spacing).max(min_size_b);
|
||||||
|
|
||||||
|
let ratio = (height_top + spacing / 2.0) / rectangle.height;
|
||||||
|
|
||||||
(
|
(
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
|
@ -34,12 +42,19 @@ impl Axis {
|
||||||
height: height_bottom,
|
height: height_bottom,
|
||||||
..*rectangle
|
..*rectangle
|
||||||
},
|
},
|
||||||
|
ratio,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Axis::Vertical => {
|
Axis::Vertical => {
|
||||||
let width_left =
|
let width_left = (rectangle.width * ratio - spacing / 2.0)
|
||||||
(rectangle.width * ratio - spacing / 2.0).round();
|
.round()
|
||||||
let width_right = rectangle.width - width_left - spacing;
|
.max(min_size_a)
|
||||||
|
.min(rectangle.width - min_size_b - spacing);
|
||||||
|
|
||||||
|
let width_right =
|
||||||
|
(rectangle.width - width_left - spacing).max(min_size_b);
|
||||||
|
|
||||||
|
let ratio = (width_left + spacing / 2.0) / rectangle.width;
|
||||||
|
|
||||||
(
|
(
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
|
@ -51,6 +66,7 @@ impl Axis {
|
||||||
width: width_right,
|
width: width_right,
|
||||||
..*rectangle
|
..*rectangle
|
||||||
},
|
},
|
||||||
|
ratio,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -187,7 +203,8 @@ mod tests {
|
||||||
width: 10.0,
|
width: 10.0,
|
||||||
height: overall_height,
|
height: overall_height,
|
||||||
};
|
};
|
||||||
let (top, bottom) = a.split(&r, 0.5, spacing);
|
let (top, bottom, _ratio) =
|
||||||
|
a.split(&r, 0.5, spacing, 0.0, 0.0);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
top,
|
top,
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
|
@ -218,7 +235,8 @@ mod tests {
|
||||||
width: overall_width,
|
width: overall_width,
|
||||||
height: 10.0,
|
height: 10.0,
|
||||||
};
|
};
|
||||||
let (left, right) = a.split(&r, 0.5, spacing);
|
let (left, right, _ratio) =
|
||||||
|
a.split(&r, 0.5, spacing, 0.0, 0.0);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
left,
|
left,
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,33 @@ pub enum Node {
|
||||||
Pane(Pane),
|
Pane(Pane),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum Count {
|
||||||
|
Split {
|
||||||
|
horizontal: usize,
|
||||||
|
vertical: usize,
|
||||||
|
a: Box<Count>,
|
||||||
|
b: Box<Count>,
|
||||||
|
},
|
||||||
|
Pane,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Count {
|
||||||
|
fn horizontal(&self) -> usize {
|
||||||
|
match self {
|
||||||
|
Count::Split { horizontal, .. } => *horizontal,
|
||||||
|
Count::Pane => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vertical(&self) -> usize {
|
||||||
|
match self {
|
||||||
|
Count::Split { vertical, .. } => *vertical,
|
||||||
|
Count::Pane => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Node {
|
impl Node {
|
||||||
/// Returns an iterator over each [`Split`] in this [`Node`].
|
/// Returns an iterator over each [`Split`] in this [`Node`].
|
||||||
pub fn splits(&self) -> impl Iterator<Item = &Split> {
|
pub fn splits(&self) -> impl Iterator<Item = &Split> {
|
||||||
|
|
@ -48,23 +75,49 @@ impl Node {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn count(&self) -> Count {
|
||||||
|
match self {
|
||||||
|
Node::Split { a, b, axis, .. } => {
|
||||||
|
let a = a.count();
|
||||||
|
let b = b.count();
|
||||||
|
|
||||||
|
let (horizontal, vertical) = match axis {
|
||||||
|
Axis::Horizontal => (1, 0),
|
||||||
|
Axis::Vertical => (0, 1),
|
||||||
|
};
|
||||||
|
|
||||||
|
Count::Split {
|
||||||
|
horizontal: a.horizontal() + b.horizontal() + horizontal,
|
||||||
|
vertical: a.vertical() + b.vertical() + vertical,
|
||||||
|
a: Box::new(a),
|
||||||
|
b: Box::new(b),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Node::Pane(_) => Count::Pane,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the rectangular region for each [`Pane`] in the [`Node`] given
|
/// Returns the rectangular region for each [`Pane`] in the [`Node`] given
|
||||||
/// the spacing between panes and the total available space.
|
/// the spacing between panes and the total available space.
|
||||||
pub fn pane_regions(
|
pub fn pane_regions(
|
||||||
&self,
|
&self,
|
||||||
spacing: f32,
|
spacing: f32,
|
||||||
size: Size,
|
min_size: f32,
|
||||||
|
bounds: Size,
|
||||||
) -> BTreeMap<Pane, Rectangle> {
|
) -> BTreeMap<Pane, Rectangle> {
|
||||||
let mut regions = BTreeMap::new();
|
let mut regions = BTreeMap::new();
|
||||||
|
let count = self.count();
|
||||||
|
|
||||||
self.compute_regions(
|
self.compute_regions(
|
||||||
spacing,
|
spacing,
|
||||||
|
min_size,
|
||||||
&Rectangle {
|
&Rectangle {
|
||||||
x: 0.0,
|
x: 0.0,
|
||||||
y: 0.0,
|
y: 0.0,
|
||||||
width: size.width,
|
width: bounds.width,
|
||||||
height: size.height,
|
height: bounds.height,
|
||||||
},
|
},
|
||||||
|
&count,
|
||||||
&mut regions,
|
&mut regions,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -77,18 +130,22 @@ impl Node {
|
||||||
pub fn split_regions(
|
pub fn split_regions(
|
||||||
&self,
|
&self,
|
||||||
spacing: f32,
|
spacing: f32,
|
||||||
size: Size,
|
min_size: f32,
|
||||||
|
bounds: Size,
|
||||||
) -> BTreeMap<Split, (Axis, Rectangle, f32)> {
|
) -> BTreeMap<Split, (Axis, Rectangle, f32)> {
|
||||||
let mut splits = BTreeMap::new();
|
let mut splits = BTreeMap::new();
|
||||||
|
let count = self.count();
|
||||||
|
|
||||||
self.compute_splits(
|
self.compute_splits(
|
||||||
spacing,
|
spacing,
|
||||||
|
min_size,
|
||||||
&Rectangle {
|
&Rectangle {
|
||||||
x: 0.0,
|
x: 0.0,
|
||||||
y: 0.0,
|
y: 0.0,
|
||||||
width: size.width,
|
width: bounds.width,
|
||||||
height: size.height,
|
height: bounds.height,
|
||||||
},
|
},
|
||||||
|
&count,
|
||||||
&mut splits,
|
&mut splits,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -192,46 +249,104 @@ impl Node {
|
||||||
fn compute_regions(
|
fn compute_regions(
|
||||||
&self,
|
&self,
|
||||||
spacing: f32,
|
spacing: f32,
|
||||||
|
min_size: f32,
|
||||||
current: &Rectangle,
|
current: &Rectangle,
|
||||||
|
count: &Count,
|
||||||
regions: &mut BTreeMap<Pane, Rectangle>,
|
regions: &mut BTreeMap<Pane, Rectangle>,
|
||||||
) {
|
) {
|
||||||
match self {
|
match (self, count) {
|
||||||
Node::Split {
|
(
|
||||||
axis, ratio, a, b, ..
|
Node::Split {
|
||||||
} => {
|
axis, ratio, a, b, ..
|
||||||
let (region_a, region_b) = axis.split(current, *ratio, spacing);
|
},
|
||||||
|
Count::Split {
|
||||||
|
a: count_a,
|
||||||
|
b: count_b,
|
||||||
|
..
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
let (a_factor, b_factor) = match axis {
|
||||||
|
Axis::Horizontal => {
|
||||||
|
(count_a.horizontal(), count_b.horizontal())
|
||||||
|
}
|
||||||
|
Axis::Vertical => (count_a.vertical(), count_b.vertical()),
|
||||||
|
};
|
||||||
|
|
||||||
a.compute_regions(spacing, ®ion_a, regions);
|
let (region_a, region_b, _ratio) = axis.split(
|
||||||
b.compute_regions(spacing, ®ion_b, regions);
|
current,
|
||||||
|
*ratio,
|
||||||
|
spacing,
|
||||||
|
min_size * (a_factor + 1) as f32
|
||||||
|
+ spacing * a_factor as f32,
|
||||||
|
min_size * (b_factor + 1) as f32
|
||||||
|
+ spacing * b_factor as f32,
|
||||||
|
);
|
||||||
|
|
||||||
|
a.compute_regions(
|
||||||
|
spacing, min_size, ®ion_a, count_a, regions,
|
||||||
|
);
|
||||||
|
b.compute_regions(
|
||||||
|
spacing, min_size, ®ion_b, count_b, regions,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Node::Pane(pane) => {
|
(Node::Pane(pane), Count::Pane) => {
|
||||||
let _ = regions.insert(*pane, *current);
|
let _ = regions.insert(*pane, *current);
|
||||||
}
|
}
|
||||||
|
_ => {
|
||||||
|
unreachable!("Node configuration and count do not match")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compute_splits(
|
fn compute_splits(
|
||||||
&self,
|
&self,
|
||||||
spacing: f32,
|
spacing: f32,
|
||||||
|
min_size: f32,
|
||||||
current: &Rectangle,
|
current: &Rectangle,
|
||||||
|
count: &Count,
|
||||||
splits: &mut BTreeMap<Split, (Axis, Rectangle, f32)>,
|
splits: &mut BTreeMap<Split, (Axis, Rectangle, f32)>,
|
||||||
) {
|
) {
|
||||||
match self {
|
match (self, count) {
|
||||||
Node::Split {
|
(
|
||||||
axis,
|
Node::Split {
|
||||||
ratio,
|
axis,
|
||||||
a,
|
ratio,
|
||||||
b,
|
a,
|
||||||
id,
|
b,
|
||||||
} => {
|
id,
|
||||||
let (region_a, region_b) = axis.split(current, *ratio, spacing);
|
},
|
||||||
|
Count::Split {
|
||||||
|
a: count_a,
|
||||||
|
b: count_b,
|
||||||
|
..
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
let (a_factor, b_factor) = match axis {
|
||||||
|
Axis::Horizontal => {
|
||||||
|
(count_a.horizontal(), count_b.horizontal())
|
||||||
|
}
|
||||||
|
Axis::Vertical => (count_a.vertical(), count_b.vertical()),
|
||||||
|
};
|
||||||
|
|
||||||
let _ = splits.insert(*id, (*axis, *current, *ratio));
|
let (region_a, region_b, ratio) = axis.split(
|
||||||
|
current,
|
||||||
|
*ratio,
|
||||||
|
spacing,
|
||||||
|
min_size * (a_factor + 1) as f32
|
||||||
|
+ spacing * a_factor as f32,
|
||||||
|
min_size * (b_factor + 1) as f32
|
||||||
|
+ spacing * b_factor as f32,
|
||||||
|
);
|
||||||
|
|
||||||
a.compute_splits(spacing, ®ion_a, splits);
|
let _ = splits.insert(*id, (*axis, *current, ratio));
|
||||||
b.compute_splits(spacing, ®ion_b, splits);
|
|
||||||
|
a.compute_splits(spacing, min_size, ®ion_a, count_a, splits);
|
||||||
|
b.compute_splits(spacing, min_size, ®ion_b, count_b, splits);
|
||||||
|
}
|
||||||
|
(Node::Pane(_), Count::Pane) => {}
|
||||||
|
_ => {
|
||||||
|
unreachable!("Node configuration and split count do not match")
|
||||||
}
|
}
|
||||||
Node::Pane(_) => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -97,10 +97,11 @@ impl<T> State<T> {
|
||||||
/// Returns the adjacent [`Pane`] of another [`Pane`] in the given
|
/// Returns the adjacent [`Pane`] of another [`Pane`] in the given
|
||||||
/// direction, if there is one.
|
/// direction, if there is one.
|
||||||
pub fn adjacent(&self, pane: Pane, direction: Direction) -> Option<Pane> {
|
pub fn adjacent(&self, pane: Pane, direction: Direction) -> Option<Pane> {
|
||||||
let regions = self
|
let regions = self.internal.layout.pane_regions(
|
||||||
.internal
|
0.0,
|
||||||
.layout
|
0.0,
|
||||||
.pane_regions(0.0, Size::new(4096.0, 4096.0));
|
Size::new(4096.0, 4096.0),
|
||||||
|
);
|
||||||
|
|
||||||
let current_region = regions.get(&pane)?;
|
let current_region = regions.get(&pane)?;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue