diff --git a/examples/demo-site/Cargo.lock b/examples/demo-site/Cargo.lock index cadcacb..8f2425e 100644 --- a/examples/demo-site/Cargo.lock +++ b/examples/demo-site/Cargo.lock @@ -133,7 +133,7 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backer" -version = "0.9.0" +version = "0.10.0" [[package]] name = "base64" diff --git a/examples/egui-case-study/src/main.rs b/examples/egui-case-study/src/main.rs index ab00258..fa26f36 100644 --- a/examples/egui-case-study/src/main.rs +++ b/examples/egui-case-study/src/main.rs @@ -55,7 +55,7 @@ impl Default for MyApp { ] }) .collect(), - show_backer: false, + show_backer: true, } } } diff --git a/examples/macroquad-example/src/main.rs b/examples/macroquad-example/src/main.rs index 5275f8a..1e2a632 100644 --- a/examples/macroquad-example/src/main.rs +++ b/examples/macroquad-example/src/main.rs @@ -54,7 +54,11 @@ fn layout_for_highlight(ctx: &mut State) -> Node { row_spaced( 20., vec![ - scope::<_, _, HighlightScoper>(|highlight| rel_abs_seq(*highlight)), + if highlight == HighlightedCase::RelAbsSequence || highlight == HighlightedCase::None { + scope::<_, _, HighlightScoper>(|highlight| rel_abs_seq(*highlight)) + } else { + empty() + }, if highlight == HighlightedCase::AlignmentOffset || highlight == HighlightedCase::None { column_spaced( 10., @@ -105,30 +109,27 @@ fn layout_for_highlight(ctx: &mut State) -> Node { ) } -fn rel_abs_seq(highlight: HighlightedCase) -> Node { - if highlight == HighlightedCase::RelAbsSequence || highlight == HighlightedCase::None { - return column_spaced( - 10., - vec![ - text("Mixed (rel/abs) Sequence Constraints", 15., WHITE), - stack(vec![ - rect(BLUE), - column_spaced(10., vec![rect(WHITE), rect(WHITE).height(30.), rect(WHITE)]) - .pad(10.), - ]), - button("Fullscreen", |highlight: &mut HighlightedCase| { - if *highlight == HighlightedCase::RelAbsSequence { - *highlight = HighlightedCase::None; - } else { - *highlight = HighlightedCase::RelAbsSequence; - } - }) - .height(BTN_SIZE) - .align(Align::Bottom), - ], - ); - } - empty() +fn rel_abs_seq(_highlight: HighlightedCase) -> Node { + column_spaced( + 10., + vec![ + text("Mixed (rel/abs) Sequence Constraints", 15., WHITE), + stack(vec![ + rect(BLUE), + column_spaced(10., vec![rect(WHITE), rect(WHITE).height(30.), rect(WHITE)]) + .pad(10.), + ]), + button("Fullscreen", |highlight: &mut HighlightedCase| { + if *highlight == HighlightedCase::RelAbsSequence { + *highlight = HighlightedCase::None; + } else { + *highlight = HighlightedCase::RelAbsSequence; + } + }) + .height(BTN_SIZE) + .align(Align::Bottom), + ], + ) } fn text(string: &'static str, font_size: f32, color: Color) -> Node { diff --git a/src/constraints.rs b/src/constraints.rs index e748735..cb0e235 100644 --- a/src/constraints.rs +++ b/src/constraints.rs @@ -12,11 +12,29 @@ pub(crate) struct SizeConstraints { #[derive(Debug, Clone, Copy, PartialEq)] pub(crate) struct Constraint { - pub(crate) lower: Option, - pub(crate) upper: Option, + lower: Option, + upper: Option, } impl Constraint { + pub(crate) fn new(lower: Option, upper: Option) -> Self { + assert!(Self::check_constraints(lower, upper)); + Self { lower, upper } + } + pub(crate) fn get_lower(&self) -> Option { + self.lower + } + pub(crate) fn set_lower(&mut self, value: Option) { + assert!(Self::check_constraints(value, self.upper)); + self.lower = value; + } + pub(crate) fn get_upper(&self) -> Option { + self.upper + } + pub(crate) fn set_upper(&mut self, value: Option) { + assert!(Self::check_constraints(self.lower, value)); + self.upper = value; + } pub(crate) fn clamp(&self, value: f32) -> f32 { match (self.lower, self.upper) { (None, None) => value, @@ -25,6 +43,13 @@ impl Constraint { (Some(lower), Some(upper)) => value.clamp(lower, upper), } } + fn check_constraints(lower: Option, upper: Option) -> bool { + if let (Some(lower_unwrapped), Some(upper_unwrapped)) = (lower, upper) { + lower_unwrapped <= upper_unwrapped + } else { + true + } + } } impl NodeValue { @@ -46,24 +71,26 @@ impl NodeValue { NodeValue::Padding { amounts, element } => { let child = element.constraints(allocations[0], state, ctx); SizeConstraints { - width: Constraint { - lower: Some( - amounts.leading + child.width.lower.unwrap_or(0.) + amounts.trailing, - ), - upper: child + width: Constraint::new( + child .width - .upper + .get_lower() + .map(|lower| lower + amounts.leading + amounts.trailing), + child + .width + .get_upper() .map(|upper| upper + amounts.leading + amounts.trailing), - }, - height: Constraint { - lower: Some( - amounts.top + child.height.lower.unwrap_or(0.) + amounts.bottom, - ), - upper: child + ), + height: Constraint::new( + child + .height + .get_lower() + .map(|lower| lower + amounts.top + amounts.bottom), + child .height - .upper + .get_upper() .map(|upper| upper + amounts.top + amounts.bottom), - }, + ), aspect: None, } } @@ -147,14 +174,10 @@ impl NodeValue { height: Constraint::none(), aspect: None, }), - NodeValue::Explicit { options, element } => element - .constraints(allocations[0], state, ctx) - .combine_equal_priority(SizeConstraints::from_size( - options.clone(), - allocations[0], - state, - ctx, - )), + NodeValue::Explicit { options, element } => { + SizeConstraints::from_size(options.clone(), allocations[0], state, ctx) + .combine_explicit_with_child(element.constraints(allocations[0], state, ctx)) + } NodeValue::Offset { element, .. } => element.constraints(allocations[0], state, ctx), NodeValue::Scope { scoped } => scoped.constraints(allocations[0], state, ctx), NodeValue::Draw(_) | NodeValue::Space | NodeValue::AreaReader { .. } => { @@ -172,10 +195,7 @@ impl NodeValue { impl Constraint { pub(crate) fn none() -> Self { - Self { - lower: None, - upper: None, - } + Self::new(None, None) } } @@ -187,18 +207,18 @@ impl SizeConstraints { aspect: None, } } - pub(crate) fn combine_equal_priority(self, other: Self) -> Self { + pub(crate) fn combine_explicit_with_child(self, child: Self) -> Self { SizeConstraints { - width: self.width.combine_equal_priority(other.width), - height: self.height.combine_equal_priority(other.height), - aspect: self.aspect.or(other.aspect), + width: self.width.combine_explicit_with_child(child.width), + height: self.height.combine_explicit_with_child(child.height), + aspect: self.aspect.or(child.aspect), } } } impl Constraint { pub(crate) fn clamping(&self, value: f32) -> f32 { - match (self.lower, self.upper) { + match (self.get_lower(), self.get_upper()) { (None, None) => value, (None, Some(upper)) => value.min(upper), (Some(lower), None) => value.max(lower), @@ -210,44 +230,47 @@ impl Constraint { impl Constraint { pub(crate) fn combine_adjacent_priority(self, other: Self) -> Self { // This always takes the bigger bound - let lower = match (self.lower, other.lower) { + let lower = match (self.get_lower(), other.get_lower()) { (None, None) => None, (None, Some(a)) | (Some(a), None) => Some(a), (Some(bound_a), Some(bound_b)) => Some(bound_a.max(bound_b)), }; // In terms of upper constraints - no constraint is the biggest constraint - let upper = match (self.upper, other.upper) { + let upper = match (self.get_upper(), other.get_upper()) { (None, None) => None, (None, Some(_)) | (Some(_), None) => None, (Some(bound_a), Some(bound_b)) => Some(bound_a.max(bound_b)), }; - Constraint { lower, upper } + Constraint::new(lower, upper) } - pub(crate) fn combine_equal_priority(self, other: Self) -> Self { - let lower = match (self.lower, other.lower) { - (None, None) => None, - (None, Some(a)) | (Some(a), None) => Some(a), - (Some(bound_a), Some(bound_b)) => Some(bound_a.max(bound_b)), - }; - let upper = match (self.upper, other.upper) { - (None, None) => None, - (None, Some(a)) | (Some(a), None) => Some(a), - (Some(bound_a), Some(bound_b)) => Some(bound_a.max(bound_b)), - }; - Constraint { lower, upper } + pub(crate) fn combine_explicit_with_child(self, child: Self) -> Self { + // Child constraint is limited by parent constraint as it propogates up + // The parent can be thought of as a wrapper which hides the constraints of it's child + // + // For example: if there is no lower bound on the parent + // & the lower bound on the child is higher than the parent's upper bound + // we should limit the lower bound to the parent's upper bound + // + // The child can't override the parent + Constraint::new( + self.lower + .or(child.lower.map(|cl| cl.min(self.upper.unwrap_or(cl)))), + self.upper + .or(child.upper.map(|cl| cl.max(self.lower.unwrap_or(cl)))), + ) } pub(crate) fn combine_sum(self, other: Self, spacing: f32) -> Self { - let lower = match (self.lower, other.lower) { + let lower = match (self.get_lower(), other.get_lower()) { (None, None) => None, (None, Some(bound)) | (Some(bound), None) => Some(bound + spacing), (Some(bound_a), Some(bound_b)) => Some(bound_a + bound_b + spacing), }; - let upper = match (self.upper, other.upper) { + let upper = match (self.get_upper(), other.get_upper()) { (None, None) => None, (None, Some(_)) | (Some(_), None) => None, (Some(bound_a), Some(bound_b)) => Some(bound_a + bound_b + spacing), }; - Constraint { lower, upper } + Constraint::new(lower, upper) } } @@ -255,38 +278,26 @@ impl SizeConstraints { pub(crate) fn from_size(value: Size, area: Area, a: &mut A, b: &mut B) -> Self { let mut initial = SizeConstraints { width: if value.width_min.is_some() || value.width_max.is_some() { - Constraint { - lower: value.width_min, - upper: value.width_max, - } + Constraint::new(value.width_min, value.width_max) } else { - Constraint { - lower: None, - upper: None, - } + Constraint::none() }, height: if value.height_min.is_some() || value.height_max.is_some() { - Constraint { - lower: value.height_min, - upper: value.height_max, - } + Constraint::new(value.height_min, value.height_max) } else { - Constraint { - lower: None, - upper: None, - } + Constraint::none() }, aspect: value.aspect, }; if let Some(dynamic) = value.dynamic_height { let result = Some(initial.height.clamp(dynamic(area.width, a, b))); - initial.height.lower = result; - initial.height.upper = result; + initial.height.set_lower(result); + initial.height.set_upper(result); } if let Some(dynamic) = value.dynamic_width { let result = Some(initial.width.clamp(dynamic(area.height, a, b))); - initial.width.lower = result; - initial.width.upper = result; + initial.width.set_lower(result); + initial.width.set_upper(result); } initial } diff --git a/src/layout.rs b/src/layout.rs index 6510f70..423ba91 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -1,9 +1,5 @@ use crate::{ - constraints::{Constraint, SizeConstraints}, - drawable::Drawable, - models::*, - traits::NodeTrait, - Node, NodeWith, + constraints::SizeConstraints, drawable::Drawable, models::*, traits::NodeTrait, Node, NodeWith, }; use core::f32; use std::rc::Rc; @@ -192,8 +188,8 @@ impl NodeValue { available_area: Area, contextual_x_align: Option, contextual_y_align: Option, - a: &mut State, - b: &mut Ctx, + state: &mut State, + ctx: &mut Ctx, ) -> Vec { match self { NodeValue::Padding { amounts, .. } => vec![Area { @@ -214,8 +210,8 @@ impl NodeValue { Orientation::Vertical, off_axis_align.unwrap_or(XAlign::Center), align.unwrap_or(YAlign::Center), - a, - b, + state, + ctx, true, ), NodeValue::Row { @@ -230,8 +226,8 @@ impl NodeValue { Orientation::Horizontal, align.unwrap_or(XAlign::Center), off_axis_align.unwrap_or(YAlign::Center), - a, - b, + state, + ctx, true, ), NodeValue::Stack(children) => children.iter().map(|_| available_area).collect(), @@ -248,7 +244,7 @@ impl NodeValue { .or(contextual_y_align) .unwrap_or(YAlign::Center); let available_area = available_area.constrained( - &SizeConstraints::from_size(options.clone(), available_area, a, b), + &SizeConstraints::from_size(options.clone(), available_area, state, ctx), x_align, y_align, ); @@ -345,13 +341,16 @@ impl NodeValue { impl Area { fn constrained(self, constraints: &SizeConstraints, x_align: XAlign, y_align: YAlign) -> Self { - let mut width = match (constraints.width.lower, constraints.width.upper) { + let mut width = match (constraints.width.get_lower(), constraints.width.get_upper()) { (None, None) => self.width, (None, Some(upper)) => self.width.min(upper), (Some(lower), None) => self.width.max(lower), (Some(lower), Some(upper)) => self.width.clamp(lower, upper.max(lower)), }; - let mut height = match (constraints.height.lower, constraints.height.upper) { + let mut height = match ( + constraints.height.get_lower(), + constraints.height.get_upper(), + ) { (None, None) => self.height, (None, Some(upper)) => self.height.min(upper), (Some(lower), None) => self.height.max(lower), @@ -423,10 +422,8 @@ pub(crate) fn layout_axis( Orientation::Vertical => size_constraint.height, }; let mut final_size = Option::::None; - let Constraint { - mut lower, - mut upper, - } = constraint; + let mut lower = constraint.get_lower(); + let mut upper = constraint.get_upper(); if let Some(aspect) = size_constraint.aspect { match orientation { diff --git a/src/modifiers.rs b/src/modifiers.rs index 440856e..528097b 100644 --- a/src/modifiers.rs +++ b/src/modifiers.rs @@ -393,7 +393,7 @@ mod tests { .width_range(5.0..) .inner .constraints(Area::zero(), &mut (), &mut ()); - assert!(c.width.upper.is_none()); - assert_eq!(c.width.lower.unwrap(), 5.); + assert!(c.width.get_upper().is_none()); + assert_eq!(c.width.get_lower().unwrap(), 5.); } } diff --git a/src/tests/layout_tests.rs b/src/tests/layout_tests.rs index c294024..91001ad 100644 --- a/src/tests/layout_tests.rs +++ b/src/tests/layout_tests.rs @@ -588,4 +588,19 @@ mod tests { }) .draw(Area::new(0., 0., 100., 100.), &mut ()); } + #[test] + fn test_explicit_in_explicit() { + Layout::new(|()| { + draw(|a, _| { + assert_eq!(a, Area::new(40., 0., 20., 100.)); + }) + .width_range(20.0..) + .pad(0.) + .attach_under(draw(|a, _| { + assert_eq!(a, Area::new(45., 0., 10., 100.)); + })) + .width_range(..10.) + }) + .draw(Area::new(0., 0., 100., 100.), &mut ()); + } } diff --git a/src/tests/sequence_tests.rs b/src/tests/sequence_tests.rs index 1ef3aaa..b22fb49 100644 --- a/src/tests/sequence_tests.rs +++ b/src/tests/sequence_tests.rs @@ -396,10 +396,7 @@ mod tests { .constraints(Area::zero(), &mut (), &mut ()), SizeConstraints { width: Constraint::none(), - height: Constraint { - lower: Some(30.), - upper: None - }, + height: Constraint::new(Some(30.), None), aspect: None } ); @@ -409,10 +406,7 @@ mod tests { .constraints(Area::zero(), &mut (), &mut ()), SizeConstraints { width: Constraint::none(), - height: Constraint { - lower: Some(40.), - upper: Some(40.) - }, + height: Constraint::new(Some(40.), Some(40.)), aspect: None } ); @@ -421,10 +415,7 @@ mod tests { .inner .constraints(Area::zero(), &mut (), &mut ()), SizeConstraints { - width: Constraint { - lower: Some(10.), - upper: None - }, + width: Constraint::new(Some(10.), None), height: Constraint::none(), aspect: None } @@ -434,10 +425,7 @@ mod tests { .inner .constraints(Area::zero(), &mut (), &mut ()), SizeConstraints { - width: Constraint { - lower: Some(20.), - upper: Some(20.) - }, + width: Constraint::new(Some(20.), Some(20.)), height: Constraint::none(), aspect: None } @@ -448,10 +436,7 @@ mod tests { .constraints(Area::zero(), &mut (), &mut ()), SizeConstraints { width: Constraint::none(), - height: Constraint { - lower: Some(10.), - upper: None - }, + height: Constraint::new(Some(10.), None), aspect: None } ); @@ -460,14 +445,8 @@ mod tests { .inner .constraints(Area::zero(), &mut (), &mut ()), SizeConstraints { - width: Constraint { - lower: Some(10.), - upper: None - }, - height: Constraint { - lower: Some(20.), - upper: None - }, + width: Constraint::new(Some(10.), None), + height: Constraint::new(Some(20.), None), aspect: None } ); @@ -476,14 +455,8 @@ mod tests { .inner .constraints(Area::zero(), &mut (), &mut ()), SizeConstraints { - width: Constraint { - lower: None, - upper: None - }, - height: Constraint { - lower: Some(20.), - upper: Some(20.) - }, + width: Constraint::none(), + height: Constraint::new(Some(20.), Some(20.)), aspect: None } ); @@ -492,14 +465,24 @@ mod tests { .inner .constraints(Area::zero(), &mut (), &mut ()), SizeConstraints { - width: Constraint { - lower: Some(20.), - upper: Some(20.) - }, - height: Constraint { - lower: None, - upper: None - }, + width: Constraint::new(Some(20.), Some(20.)), + height: Constraint::none(), + aspect: None + } + ); + } + #[test] + fn test_explicit_in_explicit_conflict_parent_priority() { + assert_eq!( + space::<(), ()>() + .width_range(10.0..) + .pad(0.) + .width_range(..5.) + .inner + .constraints(Area::zero(), &mut (), &mut ()), + SizeConstraints { + width: Constraint::new(Some(5.), Some(5.)), + height: Constraint::none(), aspect: None } );