From 6b8cd3e3fcaafb4639cd2a186cde6e62e73ecc98 Mon Sep 17 00:00:00 2001 From: pyroxymat Date: Sun, 8 Mar 2020 12:24:01 +0100 Subject: [PATCH 1/4] Implement min size constraint for splitter --- druid/examples/split_demo.rs | 14 +++-- druid/src/widget/mod.rs | 2 +- druid/src/widget/split.rs | 101 +++++++++++++++++++++++++++-------- 3 files changed, 91 insertions(+), 26 deletions(-) diff --git a/druid/examples/split_demo.rs b/druid/examples/split_demo.rs index 058e1cb471..72cb702cbf 100644 --- a/druid/examples/split_demo.rs +++ b/druid/examples/split_demo.rs @@ -15,7 +15,7 @@ //! This example demonstrates the `Split` widget use druid::piet::Color; -use druid::widget::{Align, Container, Label, Padding, Split}; +use druid::widget::{Align, Container, Label, Padding, Split, SplitConstraints}; use druid::{AppLauncher, LocalizedString, Widget, WindowDesc}; fn build_app() -> impl Widget { @@ -51,10 +51,18 @@ fn build_app() -> impl Widget { Align::centered(Label::new("Split B")), Align::centered(Label::new("Split C")), ) - .draggable(true), + .draggable(true) + .split_constraints( + SplitConstraints::with_min_size(50.0), + SplitConstraints::with_min_size(50.0), + ), ) .split_point(0.33) - .draggable(true), + .draggable(true) + .split_constraints( + SplitConstraints::with_min_size(50.0), + SplitConstraints::with_min_size(100.0), + ), ) .border(Color::WHITE, 1.0), ); diff --git a/druid/src/widget/mod.rs b/druid/src/widget/mod.rs index 3d322959f9..025a85dee0 100644 --- a/druid/src/widget/mod.rs +++ b/druid/src/widget/mod.rs @@ -66,7 +66,7 @@ pub use radio::{Radio, RadioGroup}; pub use scroll::Scroll; pub use sized_box::SizedBox; pub use slider::Slider; -pub use split::Split; +pub use split::{Split, SplitConstraints}; pub use stepper::Stepper; #[cfg(feature = "svg")] #[cfg_attr(docsrs, doc(cfg(feature = "svg")))] diff --git a/druid/src/widget/split.rs b/druid/src/widget/split.rs index 95f6b6aa30..be405167d7 100644 --- a/druid/src/widget/split.rs +++ b/druid/src/widget/split.rs @@ -21,10 +21,28 @@ use crate::{ PaintCtx, RenderContext, UpdateCtx, Widget, WidgetPod, }; +pub struct SplitConstraints { + min_size: f64, +} + +impl Default for SplitConstraints { + fn default() -> Self { + Self { min_size: 0.0 } + } +} + +impl SplitConstraints { + pub fn with_min_size(min_size: f64) -> Self { + Self { min_size } + } +} + ///A container containing two other widgets, splitting the area either horizontally or vertically. pub struct Split { split_direction: Axis, draggable: bool, + constraints_left: SplitConstraints, + constraints_right: SplitConstraints, split_point: f64, splitter_size: f64, child1: WidgetPod>>, @@ -40,6 +58,8 @@ impl Split { ) -> Self { Split { split_direction, + constraints_left: SplitConstraints::default(), + constraints_right: SplitConstraints::default(), split_point: 0.5, splitter_size: 10.0, draggable: false, @@ -65,6 +85,13 @@ impl Split { self.split_point = split_point; self } + pub fn split_constraints(mut self, left: SplitConstraints, right: SplitConstraints) -> Self { + // TODO add checks + self.constraints_left = left; + self.constraints_right = right; + + 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 +119,34 @@ impl Split { } } } + fn calculate_limits(&self, size: Size) -> (f64, f64) { + let size_in_split_direction = match self.split_direction { + Axis::Vertical => size.width, + Axis::Horizontal => size.height, + }; + + let min_offset = (self.splitter_size * 0.5).min(5.0); + let mut min_limit = self.constraints_left.min_size.max(min_offset); + let mut max_limit = + size_in_split_direction - self.constraints_right.min_size.max(min_offset); + + if min_limit > max_limit { + min_limit = (min_limit + max_limit) / 2.0; + 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 +221,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 +293,22 @@ 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 => clamp( + self.split_point, + min_limit / my_size.width, + max_limit / my_size.width, + ), + Axis::Horizontal => clamp( + self.split_point, + min_limit / my_size.height, + max_limit / my_size.height, + ), + }; + my_size } @@ -316,3 +361,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 +} From 88832665de3b364195b34eee1b786b22fb390ab5 Mon Sep 17 00:00:00 2001 From: pyroxymat Date: Sun, 8 Mar 2020 23:23:47 +0100 Subject: [PATCH 2/4] Simplify split api for min size --- druid/examples/split_demo.rs | 19 +++------- druid/src/widget/mod.rs | 2 +- druid/src/widget/split.rs | 70 +++++++++++++++++------------------- 3 files changed, 37 insertions(+), 54 deletions(-) diff --git a/druid/examples/split_demo.rs b/druid/examples/split_demo.rs index 72cb702cbf..490fc11355 100644 --- a/druid/examples/split_demo.rs +++ b/druid/examples/split_demo.rs @@ -15,7 +15,7 @@ //! This example demonstrates the `Split` widget use druid::piet::Color; -use druid::widget::{Align, Container, Label, Padding, Split, SplitConstraints}; +use druid::widget::{Align, Container, Label, Padding, Split}; use druid::{AppLauncher, LocalizedString, Widget, WindowDesc}; fn build_app() -> impl Widget { @@ -47,22 +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) - .split_constraints( - SplitConstraints::with_min_size(50.0), - SplitConstraints::with_min_size(50.0), - ), + Align::centered(Label::new("Split B")), ) - .split_point(0.33) + .split_point(0.5) .draggable(true) - .split_constraints( - SplitConstraints::with_min_size(50.0), - SplitConstraints::with_min_size(100.0), - ), + .min_size(60.0), ) .border(Color::WHITE, 1.0), ); diff --git a/druid/src/widget/mod.rs b/druid/src/widget/mod.rs index 025a85dee0..3d322959f9 100644 --- a/druid/src/widget/mod.rs +++ b/druid/src/widget/mod.rs @@ -66,7 +66,7 @@ pub use radio::{Radio, RadioGroup}; pub use scroll::Scroll; pub use sized_box::SizedBox; pub use slider::Slider; -pub use split::{Split, SplitConstraints}; +pub use split::Split; pub use stepper::Stepper; #[cfg(feature = "svg")] #[cfg_attr(docsrs, doc(cfg(feature = "svg")))] diff --git a/druid/src/widget/split.rs b/druid/src/widget/split.rs index be405167d7..485e4c75e2 100644 --- a/druid/src/widget/split.rs +++ b/druid/src/widget/split.rs @@ -21,28 +21,11 @@ use crate::{ PaintCtx, RenderContext, UpdateCtx, Widget, WidgetPod, }; -pub struct SplitConstraints { - min_size: f64, -} - -impl Default for SplitConstraints { - fn default() -> Self { - Self { min_size: 0.0 } - } -} - -impl SplitConstraints { - pub fn with_min_size(min_size: f64) -> Self { - Self { min_size } - } -} - ///A container containing two other widgets, splitting the area either horizontally or vertically. pub struct Split { split_direction: Axis, draggable: bool, - constraints_left: SplitConstraints, - constraints_right: SplitConstraints, + min_size: f64, split_point: f64, splitter_size: f64, child1: WidgetPod>>, @@ -58,8 +41,7 @@ impl Split { ) -> Self { Split { split_direction, - constraints_left: SplitConstraints::default(), - constraints_right: SplitConstraints::default(), + min_size: 0.0, split_point: 0.5, splitter_size: 10.0, draggable: false, @@ -85,10 +67,11 @@ impl Split { self.split_point = split_point; self } - pub fn split_constraints(mut self, left: SplitConstraints, right: SplitConstraints) -> Self { - // TODO add checks - self.constraints_left = left; - self.constraints_right = right; + /// Set the minimum size for both sides of the split + /// The value must be atleast 0.0 + pub fn min_size(mut self, min_size: f64) -> Self { + assert!(min_size >= 0.0); + self.min_size = min_size; self } @@ -126,12 +109,11 @@ impl Split { }; let min_offset = (self.splitter_size * 0.5).min(5.0); - let mut min_limit = self.constraints_left.min_size.max(min_offset); - let mut max_limit = - size_in_split_direction - self.constraints_right.min_size.max(min_offset); + let mut min_limit = self.min_size.max(min_offset); + let mut max_limit = (size_in_split_direction - self.min_size.max(min_offset)).max(0.0); if min_limit > max_limit { - min_limit = (min_limit + max_limit) / 2.0; + min_limit = 0.5 * (min_limit + max_limit); max_limit = min_limit; } @@ -297,16 +279,28 @@ impl Widget for Split { // 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 => clamp( - self.split_point, - min_limit / my_size.width, - max_limit / my_size.width, - ), - Axis::Horizontal => clamp( - self.split_point, - min_limit / my_size.height, - max_limit / my_size.height, - ), + 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 From 6d9caa8e8283720e0c37ac747705bc62ea87e003 Mon Sep 17 00:00:00 2001 From: pyroxymat Date: Mon, 9 Mar 2020 22:20:37 +0100 Subject: [PATCH 3/4] Process feedback about split min size --- druid/src/widget/flex.rs | 6 +++--- druid/src/widget/split.rs | 11 +++++------ 2 files changed, 8 insertions(+), 9 deletions(-) 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 485e4c75e2..12c675f1fa 100644 --- a/druid/src/widget/split.rs +++ b/druid/src/widget/split.rs @@ -68,7 +68,7 @@ impl Split { self } /// Set the minimum size for both sides of the split - /// The value must be atleast 0.0 + /// 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; @@ -103,14 +103,13 @@ impl Split { } } fn calculate_limits(&self, size: Size) -> (f64, f64) { - let size_in_split_direction = match self.split_direction { - Axis::Vertical => size.width, - Axis::Horizontal => size.height, - }; + // 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_split_direction - self.min_size.max(min_offset)).max(0.0); + 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); From 477b0e9b5f7a472c3981fba666e662f917456115 Mon Sep 17 00:00:00 2001 From: Colin Rofls Date: Mon, 9 Mar 2020 21:14:50 -0400 Subject: [PATCH 4/4] Docs fixup --- druid/src/widget/split.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/druid/src/widget/split.rs b/druid/src/widget/split.rs index 12c675f1fa..70c43b4d32 100644 --- a/druid/src/widget/split.rs +++ b/druid/src/widget/split.rs @@ -67,7 +67,8 @@ impl Split { self.split_point = split_point; self } - /// Set the minimum size for both sides of the split + /// 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);