diff --git a/druid/examples/split_demo.rs b/druid/examples/split_demo.rs index 058e1cb471..490fc11355 100644 --- a/druid/examples/split_demo.rs +++ b/druid/examples/split_demo.rs @@ -47,14 +47,11 @@ fn build_app() -> impl Widget { Container::new( Split::vertical( Align::centered(Label::new("Split A")), - Split::vertical( - Align::centered(Label::new("Split B")), - Align::centered(Label::new("Split C")), - ) - .draggable(true), + Align::centered(Label::new("Split B")), ) - .split_point(0.33) - .draggable(true), + .split_point(0.5) + .draggable(true) + .min_size(60.0), ) .border(Color::WHITE, 1.0), ); diff --git a/druid/src/widget/flex.rs b/druid/src/widget/flex.rs index f8f8c5ef8e..bed299e696 100644 --- a/druid/src/widget/flex.rs +++ b/druid/src/widget/flex.rs @@ -64,21 +64,21 @@ struct Params { } impl Axis { - fn major(&self, coords: Size) -> f64 { + pub(crate) fn major(&self, coords: Size) -> f64 { match *self { Axis::Horizontal => coords.width, Axis::Vertical => coords.height, } } - fn minor(&self, coords: Size) -> f64 { + pub(crate) fn minor(&self, coords: Size) -> f64 { match *self { Axis::Horizontal => coords.height, Axis::Vertical => coords.width, } } - fn pack(&self, major: f64, minor: f64) -> (f64, f64) { + pub(crate) fn pack(&self, major: f64, minor: f64) -> (f64, f64) { match *self { Axis::Horizontal => (major, minor), Axis::Vertical => (minor, major), diff --git a/druid/src/widget/split.rs b/druid/src/widget/split.rs index 95f6b6aa30..70c43b4d32 100644 --- a/druid/src/widget/split.rs +++ b/druid/src/widget/split.rs @@ -25,6 +25,7 @@ use crate::{ pub struct Split { split_direction: Axis, draggable: bool, + min_size: f64, split_point: f64, splitter_size: f64, child1: WidgetPod>>, @@ -40,6 +41,7 @@ impl Split { ) -> Self { Split { split_direction, + min_size: 0.0, split_point: 0.5, splitter_size: 10.0, draggable: false, @@ -65,6 +67,15 @@ impl Split { self.split_point = split_point; self } + /// Builder-style method to set the minimum size for both sides of the split. + /// + /// The value must be greater than or equal to `0.0`. + pub fn min_size(mut self, min_size: f64) -> Self { + assert!(min_size >= 0.0); + self.min_size = min_size; + + self + } /// Set the width of the splitter bar, in pixels /// The value must be positive or zero pub fn splitter_size(mut self, splitter_size: f64) -> Self { @@ -92,33 +103,32 @@ impl Split { } } } + fn calculate_limits(&self, size: Size) -> (f64, f64) { + // Since the Axis::Direction tells us the direction of the splitter itself + // we need the minor axis to get the size of the 'splitted' direction + let size_in_splitted_direction = self.split_direction.minor(size); + + let min_offset = (self.splitter_size * 0.5).min(5.0); + let mut min_limit = self.min_size.max(min_offset); + let mut max_limit = (size_in_splitted_direction - self.min_size.max(min_offset)).max(0.0); + + if min_limit > max_limit { + min_limit = 0.5 * (min_limit + max_limit); + max_limit = min_limit; + } + + (min_limit, max_limit) + } + fn update_splitter(&mut self, size: Size, mouse_pos: Point) { self.split_point = match self.split_direction { Axis::Vertical => { - let max_limit = size.width - (self.splitter_size * 0.5).min(5.0); - let min_limit = (self.splitter_size * 0.5).min(5.0); - let max_split = max_limit / size.width; - let min_split = min_limit / size.width; - if mouse_pos.x > max_limit { - max_split - } else if mouse_pos.x < min_limit { - min_split - } else { - mouse_pos.x / size.width - } + let (min_limit, max_limit) = self.calculate_limits(size); + clamp(mouse_pos.x, min_limit, max_limit) / size.width } Axis::Horizontal => { - let max_limit = size.height - (self.splitter_size * 0.5).min(5.0); - let min_limit = (self.splitter_size * 0.5).min(5.0); - let max_split = max_limit / size.height; - let min_split = min_limit / size.height; - if mouse_pos.y > max_limit { - max_split - } else if mouse_pos.y < min_limit { - min_split - } else { - mouse_pos.y / size.height - } + let (min_limit, max_limit) = self.calculate_limits(size); + clamp(mouse_pos.y, min_limit, max_limit) / size.height } } } @@ -193,6 +203,7 @@ impl Widget for Split { bc.debug_check("Split"); let mut my_size = bc.max(); + let reduced_width = my_size.width - self.splitter_size; let reduced_height = my_size.height - self.splitter_size; let (child1_bc, child2_bc) = match self.split_direction { @@ -264,6 +275,34 @@ impl Widget for Split { let paint_rect = self.child1.paint_rect().union(self.child2.paint_rect()); let insets = paint_rect - Rect::ZERO.with_size(my_size); ctx.set_paint_insets(insets); + + // Update our splits to hold our constraints if needed + let (min_limit, max_limit) = self.calculate_limits(my_size); + self.split_point = match self.split_direction { + Axis::Vertical => { + if my_size.width <= std::f64::EPSILON { + 0.5 + } else { + clamp( + self.split_point, + min_limit / my_size.width, + max_limit / my_size.width, + ) + } + } + Axis::Horizontal => { + if my_size.height <= std::f64::EPSILON { + 0.5 + } else { + clamp( + self.split_point, + min_limit / my_size.height, + max_limit / my_size.height, + ) + } + } + }; + my_size } @@ -316,3 +355,15 @@ impl Widget for Split { self.child2.paint_with_offset(paint_ctx, &data, env); } } + +// Move to std lib clamp as soon as https://github.com/rust-lang/rust/issues/44095 lands +fn clamp(mut x: f64, min: f64, max: f64) -> f64 { + assert!(min <= max); + if x < min { + x = min; + } + if x > max { + x = max; + } + x +}