From 0d632b233273f2fe9de20d55ef72bcc75c431e29 Mon Sep 17 00:00:00 2001 From: marc2332 Date: Sun, 4 Aug 2024 16:32:03 +0200 Subject: [PATCH 01/14] fix(torin): Consider `padding` when accumulating inner sizes, used in e.g scroll views --- crates/torin/src/dom_adapter.rs | 2 +- crates/torin/src/geometry.rs | 31 ++++++++++++++++++++----------- crates/torin/src/measure.rs | 7 ++++--- 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/crates/torin/src/dom_adapter.rs b/crates/torin/src/dom_adapter.rs index fb95167c3..bd76a3993 100644 --- a/crates/torin/src/dom_adapter.rs +++ b/crates/torin/src/dom_adapter.rs @@ -46,7 +46,7 @@ impl PartialEq for LayoutNode { impl LayoutNode { // The area without any margin pub fn visible_area(&self) -> Area { - self.area.after_gaps(&self.margin) + self.area.without_gaps(&self.margin) } } diff --git a/crates/torin/src/geometry.rs b/crates/torin/src/geometry.rs index 88033dd25..eafc07765 100644 --- a/crates/torin/src/geometry.rs +++ b/crates/torin/src/geometry.rs @@ -14,13 +14,13 @@ pub type CursorPoint = euclid::Point2D; pub type Length = euclid::Length; pub trait AreaModel { - // The area without any outer gap (e.g margin) - fn after_gaps(&self, margin: &Gaps) -> Area; + /// The area without any outer gap (e.g margin) + fn without_gaps(&self, gap: &Gaps) -> Area; - // Adjust the available area with the node offsets (mainly used by scrollviews) + /// Adjust the available area with the node offsets (mainly used by scrollviews) fn move_with_offsets(&mut self, offset_x: &Length, offset_y: &Length); - // Align the content of this node. + /// Align the content of this node. fn align_content( &mut self, available_area: &Area, @@ -30,7 +30,7 @@ pub trait AreaModel { alignment_direction: AlignmentDirection, ); - // Align the position of this node. + /// Align the position of this node. #[allow(clippy::too_many_arguments)] fn align_position( &mut self, @@ -45,20 +45,18 @@ pub trait AreaModel { } impl AreaModel for Area { - /// Get the area inside after including the gaps (margins or paddings) - fn after_gaps(&self, margin: &Gaps) -> Area { + fn without_gaps(&self, gaps: &Gaps) -> Area { let origin = self.origin; let size = self.size; Area::new( - Point2D::new(origin.x + margin.left(), origin.y + margin.top()), + Point2D::new(origin.x + gaps.left(), origin.y + gaps.top()), Size2D::new( - size.width - margin.horizontal(), - size.height - margin.vertical(), + size.width - gaps.horizontal(), + size.height - gaps.vertical(), ), ) } - /// Get the area inside after including the gaps (margins or paddings) fn move_with_offsets(&mut self, offset_x: &Length, offset_y: &Length) { self.origin.x += offset_x.get(); self.origin.y += offset_y.get(); @@ -190,3 +188,14 @@ pub enum AlignAxis { Height, Width, } + +pub trait SizeModel { + /// Get the size with the given gap, e.g padding. + fn with_gaps(&self, gap: &Gaps) -> Size2D; +} + +impl SizeModel for Size2D { + fn with_gaps(&self, gap: &Gaps) -> Size2D { + Size2D::new(self.width + gap.horizontal(), self.height + gap.vertical()) + } +} diff --git a/crates/torin/src/measure.rs b/crates/torin/src/measure.rs index d5e173311..2f9f1ab27 100644 --- a/crates/torin/src/measure.rs +++ b/crates/torin/src/measure.rs @@ -18,6 +18,7 @@ use crate::{ AlignmentDirection, AreaModel, LayoutMetadata, + SizeModel, Torin, }, }; @@ -184,10 +185,10 @@ pub fn measure_node( .get_origin(available_parent_area, parent_area, &area_size); let mut area = Rect::new(area_origin, area_size); let mut inner_area = Rect::new(area_origin, inner_size) - .after_gaps(&node.padding) - .after_gaps(&node.margin); + .without_gaps(&node.padding) + .without_gaps(&node.margin); - let mut inner_sizes = Size2D::default(); + let mut inner_sizes = Size2D::default().with_gaps(&node.padding); if measure_inner_children && phase_measure_inner_children { // Create an area containing the available space inside the inner area From 6652b2a3bc60c92aa8ffbb28ce5a12c68fd3d075 Mon Sep 17 00:00:00 2001 From: marc2332 Date: Sun, 4 Aug 2024 17:25:26 +0200 Subject: [PATCH 02/14] experiments --- crates/torin/src/geometry.rs | 24 +++++++++++------------- crates/torin/src/measure.rs | 4 +++- crates/torin/tests/alignment.rs | 4 ++-- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/crates/torin/src/geometry.rs b/crates/torin/src/geometry.rs index eafc07765..064905080 100644 --- a/crates/torin/src/geometry.rs +++ b/crates/torin/src/geometry.rs @@ -15,7 +15,7 @@ pub type Length = euclid::Length; pub trait AreaModel { /// The area without any outer gap (e.g margin) - fn without_gaps(&self, gap: &Gaps) -> Area; + fn without_gaps(self, gap: &Gaps) -> Area; /// Adjust the available area with the node offsets (mainly used by scrollviews) fn move_with_offsets(&mut self, offset_x: &Length, offset_y: &Length); @@ -45,7 +45,7 @@ pub trait AreaModel { } impl AreaModel for Area { - fn without_gaps(&self, gaps: &Gaps) -> Area { + fn without_gaps(self, gaps: &Gaps) -> Area { let origin = self.origin; let size = self.size; Area::new( @@ -64,7 +64,7 @@ impl AreaModel for Area { fn align_content( &mut self, - available_area: &Area, + inner_area: &Area, contents_size: &Size2D, alignment: &Alignment, direction: &DirectionMode, @@ -75,24 +75,22 @@ impl AreaModel for Area { match axis { AlignAxis::Height => match alignment { Alignment::Center => { - let new_origin_y = - (available_area.height() / 2.0) - (contents_size.height / 2.0); + let new_origin_y = (inner_area.height() / 2.0) - (contents_size.height / 2.0); - self.origin.y = available_area.min_y() + new_origin_y; + self.origin.y = inner_area.min_y() + new_origin_y; } Alignment::End => { - self.origin.y = available_area.max_y() - contents_size.height; + self.origin.y = inner_area.max_y() - contents_size.height; } _ => {} }, AlignAxis::Width => match alignment { Alignment::Center => { - let new_origin_x = (available_area.width() / 2.0) - (contents_size.width / 2.0); - - self.origin.x = available_area.min_x() + new_origin_x; + let new_origin_x = (inner_area.width() / 2.0) - (contents_size.width / 2.0); + self.origin.x = inner_area.min_x() + new_origin_x; } Alignment::End => { - self.origin.x = available_area.max_x() - contents_size.width; + self.origin.x = inner_area.max_x() - contents_size.width; } _ => {} }, @@ -191,11 +189,11 @@ pub enum AlignAxis { pub trait SizeModel { /// Get the size with the given gap, e.g padding. - fn with_gaps(&self, gap: &Gaps) -> Size2D; + fn with_gaps(self, gap: &Gaps) -> Size2D; } impl SizeModel for Size2D { - fn with_gaps(&self, gap: &Gaps) -> Size2D { + fn with_gaps(self, gap: &Gaps) -> Size2D { Size2D::new(self.width + gap.horizontal(), self.height + gap.vertical()) } } diff --git a/crates/torin/src/measure.rs b/crates/torin/src/measure.rs index 2f9f1ab27..79c039e69 100644 --- a/crates/torin/src/measure.rs +++ b/crates/torin/src/measure.rs @@ -343,6 +343,8 @@ pub fn measure_inner_nodes( } if parent_node.main_alignment.is_not_start() { + let inner_area = *initial_phase_mode.inner_area(); + // 2. Adjust the available and inner areas of the Main axis initial_phase_mode.fit_bounds_when_unspecified( parent_node, @@ -352,7 +354,7 @@ pub fn measure_inner_nodes( // 3. Align the Main axis available_area.align_content( - initial_phase_mode.inner_area(), + &inner_area, &initial_phase_inner_sizes, &parent_node.main_alignment, &parent_node.direction, diff --git a/crates/torin/tests/alignment.rs b/crates/torin/tests/alignment.rs index 9596dd9ac..f0fa39334 100644 --- a/crates/torin/tests/alignment.rs +++ b/crates/torin/tests/alignment.rs @@ -273,12 +273,12 @@ pub fn unsized_alignment() { assert_eq!( layout.get(1).unwrap().visible_area(), - Rect::new(Point2D::new(15.0, 75.0), Size2D::new(100.0, 50.0)), + Rect::new(Point2D::new(10.0, 75.0), Size2D::new(100.0, 50.0)), ); assert_eq!( layout.get(2).unwrap().visible_area(), - Rect::new(Point2D::new(115.0, 25.0), Size2D::new(150.0, 80.0)), + Rect::new(Point2D::new(110.0, 25.0), Size2D::new(150.0, 80.0)), ); } From ae2874d9be10a55906561d9dc2deb66b4ba24534 Mon Sep 17 00:00:00 2001 From: marc2332 Date: Mon, 5 Aug 2024 23:56:47 +0200 Subject: [PATCH 03/14] refactor: Start refactoring Torin --- crates/torin/src/geometry.rs | 181 ++++++++++++++++++++++++- crates/torin/src/lib.rs | 1 - crates/torin/src/measure.rs | 57 ++++---- crates/torin/src/measure_mode.rs | 226 ------------------------------- 4 files changed, 205 insertions(+), 260 deletions(-) delete mode 100644 crates/torin/src/measure_mode.rs diff --git a/crates/torin/src/geometry.rs b/crates/torin/src/geometry.rs index 064905080..e62e02b6a 100644 --- a/crates/torin/src/geometry.rs +++ b/crates/torin/src/geometry.rs @@ -1,7 +1,10 @@ -use crate::prelude::{ - Alignment, - DirectionMode, - Gaps, +use crate::{ + node::Node, + prelude::{ + Alignment, + DirectionMode, + Gaps, + }, }; #[derive(PartialEq)] @@ -42,6 +45,24 @@ pub trait AreaModel { siblings_len: usize, child_position: usize, ); + + fn stack_into_node( + &mut self, + parent_node: &Node, + parent_area: &mut Area, + inner_area: &mut Area, + content_area: &Area, + inner_sizes: &mut Size2D, + node_data: &Node, + ); + + fn fit_bounds_when_unspecified( + &mut self, + parent_area: &mut Area, + inner_area: &mut Area, + parent_node: &Node, + alignment_direction: AlignmentDirection, + ); } impl AreaModel for Area { @@ -158,6 +179,158 @@ impl AreaModel for Area { }, } } + /// Stack a Node into another Node + fn stack_into_node( + &mut self, + parent_node: &Node, + parent_area: &mut Area, + inner_area: &mut Area, + content_area: &Area, + inner_sizes: &mut Size2D, + node_data: &Node, + ) { + if node_data.position.is_absolute() { + return; + } + + match parent_node.direction { + DirectionMode::Horizontal => { + // Move the available area + self.origin.x = content_area.max_x(); + self.size.width -= content_area.size.width; + + inner_sizes.height = content_area.height().max(inner_sizes.height); + inner_sizes.width += content_area.width(); + + // Keep the biggest height + if parent_node.height.inner_sized() { + parent_area.size.height = parent_area.size.height.max( + content_area.size.height + + parent_node.padding.vertical() + + parent_node.margin.vertical(), + ); + // Keep the inner area in sync + inner_area.size.height = parent_area.size.height + - parent_node.padding.vertical() + - parent_node.margin.vertical(); + } + + // Accumulate width + if parent_node.width.inner_sized() { + parent_area.size.width += content_area.size.width; + } + } + DirectionMode::Vertical => { + // Move the available area + self.origin.y = content_area.max_y(); + self.size.height -= content_area.size.height; + + inner_sizes.width = content_area.width().max(inner_sizes.width); + inner_sizes.height += content_area.height(); + + // Keep the biggest width + if parent_node.width.inner_sized() { + parent_area.size.width = parent_area.size.width.max( + content_area.size.width + + parent_node.padding.horizontal() + + parent_node.margin.horizontal(), + ); + // Keep the inner area in sync + inner_area.size.width = parent_area.size.width + - parent_node.padding.horizontal() + - parent_node.margin.horizontal(); + } + + // Accumulate height + if parent_node.height.inner_sized() { + parent_area.size.height += content_area.size.height; + } + } + } + } + + /// This will fit the available area and inner area of a parent node when for example height is set to "auto", + /// direction is vertical and main_alignment is set to "center" or "end" or the content is set to "fit". + /// The intended usage is to call this after the first measurement and before the second, + /// this way the second measurement will align the content relatively to the parent element instead + /// of overflowing due to being aligned relatively to the upper parent element + fn fit_bounds_when_unspecified( + &mut self, + parent_area: &mut Area, + inner_area: &mut Area, + parent_node: &Node, + alignment_direction: AlignmentDirection, + ) { + struct NodeData<'a> { + pub inner_origin: &'a mut f32, + pub inner_size: &'a mut f32, + pub area_origin: &'a mut f32, + pub area_size: &'a mut f32, + pub one_side_padding: f32, + pub two_sides_padding: f32, + pub one_side_margin: f32, + pub two_sides_margin: f32, + pub available_size: &'a mut f32, + } + + let axis = get_align_axis(&parent_node.direction, alignment_direction); + let (is_vertical_not_start, is_horizontal_not_start) = match parent_node.direction { + DirectionMode::Vertical => ( + parent_node.main_alignment.is_not_start(), + parent_node.cross_alignment.is_not_start() || parent_node.content.is_fit(), + ), + DirectionMode::Horizontal => ( + parent_node.cross_alignment.is_not_start() || parent_node.content.is_fit(), + parent_node.main_alignment.is_not_start(), + ), + }; + let NodeData { + inner_origin, + inner_size, + area_origin, + area_size, + one_side_padding, + two_sides_padding, + one_side_margin, + two_sides_margin, + available_size, + } = match axis { + AlignAxis::Height if parent_node.height.inner_sized() && is_vertical_not_start => { + NodeData { + inner_origin: &mut inner_area.origin.y, + inner_size: &mut inner_area.size.height, + area_origin: &mut parent_area.origin.y, + area_size: &mut parent_area.size.height, + one_side_padding: parent_node.padding.top(), + two_sides_padding: parent_node.padding.vertical(), + one_side_margin: parent_node.margin.top(), + two_sides_margin: parent_node.margin.vertical(), + available_size: &mut self.size.height, + } + } + AlignAxis::Width if parent_node.width.inner_sized() && is_horizontal_not_start => { + NodeData { + inner_origin: &mut inner_area.origin.x, + inner_size: &mut inner_area.size.width, + area_origin: &mut parent_area.origin.x, + area_size: &mut parent_area.size.width, + one_side_padding: parent_node.padding.left(), + two_sides_padding: parent_node.padding.horizontal(), + one_side_margin: parent_node.margin.left(), + two_sides_margin: parent_node.margin.horizontal(), + available_size: &mut self.size.width, + } + } + _ => return, + }; + + // Set the origin of the inner area to the origin of the area plus the padding and margin for the given axis + *inner_origin = *area_origin + one_side_padding + one_side_margin; + // Set the size of the inner area to the size of the area minus the padding and margin for the given axis + *inner_size = *area_size - two_sides_padding - two_sides_margin; + // Set the same available size as the inner area for the given axis + *available_size = *inner_size; + } } pub fn get_align_axis( diff --git a/crates/torin/src/lib.rs b/crates/torin/src/lib.rs index 0911b91c7..0b9f3f996 100644 --- a/crates/torin/src/lib.rs +++ b/crates/torin/src/lib.rs @@ -2,7 +2,6 @@ pub mod custom_measurer; pub mod dom_adapter; pub mod geometry; mod measure; -mod measure_mode; pub mod node; pub mod scaled; pub mod torin; diff --git a/crates/torin/src/measure.rs b/crates/torin/src/measure.rs index 79c039e69..e5dbba5f5 100644 --- a/crates/torin/src/measure.rs +++ b/crates/torin/src/measure.rs @@ -12,7 +12,6 @@ use crate::{ Area, Size2D, }, - measure_mode::MeasureMode, node::Node, prelude::{ AlignmentDirection, @@ -188,7 +187,7 @@ pub fn measure_node( .without_gaps(&node.padding) .without_gaps(&node.margin); - let mut inner_sizes = Size2D::default().with_gaps(&node.padding); + let mut inner_sizes = Size2D::default(); if measure_inner_children && phase_measure_inner_children { // Create an area containing the available space inside the inner area @@ -196,11 +195,6 @@ pub fn measure_node( available_area.move_with_offsets(&node.offset_x, &node.offset_y); - let mut measurement_mode = MeasureMode::ParentIsNotCached { - area: &mut area, - inner_area: &mut inner_area, - }; - // Measure the layout of this Node's children measure_inner_nodes( &node_id, @@ -210,7 +204,8 @@ pub fn measure_node( &mut inner_sizes, measurer, must_cache_inner_nodes, - &mut measurement_mode, + &mut area, + &mut inner_area, dom_adapter, layout_metadata, true, @@ -228,17 +223,15 @@ pub fn measure_node( }, ) } else { - let layout_node = layout.get(node_id).unwrap().clone(); + let mut layout_node = layout.get(node_id).unwrap().clone(); let mut inner_sizes = layout_node.inner_sizes; let mut available_area = layout_node.inner_area; + let mut area = &mut layout_node.area; + let mut inner_area = &mut layout_node.inner_area; available_area.move_with_offsets(&node.offset_x, &node.offset_y); - let mut measurement_mode = MeasureMode::ParentIsCached { - inner_area: &layout_node.inner_area, - }; - let measure_inner_children = if let Some(measurer) = measurer { measurer.should_measure_inner_children(node_id) } else { @@ -254,7 +247,8 @@ pub fn measure_node( &mut inner_sizes, measurer, must_cache_inner_nodes, - &mut measurement_mode, + &mut area, + &mut inner_area, dom_adapter, layout_metadata, false, @@ -279,7 +273,10 @@ pub fn measure_inner_nodes( measurer: &mut Option>, // Whether to cache the measurements of this Node's children must_cache_inner_nodes: bool, - mode: &mut MeasureMode, + + area: &mut Area, + + inner_area: &mut Area, // Adapter for the provided DOM dom_adapter: &mut impl DOMAdapter, @@ -298,8 +295,8 @@ pub fn measure_inner_nodes( || parent_node.main_alignment.is_not_start() || parent_node.content.is_fit() { - let mut initial_phase_mode = mode.to_owned(); - let mut initial_phase_mode = initial_phase_mode.to_mut(); + let mut initial_phase_area = *area; + let mut initial_phase_inner_area = *inner_area; let mut initial_phase_available_area = *available_area; // 1. Measure the children @@ -312,7 +309,7 @@ pub fn measure_inner_nodes( continue; } - let inner_area = *initial_phase_mode.inner_area(); + let inner_area = initial_phase_inner_area; let (_, child_areas) = measure_node( *child_id, @@ -328,9 +325,10 @@ pub fn measure_inner_nodes( Phase::Initial, ); - initial_phase_mode.stack_into_node( + initial_phase_available_area.stack_into_node( parent_node, - &mut initial_phase_available_area, + &mut initial_phase_area, + &mut initial_phase_inner_area, &child_areas.area, &mut initial_phase_inner_sizes, &child_data, @@ -343,13 +341,14 @@ pub fn measure_inner_nodes( } if parent_node.main_alignment.is_not_start() { - let inner_area = *initial_phase_mode.inner_area(); + let inner_area = initial_phase_inner_area; // 2. Adjust the available and inner areas of the Main axis - initial_phase_mode.fit_bounds_when_unspecified( + available_area.fit_bounds_when_unspecified( + &mut initial_phase_area, + &mut initial_phase_inner_area, parent_node, AlignmentDirection::Main, - available_area, ); // 3. Align the Main axis @@ -364,10 +363,11 @@ pub fn measure_inner_nodes( if parent_node.cross_alignment.is_not_start() || parent_node.content.is_fit() { // 4. Adjust the available and inner areas of the Cross axis - initial_phase_mode.fit_bounds_when_unspecified( + available_area.fit_bounds_when_unspecified( + &mut initial_phase_area, + &mut initial_phase_inner_area, parent_node, AlignmentDirection::Cross, - available_area, ); } } @@ -410,8 +410,6 @@ pub fn measure_inner_nodes( } } - let inner_area = *mode.inner_area(); - // Final measurement let (child_revalidated, child_areas) = measure_node( child_id, @@ -428,9 +426,10 @@ pub fn measure_inner_nodes( ); // Stack the child into its parent - mode.stack_into_node( + available_area.stack_into_node( parent_node, - available_area, + area, + inner_area, &child_areas.area, inner_sizes, &child_data, diff --git a/crates/torin/src/measure_mode.rs b/crates/torin/src/measure_mode.rs deleted file mode 100644 index b2d7eef57..000000000 --- a/crates/torin/src/measure_mode.rs +++ /dev/null @@ -1,226 +0,0 @@ -use crate::prelude::{ - get_align_axis, - AlignAxis, - AlignmentDirection, - Area, - DirectionMode, - Node, - Size2D, -}; - -/// Measurement data for the inner Nodes of a Node -#[derive(Debug)] -pub enum MeasureMode<'a> { - ParentIsCached { - inner_area: &'a Area, - }, - ParentIsNotCached { - area: &'a mut Area, - inner_area: &'a mut Area, - }, -} - -impl<'a> MeasureMode<'a> { - /// Get a reference to the inner area - pub fn inner_area(&'a self) -> &'a Area { - match self { - Self::ParentIsCached { inner_area } => inner_area, - Self::ParentIsNotCached { inner_area, .. } => inner_area, - } - } - - /// Create an owned version of [MeasureMode] - pub fn to_owned(&self) -> OwnedMeasureMode { - match self { - MeasureMode::ParentIsCached { inner_area } => OwnedMeasureMode::ParentIsCached { - inner_area: *inner_area.to_owned(), - }, - MeasureMode::ParentIsNotCached { area, inner_area } => { - OwnedMeasureMode::ParentIsNotCached { - area: **area, - inner_area: **inner_area, - } - } - } - } - - /// This will fit the available area and inner area of a parent node when for example height is set to "auto", - /// direction is vertical and main_alignment is set to "center" or "end" or the content is set to "fit". - /// The intended usage is to call this after the first measurement and before the second, - /// this way the second measurement will align the content relatively to the parent element instead - /// of overflowing due to being aligned relatively to the upper parent element - pub fn fit_bounds_when_unspecified( - &mut self, - parent_node: &Node, - alignment_direction: AlignmentDirection, - available_area: &mut Area, - ) { - struct NodeData<'a> { - pub inner_origin: &'a mut f32, - pub inner_size: &'a mut f32, - pub area_origin: &'a mut f32, - pub area_size: &'a mut f32, - pub one_side_padding: f32, - pub two_sides_padding: f32, - pub one_side_margin: f32, - pub two_sides_margin: f32, - pub available_size: &'a mut f32, - } - - let axis = get_align_axis(&parent_node.direction, alignment_direction); - let (is_vertical_not_start, is_horizontal_not_start) = match parent_node.direction { - DirectionMode::Vertical => ( - parent_node.main_alignment.is_not_start(), - parent_node.cross_alignment.is_not_start() || parent_node.content.is_fit(), - ), - DirectionMode::Horizontal => ( - parent_node.cross_alignment.is_not_start() || parent_node.content.is_fit(), - parent_node.main_alignment.is_not_start(), - ), - }; - let params = if let MeasureMode::ParentIsNotCached { area, inner_area } = self { - match axis { - AlignAxis::Height if parent_node.height.inner_sized() && is_vertical_not_start => { - Some(NodeData { - inner_origin: &mut inner_area.origin.y, - inner_size: &mut inner_area.size.height, - area_origin: &mut area.origin.y, - area_size: &mut area.size.height, - one_side_padding: parent_node.padding.top(), - two_sides_padding: parent_node.padding.vertical(), - one_side_margin: parent_node.margin.top(), - two_sides_margin: parent_node.margin.vertical(), - available_size: &mut available_area.size.height, - }) - } - AlignAxis::Width if parent_node.width.inner_sized() && is_horizontal_not_start => { - Some(NodeData { - inner_origin: &mut inner_area.origin.x, - inner_size: &mut inner_area.size.width, - area_origin: &mut area.origin.x, - area_size: &mut area.size.width, - one_side_padding: parent_node.padding.left(), - two_sides_padding: parent_node.padding.horizontal(), - one_side_margin: parent_node.margin.left(), - two_sides_margin: parent_node.margin.horizontal(), - available_size: &mut available_area.size.width, - }) - } - _ => None, - } - } else { - None - }; - - if let Some(NodeData { - inner_origin, - inner_size, - area_origin, - area_size, - one_side_padding, - two_sides_padding, - one_side_margin, - two_sides_margin, - available_size, - }) = params - { - // Set the origin of the inner area to the origin of the area plus the padding and margin for the given axis - *inner_origin = *area_origin + one_side_padding + one_side_margin; - // Set the size of the inner area to the size of the area minus the padding and margin for the given axis - *inner_size = *area_size - two_sides_padding - two_sides_margin; - // Set the same available size as the inner area for the given axis - *available_size = *inner_size; - } - } - - /// Stack a Node into another Node - pub fn stack_into_node( - &mut self, - parent_node: &Node, - available_area: &mut Area, - content_area: &Area, - inner_sizes: &mut Size2D, - node_data: &Node, - ) { - if node_data.position.is_absolute() { - return; - } - - match parent_node.direction { - DirectionMode::Horizontal => { - // Move the available area - available_area.origin.x = content_area.max_x(); - available_area.size.width -= content_area.size.width; - - if let MeasureMode::ParentIsNotCached { area, inner_area } = self { - inner_sizes.height = content_area.height().max(inner_sizes.height); - inner_sizes.width += content_area.width(); - - // Keep the biggest height - if parent_node.height.inner_sized() { - area.size.height = area.size.height.max( - content_area.size.height - + parent_node.padding.vertical() - + parent_node.margin.vertical(), - ); - // Keep the inner area in sync - inner_area.size.height = area.size.height - - parent_node.padding.vertical() - - parent_node.margin.vertical(); - } - - // Accumulate width - if parent_node.width.inner_sized() { - area.size.width += content_area.size.width; - } - } - } - DirectionMode::Vertical => { - // Move the available area - available_area.origin.y = content_area.max_y(); - available_area.size.height -= content_area.size.height; - - if let MeasureMode::ParentIsNotCached { area, inner_area } = self { - inner_sizes.width = content_area.width().max(inner_sizes.width); - inner_sizes.height += content_area.height(); - - // Keep the biggest width - if parent_node.width.inner_sized() { - area.size.width = area.size.width.max( - content_area.size.width - + parent_node.padding.horizontal() - + parent_node.margin.horizontal(), - ); - // Keep the inner area in sync - inner_area.size.width = area.size.width - - parent_node.padding.horizontal() - - parent_node.margin.horizontal(); - } - - // Accumulate height - if parent_node.height.inner_sized() { - area.size.height += content_area.size.height; - } - } - } - } - } -} - -/// Just an owned version of [MeasureMode] -#[derive(Debug)] -pub enum OwnedMeasureMode { - ParentIsCached { inner_area: Area }, - ParentIsNotCached { area: Area, inner_area: Area }, -} - -impl OwnedMeasureMode { - pub fn to_mut(&mut self) -> MeasureMode<'_> { - match self { - Self::ParentIsCached { inner_area } => MeasureMode::ParentIsCached { inner_area }, - Self::ParentIsNotCached { area, inner_area } => { - MeasureMode::ParentIsNotCached { area, inner_area } - } - } - } -} From d5380575a8643461816d83cf32167a740423f7bd Mon Sep 17 00:00:00 2001 From: marc2332 Date: Tue, 6 Aug 2024 00:34:45 +0200 Subject: [PATCH 04/14] chore: Clean up and fixes --- crates/components/src/button.rs | 1 - crates/torin/src/measure.rs | 32 +++++++++++++++++--------------- crates/torin/tests/alignment.rs | 2 +- examples/counter.rs | 24 +++++------------------- 4 files changed, 23 insertions(+), 36 deletions(-) diff --git a/crates/components/src/button.rs b/crates/components/src/button.rs index f2ab2083e..693ed9f92 100644 --- a/crates/components/src/button.rs +++ b/crates/components/src/button.rs @@ -181,7 +181,6 @@ pub fn Button( padding: "{padding}", margin: "{margin}", focusable: "true", - overflow: "clip", role: "button", color: "{font_theme.color}", shadow: "{shadow}", diff --git a/crates/torin/src/measure.rs b/crates/torin/src/measure.rs index e5dbba5f5..99bb68d7e 100644 --- a/crates/torin/src/measure.rs +++ b/crates/torin/src/measure.rs @@ -17,7 +17,6 @@ use crate::{ AlignmentDirection, AreaModel, LayoutMetadata, - SizeModel, Torin, }, }; @@ -48,12 +47,15 @@ pub fn measure_node( dom_adapter: &mut impl DOMAdapter, layout_metadata: &LayoutMetadata, - - invalidated_tree: bool, + // Parent Node is dirty. + parent_is_dirty: bool, phase: Phase, ) -> (bool, LayoutNode) { - let must_revalidate = invalidated_tree + // 1. If parent is dirty + // 2. If this Node has been marked as dirty + // 3. If there is no know cached data about this Node. + let must_revalidate = parent_is_dirty || layout.dirty.contains(&node_id) || !layout.results.contains_key(&node_id); if must_revalidate { @@ -223,12 +225,12 @@ pub fn measure_node( }, ) } else { - let mut layout_node = layout.get(node_id).unwrap().clone(); + let layout_node = layout.get(node_id).unwrap().clone(); let mut inner_sizes = layout_node.inner_sizes; let mut available_area = layout_node.inner_area; - let mut area = &mut layout_node.area; - let mut inner_area = &mut layout_node.inner_area; + let mut area = layout_node.area; + let mut inner_area = layout_node.inner_area; available_area.move_with_offsets(&node.offset_x, &node.offset_y); @@ -281,8 +283,8 @@ pub fn measure_inner_nodes( dom_adapter: &mut impl DOMAdapter, layout_metadata: &LayoutMetadata, - - invalidated_tree: bool, + // Parent Node is dirty. + parent_is_dirty: bool, ) { let children = dom_adapter.children_of(parent_node_id); @@ -305,6 +307,8 @@ pub fn measure_inner_nodes( continue; }; + // No need to consider this Node for a two-phasing + // measurements as it will float on its own. if child_data.position.is_absolute() { continue; } @@ -321,7 +325,7 @@ pub fn measure_inner_nodes( false, dom_adapter, layout_metadata, - invalidated_tree, + parent_is_dirty, Phase::Initial, ); @@ -341,8 +345,6 @@ pub fn measure_inner_nodes( } if parent_node.main_alignment.is_not_start() { - let inner_area = initial_phase_inner_area; - // 2. Adjust the available and inner areas of the Main axis available_area.fit_bounds_when_unspecified( &mut initial_phase_area, @@ -353,7 +355,7 @@ pub fn measure_inner_nodes( // 3. Align the Main axis available_area.align_content( - &inner_area, + &initial_phase_inner_area, &initial_phase_inner_sizes, &parent_node.main_alignment, &parent_node.direction, @@ -415,13 +417,13 @@ pub fn measure_inner_nodes( child_id, &child_data, layout, - &inner_area, + inner_area, &adapted_available_area, measurer, must_cache_inner_nodes, dom_adapter, layout_metadata, - invalidated_tree, + parent_is_dirty, Phase::Final, ); diff --git a/crates/torin/tests/alignment.rs b/crates/torin/tests/alignment.rs index f0fa39334..59d7b85b1 100644 --- a/crates/torin/tests/alignment.rs +++ b/crates/torin/tests/alignment.rs @@ -273,7 +273,7 @@ pub fn unsized_alignment() { assert_eq!( layout.get(1).unwrap().visible_area(), - Rect::new(Point2D::new(10.0, 75.0), Size2D::new(100.0, 50.0)), + Rect::new(Point2D::new(15.0, 75.0), Size2D::new(100.0, 50.0)), ); assert_eq!( diff --git a/examples/counter.rs b/examples/counter.rs index 25160e129..6f0601e62 100644 --- a/examples/counter.rs +++ b/examples/counter.rs @@ -13,20 +13,6 @@ fn app() -> Element { let mut count = use_signal(|| 0); rsx!( - rect { - height: "50%", - width: "100%", - main_align: "center", - cross_align: "center", - background: "rgb(0, 119, 182)", - color: "white", - shadow: "0 4 20 5 rgb(0, 0, 0, 80)", - label { - font_size: "75", - font_weight: "bold", - "{count}" - } - } rect { height: "50%", width: "100%", @@ -35,11 +21,11 @@ fn app() -> Element { direction: "horizontal", Button { onclick: move |_| count += 1, - label { "Increase" } - } - Button { - onclick: move |_| count -= 1, - label { "Decrease" } + rect { + width: "25", + height: "20", + background: "red" + } } } ) From eebe89da865a2184546c436d859365842675ad5e Mon Sep 17 00:00:00 2001 From: marc2332 Date: Tue, 6 Aug 2024 15:13:12 +0200 Subject: [PATCH 05/14] feat: MeasureContext --- crates/torin/src/geometry.rs | 331 +---------- crates/torin/src/measure.rs | 962 ++++++++++++++++++++------------ crates/torin/src/torin.rs | 17 +- crates/torin/tests/alignment.rs | 2 +- 4 files changed, 642 insertions(+), 670 deletions(-) diff --git a/crates/torin/src/geometry.rs b/crates/torin/src/geometry.rs index e62e02b6a..cb5fe3297 100644 --- a/crates/torin/src/geometry.rs +++ b/crates/torin/src/geometry.rs @@ -1,10 +1,6 @@ -use crate::{ - node::Node, - prelude::{ - Alignment, - DirectionMode, - Gaps, - }, +use crate::prelude::{ + DirectionMode, + Gaps, }; #[derive(PartialEq)] @@ -22,47 +18,6 @@ pub trait AreaModel { /// Adjust the available area with the node offsets (mainly used by scrollviews) fn move_with_offsets(&mut self, offset_x: &Length, offset_y: &Length); - - /// Align the content of this node. - fn align_content( - &mut self, - available_area: &Area, - contents_area: &Size2D, - alignment: &Alignment, - direction: &DirectionMode, - alignment_direction: AlignmentDirection, - ); - - /// Align the position of this node. - #[allow(clippy::too_many_arguments)] - fn align_position( - &mut self, - initial_available_area: &Area, - inner_sizes: &Size2D, - alignment: &Alignment, - direction: &DirectionMode, - alignment_direction: AlignmentDirection, - siblings_len: usize, - child_position: usize, - ); - - fn stack_into_node( - &mut self, - parent_node: &Node, - parent_area: &mut Area, - inner_area: &mut Area, - content_area: &Area, - inner_sizes: &mut Size2D, - node_data: &Node, - ); - - fn fit_bounds_when_unspecified( - &mut self, - parent_area: &mut Area, - inner_area: &mut Area, - parent_node: &Node, - alignment_direction: AlignmentDirection, - ); } impl AreaModel for Area { @@ -82,271 +37,6 @@ impl AreaModel for Area { self.origin.x += offset_x.get(); self.origin.y += offset_y.get(); } - - fn align_content( - &mut self, - inner_area: &Area, - contents_size: &Size2D, - alignment: &Alignment, - direction: &DirectionMode, - alignment_direction: AlignmentDirection, - ) { - let axis = get_align_axis(direction, alignment_direction); - - match axis { - AlignAxis::Height => match alignment { - Alignment::Center => { - let new_origin_y = (inner_area.height() / 2.0) - (contents_size.height / 2.0); - - self.origin.y = inner_area.min_y() + new_origin_y; - } - Alignment::End => { - self.origin.y = inner_area.max_y() - contents_size.height; - } - _ => {} - }, - AlignAxis::Width => match alignment { - Alignment::Center => { - let new_origin_x = (inner_area.width() / 2.0) - (contents_size.width / 2.0); - self.origin.x = inner_area.min_x() + new_origin_x; - } - Alignment::End => { - self.origin.x = inner_area.max_x() - contents_size.width; - } - _ => {} - }, - } - } - - fn align_position( - &mut self, - initial_available_area: &Area, - inner_sizes: &Size2D, - alignment: &Alignment, - direction: &DirectionMode, - alignment_direction: AlignmentDirection, - siblings_len: usize, - child_position: usize, - ) { - let axis = get_align_axis(direction, alignment_direction); - - match axis { - AlignAxis::Height => match alignment { - Alignment::SpaceBetween if child_position > 0 => { - let all_gaps_sizes = initial_available_area.height() - inner_sizes.height; - let gap_size = all_gaps_sizes / (siblings_len - 1) as f32; - self.origin.y += gap_size; - } - Alignment::SpaceEvenly => { - let all_gaps_sizes = initial_available_area.height() - inner_sizes.height; - let gap_size = all_gaps_sizes / (siblings_len + 1) as f32; - self.origin.y += gap_size; - } - Alignment::SpaceAround => { - let all_gaps_sizes = initial_available_area.height() - inner_sizes.height; - let one_gap_size = all_gaps_sizes / siblings_len as f32; - let gap_size = if child_position == 0 || child_position == siblings_len { - one_gap_size / 2. - } else { - one_gap_size - }; - self.origin.y += gap_size; - } - _ => {} - }, - AlignAxis::Width => match alignment { - Alignment::SpaceBetween if child_position > 0 => { - let all_gaps_sizes = initial_available_area.width() - inner_sizes.width; - let gap_size = all_gaps_sizes / (siblings_len - 1) as f32; - self.origin.x += gap_size; - } - Alignment::SpaceEvenly => { - let all_gaps_sizes = initial_available_area.width() - inner_sizes.width; - let gap_size = all_gaps_sizes / (siblings_len + 1) as f32; - self.origin.x += gap_size; - } - Alignment::SpaceAround => { - let all_gaps_sizes = initial_available_area.width() - inner_sizes.width; - let one_gap_size = all_gaps_sizes / siblings_len as f32; - let gap_size = if child_position == 0 || child_position == siblings_len { - one_gap_size / 2. - } else { - one_gap_size - }; - self.origin.x += gap_size; - } - _ => {} - }, - } - } - /// Stack a Node into another Node - fn stack_into_node( - &mut self, - parent_node: &Node, - parent_area: &mut Area, - inner_area: &mut Area, - content_area: &Area, - inner_sizes: &mut Size2D, - node_data: &Node, - ) { - if node_data.position.is_absolute() { - return; - } - - match parent_node.direction { - DirectionMode::Horizontal => { - // Move the available area - self.origin.x = content_area.max_x(); - self.size.width -= content_area.size.width; - - inner_sizes.height = content_area.height().max(inner_sizes.height); - inner_sizes.width += content_area.width(); - - // Keep the biggest height - if parent_node.height.inner_sized() { - parent_area.size.height = parent_area.size.height.max( - content_area.size.height - + parent_node.padding.vertical() - + parent_node.margin.vertical(), - ); - // Keep the inner area in sync - inner_area.size.height = parent_area.size.height - - parent_node.padding.vertical() - - parent_node.margin.vertical(); - } - - // Accumulate width - if parent_node.width.inner_sized() { - parent_area.size.width += content_area.size.width; - } - } - DirectionMode::Vertical => { - // Move the available area - self.origin.y = content_area.max_y(); - self.size.height -= content_area.size.height; - - inner_sizes.width = content_area.width().max(inner_sizes.width); - inner_sizes.height += content_area.height(); - - // Keep the biggest width - if parent_node.width.inner_sized() { - parent_area.size.width = parent_area.size.width.max( - content_area.size.width - + parent_node.padding.horizontal() - + parent_node.margin.horizontal(), - ); - // Keep the inner area in sync - inner_area.size.width = parent_area.size.width - - parent_node.padding.horizontal() - - parent_node.margin.horizontal(); - } - - // Accumulate height - if parent_node.height.inner_sized() { - parent_area.size.height += content_area.size.height; - } - } - } - } - - /// This will fit the available area and inner area of a parent node when for example height is set to "auto", - /// direction is vertical and main_alignment is set to "center" or "end" or the content is set to "fit". - /// The intended usage is to call this after the first measurement and before the second, - /// this way the second measurement will align the content relatively to the parent element instead - /// of overflowing due to being aligned relatively to the upper parent element - fn fit_bounds_when_unspecified( - &mut self, - parent_area: &mut Area, - inner_area: &mut Area, - parent_node: &Node, - alignment_direction: AlignmentDirection, - ) { - struct NodeData<'a> { - pub inner_origin: &'a mut f32, - pub inner_size: &'a mut f32, - pub area_origin: &'a mut f32, - pub area_size: &'a mut f32, - pub one_side_padding: f32, - pub two_sides_padding: f32, - pub one_side_margin: f32, - pub two_sides_margin: f32, - pub available_size: &'a mut f32, - } - - let axis = get_align_axis(&parent_node.direction, alignment_direction); - let (is_vertical_not_start, is_horizontal_not_start) = match parent_node.direction { - DirectionMode::Vertical => ( - parent_node.main_alignment.is_not_start(), - parent_node.cross_alignment.is_not_start() || parent_node.content.is_fit(), - ), - DirectionMode::Horizontal => ( - parent_node.cross_alignment.is_not_start() || parent_node.content.is_fit(), - parent_node.main_alignment.is_not_start(), - ), - }; - let NodeData { - inner_origin, - inner_size, - area_origin, - area_size, - one_side_padding, - two_sides_padding, - one_side_margin, - two_sides_margin, - available_size, - } = match axis { - AlignAxis::Height if parent_node.height.inner_sized() && is_vertical_not_start => { - NodeData { - inner_origin: &mut inner_area.origin.y, - inner_size: &mut inner_area.size.height, - area_origin: &mut parent_area.origin.y, - area_size: &mut parent_area.size.height, - one_side_padding: parent_node.padding.top(), - two_sides_padding: parent_node.padding.vertical(), - one_side_margin: parent_node.margin.top(), - two_sides_margin: parent_node.margin.vertical(), - available_size: &mut self.size.height, - } - } - AlignAxis::Width if parent_node.width.inner_sized() && is_horizontal_not_start => { - NodeData { - inner_origin: &mut inner_area.origin.x, - inner_size: &mut inner_area.size.width, - area_origin: &mut parent_area.origin.x, - area_size: &mut parent_area.size.width, - one_side_padding: parent_node.padding.left(), - two_sides_padding: parent_node.padding.horizontal(), - one_side_margin: parent_node.margin.left(), - two_sides_margin: parent_node.margin.horizontal(), - available_size: &mut self.size.width, - } - } - _ => return, - }; - - // Set the origin of the inner area to the origin of the area plus the padding and margin for the given axis - *inner_origin = *area_origin + one_side_padding + one_side_margin; - // Set the size of the inner area to the size of the area minus the padding and margin for the given axis - *inner_size = *area_size - two_sides_padding - two_sides_margin; - // Set the same available size as the inner area for the given axis - *available_size = *inner_size; - } -} - -pub fn get_align_axis( - direction: &DirectionMode, - alignment_direction: AlignmentDirection, -) -> AlignAxis { - match direction { - DirectionMode::Vertical => match alignment_direction { - AlignmentDirection::Main => AlignAxis::Height, - AlignmentDirection::Cross => AlignAxis::Width, - }, - DirectionMode::Horizontal => match alignment_direction { - AlignmentDirection::Main => AlignAxis::Width, - AlignmentDirection::Cross => AlignAxis::Height, - }, - } } pub enum AlignmentDirection { @@ -360,6 +50,21 @@ pub enum AlignAxis { Width, } +impl AlignAxis { + pub fn new(direction: &DirectionMode, alignment_direction: AlignmentDirection) -> Self { + match direction { + DirectionMode::Vertical => match alignment_direction { + AlignmentDirection::Main => AlignAxis::Height, + AlignmentDirection::Cross => AlignAxis::Width, + }, + DirectionMode::Horizontal => match alignment_direction { + AlignmentDirection::Main => AlignAxis::Width, + AlignmentDirection::Cross => AlignAxis::Height, + }, + } + } +} + pub trait SizeModel { /// Get the size with the given gap, e.g padding. fn with_gaps(self, gap: &Gaps) -> Size2D; diff --git a/crates/torin/src/measure.rs b/crates/torin/src/measure.rs index 99bb68d7e..e0ae7f745 100644 --- a/crates/torin/src/measure.rs +++ b/crates/torin/src/measure.rs @@ -14,8 +14,11 @@ use crate::{ }, node::Node, prelude::{ + AlignAxis, + Alignment, AlignmentDirection, AreaModel, + DirectionMode, LayoutMetadata, Torin, }, @@ -29,422 +32,683 @@ pub enum Phase { Final, } -/// Measure a Node layout -#[allow(clippy::too_many_arguments)] -#[inline(always)] -pub fn measure_node( - node_id: Key, - node: &Node, - layout: &mut Torin, - // Area occupied by it's parent - parent_area: &Area, - // Area that is available to use by the children of the parent - available_parent_area: &Area, - measurer: &mut Option>, - // Whether to cache the measurements of this Node's children - must_cache_inner_nodes: bool, - // Adapter for the provided DOM - dom_adapter: &mut impl DOMAdapter, - - layout_metadata: &LayoutMetadata, - // Parent Node is dirty. - parent_is_dirty: bool, - - phase: Phase, -) -> (bool, LayoutNode) { - // 1. If parent is dirty - // 2. If this Node has been marked as dirty - // 3. If there is no know cached data about this Node. - let must_revalidate = parent_is_dirty - || layout.dirty.contains(&node_id) - || !layout.results.contains_key(&node_id); - if must_revalidate { - // Create the initial Node area size - let mut area_size = Size2D::new(node.padding.horizontal(), node.padding.vertical()); - - // Compute the width and height given the size, the minimum size, the maximum size and margins - area_size.width = node.width.min_max( - area_size.width, - parent_area.size.width, - available_parent_area.size.width, - node.margin.left(), - node.margin.horizontal(), - &node.minimum_width, - &node.maximum_width, - layout_metadata.root_area.width(), - phase, - ); - area_size.height = node.height.min_max( - area_size.height, - parent_area.size.height, - available_parent_area.size.height, - node.margin.top(), - node.margin.vertical(), - &node.minimum_height, - &node.maximum_height, - layout_metadata.root_area.height(), - phase, - ); - - // If available, run a custom layout measure function - // This is useful when you use third-party libraries (e.g. rust-skia, cosmic-text) to measure text layouts - // When a Node is measured by a custom measurer function the inner children will be skipped - let (measure_inner_children, node_data) = if let Some(measurer) = measurer { - let most_fitting_width = *node - .width - .most_fitting_size(&area_size.width, &available_parent_area.size.width); - let most_fitting_height = *node - .height - .most_fitting_size(&area_size.height, &available_parent_area.size.height); - - let most_fitting_area_size = Size2D::new(most_fitting_width, most_fitting_height); - let res = measurer.measure(node_id, node, &most_fitting_area_size); - - // Compute the width and height again using the new custom area sizes - if let Some((custom_size, node_data)) = res { +pub struct MeasureContext<'a, Key, L, D> +where + Key: NodeKey, + L: LayoutMeasurer, + D: DOMAdapter, +{ + pub layout: &'a mut Torin, + pub measurer: &'a mut Option, + pub dom_adapter: &'a mut D, + pub layout_metadata: LayoutMetadata, +} + +impl MeasureContext<'_, Key, L, D> +where + Key: NodeKey, + L: LayoutMeasurer, + D: DOMAdapter, +{ + /// Measure a Node. + #[allow(clippy::too_many_arguments)] + #[inline(always)] + pub fn measure_node( + &mut self, + // ID for this Node + node_id: Key, + // Data of this Node + node: &Node, + // Area occupied by it's parent + parent_area: &Area, + // Area that is available to use by the children of the parent + available_parent_area: &Area, + // Whether to cache the measurements of this Node's children + must_cache_children: bool, + // Parent Node is dirty. + parent_is_dirty: bool, + // Current phase of measurement + phase: Phase, + ) -> (bool, LayoutNode) { + // 1. If parent is dirty + // 2. If this Node has been marked as dirty + // 3. If there is no know cached data about this Node. + let must_revalidate = parent_is_dirty + || self.layout.dirty.contains(&node_id) + || !self.layout.results.contains_key(&node_id); + if must_revalidate { + // Create the initial Node area size + let mut area_size = Size2D::new(node.padding.horizontal(), node.padding.vertical()); + + // Compute the width and height given the size, the minimum size, the maximum size and margins + area_size.width = node.width.min_max( + area_size.width, + parent_area.size.width, + available_parent_area.size.width, + node.margin.left(), + node.margin.horizontal(), + &node.minimum_width, + &node.maximum_width, + self.layout_metadata.root_area.width(), + phase, + ); + area_size.height = node.height.min_max( + area_size.height, + parent_area.size.height, + available_parent_area.size.height, + node.margin.top(), + node.margin.vertical(), + &node.minimum_height, + &node.maximum_height, + self.layout_metadata.root_area.height(), + phase, + ); + + // If available, run a custom layout measure function + // This is useful when you use third-party libraries (e.g. rust-skia, cosmic-text) to measure text layouts + // When a Node is measured by a custom measurer function the inner children will be skipped + let (measure_inner_children, node_data) = if let Some(measurer) = self.measurer { + let most_fitting_width = *node + .width + .most_fitting_size(&area_size.width, &available_parent_area.size.width); + let most_fitting_height = *node + .height + .most_fitting_size(&area_size.height, &available_parent_area.size.height); + + let most_fitting_area_size = Size2D::new(most_fitting_width, most_fitting_height); + let res = measurer.measure(node_id, node, &most_fitting_area_size); + + // Compute the width and height again using the new custom area sizes + if let Some((custom_size, node_data)) = res { + if node.width.inner_sized() { + area_size.width = node.width.min_max( + custom_size.width, + parent_area.size.width, + available_parent_area.size.width, + node.margin.left(), + node.margin.horizontal(), + &node.minimum_width, + &node.maximum_width, + self.layout_metadata.root_area.width(), + phase, + ); + } + if node.height.inner_sized() { + area_size.height = node.height.min_max( + custom_size.height, + parent_area.size.height, + available_parent_area.size.height, + node.margin.top(), + node.margin.vertical(), + &node.minimum_height, + &node.maximum_height, + self.layout_metadata.root_area.height(), + phase, + ); + } + + // Do not measure inner children + (false, Some(node_data)) + } else { + (true, None) + } + } else { + (true, None) + }; + + // There is no need to measure inner children in the initial phase if this Node size + // isn't decided by his children + let phase_measure_inner_children = if phase == Phase::Initial { + node.width.inner_sized() || node.height.inner_sized() + } else { + true + }; + + // Compute the inner size of the Node, which is basically the size inside the margins and paddings + let inner_size = { + let mut inner_size = area_size; + + // When having an unsized bound we set it to whatever is still available in the parent's area if node.width.inner_sized() { - area_size.width = node.width.min_max( - custom_size.width, + inner_size.width = node.width.min_max( + available_parent_area.width(), parent_area.size.width, - available_parent_area.size.width, + available_parent_area.width(), node.margin.left(), node.margin.horizontal(), &node.minimum_width, &node.maximum_width, - layout_metadata.root_area.width(), + self.layout_metadata.root_area.width(), phase, ); } if node.height.inner_sized() { - area_size.height = node.height.min_max( - custom_size.height, + inner_size.height = node.height.min_max( + available_parent_area.height(), parent_area.size.height, - available_parent_area.size.height, + available_parent_area.height(), node.margin.top(), node.margin.vertical(), &node.minimum_height, &node.maximum_height, - layout_metadata.root_area.height(), + self.layout_metadata.root_area.height(), phase, ); } + inner_size + }; - // Do not measure inner children - (false, Some(node_data)) - } else { - (true, None) + // Create the areas + let area_origin = + node.position + .get_origin(available_parent_area, parent_area, &area_size); + let mut area = Rect::new(area_origin, area_size); + let mut inner_area = Rect::new(area_origin, inner_size) + .without_gaps(&node.padding) + .without_gaps(&node.margin); + + let mut inner_sizes = Size2D::default(); + + if measure_inner_children && phase_measure_inner_children { + // Create an area containing the available space inside the inner area + let mut available_area = inner_area; + + available_area.move_with_offsets(&node.offset_x, &node.offset_y); + + // Measure the layout of this Node's children + self.measure_children( + &node_id, + node, + &mut available_area, + &mut inner_sizes, + must_cache_children, + &mut area, + &mut inner_area, + true, + ); } - } else { - (true, None) - }; - // There is no need to measure inner children in the initial phase if this Node size - // isn't decided by his children - let phase_measure_inner_children = if phase == Phase::Initial { - node.width.inner_sized() || node.height.inner_sized() + ( + must_cache_children, + LayoutNode { + area, + margin: node.margin, + inner_area, + inner_sizes, + data: node_data, + }, + ) } else { - true - }; + let layout_node = self.layout.get(node_id).unwrap().clone(); - // Compute the inner size of the Node, which is basically the size inside the margins and paddings - let inner_size = { - let mut inner_size = area_size; - - // When having an unsized bound we set it to whatever is still available in the parent's area - if node.width.inner_sized() { - inner_size.width = node.width.min_max( - available_parent_area.width(), - parent_area.size.width, - available_parent_area.width(), - node.margin.left(), - node.margin.horizontal(), - &node.minimum_width, - &node.maximum_width, - layout_metadata.root_area.width(), - phase, - ); - } - if node.height.inner_sized() { - inner_size.height = node.height.min_max( - available_parent_area.height(), - parent_area.size.height, - available_parent_area.height(), - node.margin.top(), - node.margin.vertical(), - &node.minimum_height, - &node.maximum_height, - layout_metadata.root_area.height(), - phase, + let mut inner_sizes = layout_node.inner_sizes; + let mut available_area = layout_node.inner_area; + let mut area = layout_node.area; + let mut inner_area = layout_node.inner_area; + + available_area.move_with_offsets(&node.offset_x, &node.offset_y); + + let measure_inner_children = if let Some(measurer) = self.measurer { + measurer.should_measure_inner_children(node_id) + } else { + true + }; + + if measure_inner_children { + self.measure_children( + &node_id, + node, + &mut available_area, + &mut inner_sizes, + must_cache_children, + &mut area, + &mut inner_area, + false, ); } - inner_size - }; - - // Create the areas - let area_origin = node - .position - .get_origin(available_parent_area, parent_area, &area_size); - let mut area = Rect::new(area_origin, area_size); - let mut inner_area = Rect::new(area_origin, inner_size) - .without_gaps(&node.padding) - .without_gaps(&node.margin); - let mut inner_sizes = Size2D::default(); + (false, layout_node) + } + } - if measure_inner_children && phase_measure_inner_children { - // Create an area containing the available space inside the inner area - let mut available_area = inner_area; + /// Measure the children layouts of a Node + #[allow(clippy::too_many_arguments)] + #[inline(always)] + pub fn measure_children( + &mut self, + parent_node_id: &Key, + parent_node: &Node, + // Area available inside the Node + available_area: &mut Area, + // Accumulated sizes in both axis in the Node + inner_sizes: &mut Size2D, + // Whether to cache the measurements of this Node's children + must_cache_children: bool, + // Parent area. + area: &mut Area, + // Inner area of the parent. + inner_area: &mut Area, + // Parent Node is dirty. + parent_is_dirty: bool, + ) { + let children = self.dom_adapter.children_of(parent_node_id); + + let mut initial_phase_sizes = FxHashMap::default(); + let mut initial_phase_inner_sizes = *inner_sizes; + + // Initial phase: Measure the size and position of the children if the parent has a + // non-start cross alignment, non-start main aligment of a fit-content. + if parent_node.cross_alignment.is_not_start() + || parent_node.main_alignment.is_not_start() + || parent_node.content.is_fit() + { + let mut initial_phase_area = *area; + let mut initial_phase_inner_area = *inner_area; + let mut initial_phase_available_area = *available_area; + + // Measure the children + for child_id in &children { + let Some(child_data) = self.dom_adapter.get_node(child_id) else { + continue; + }; + + // No need to consider this Node for a two-phasing + // measurements as it will float on its own. + if child_data.position.is_absolute() { + continue; + } - available_area.move_with_offsets(&node.offset_x, &node.offset_y); + let inner_area = initial_phase_inner_area; - // Measure the layout of this Node's children - measure_inner_nodes( - &node_id, - node, - layout, - &mut available_area, - &mut inner_sizes, - measurer, - must_cache_inner_nodes, - &mut area, - &mut inner_area, - dom_adapter, - layout_metadata, - true, - ); - } + let (_, child_areas) = self.measure_node( + *child_id, + &child_data, + &inner_area, + &initial_phase_available_area, + false, + parent_is_dirty, + Phase::Initial, + ); - ( - must_cache_inner_nodes, - LayoutNode { - area, - margin: node.margin, - inner_area, - inner_sizes, - data: node_data, - }, - ) - } else { - let layout_node = layout.get(node_id).unwrap().clone(); + // Stack this child into the parent + Self::stack_node( + &mut initial_phase_available_area, + parent_node, + &mut initial_phase_area, + &mut initial_phase_inner_area, + &child_areas.area, + &mut initial_phase_inner_sizes, + &child_data, + ); - let mut inner_sizes = layout_node.inner_sizes; - let mut available_area = layout_node.inner_area; - let mut area = layout_node.area; - let mut inner_area = layout_node.inner_area; + if parent_node.cross_alignment.is_not_start() + || parent_node.main_alignment.is_spaced() + { + initial_phase_sizes.insert(*child_id, child_areas.area.size); + } + } - available_area.move_with_offsets(&node.offset_x, &node.offset_y); + if parent_node.main_alignment.is_not_start() { + // Adjust the available and inner areas of the Main axis + Self::shrink_area_to_fit_when_unbounded( + available_area, + &initial_phase_area, + &mut initial_phase_inner_area, + parent_node, + AlignmentDirection::Main, + ); - let measure_inner_children = if let Some(measurer) = measurer { - measurer.should_measure_inner_children(node_id) - } else { - true - }; + // Align the Main axis + Self::align_content( + available_area, + &initial_phase_inner_area, + &initial_phase_inner_sizes, + &parent_node.main_alignment, + &parent_node.direction, + AlignmentDirection::Main, + ); + } - if measure_inner_children { - measure_inner_nodes( - &node_id, - node, - layout, - &mut available_area, - &mut inner_sizes, - measurer, - must_cache_inner_nodes, - &mut area, - &mut inner_area, - dom_adapter, - layout_metadata, - false, - ); + if parent_node.cross_alignment.is_not_start() || parent_node.content.is_fit() { + // Adjust the available and inner areas of the Cross axis + Self::shrink_area_to_fit_when_unbounded( + available_area, + &initial_phase_area, + &mut initial_phase_inner_area, + parent_node, + AlignmentDirection::Cross, + ); + } } - (false, layout_node) - } -} + let initial_available_area = *available_area; -/// Measure the children layouts of a Node -#[allow(clippy::too_many_arguments)] -#[inline(always)] -pub fn measure_inner_nodes( - parent_node_id: &Key, - parent_node: &Node, - layout: &mut Torin, - // Area available inside the Node - available_area: &mut Area, - // Accumulated sizes in both axis in the Node - inner_sizes: &mut Size2D, - measurer: &mut Option>, - // Whether to cache the measurements of this Node's children - must_cache_inner_nodes: bool, - - area: &mut Area, - - inner_area: &mut Area, - // Adapter for the provided DOM - dom_adapter: &mut impl DOMAdapter, - - layout_metadata: &LayoutMetadata, - // Parent Node is dirty. - parent_is_dirty: bool, -) { - let children = dom_adapter.children_of(parent_node_id); - - let mut initial_phase_sizes = FxHashMap::default(); - let mut initial_phase_inner_sizes = *inner_sizes; - - // Initial phase: Measure the size and position of the children if the parent has a - // non-start cross alignment, non-start main aligment of a fit-content. - if parent_node.cross_alignment.is_not_start() - || parent_node.main_alignment.is_not_start() - || parent_node.content.is_fit() - { - let mut initial_phase_area = *area; - let mut initial_phase_inner_area = *inner_area; - let mut initial_phase_available_area = *available_area; - - // 1. Measure the children - for child_id in &children { - let Some(child_data) = dom_adapter.get_node(child_id) else { + // Final phase: measure the children with all the axis and sizes adjusted + for (child_n, child_id) in children.into_iter().enumerate() { + let Some(child_data) = self.dom_adapter.get_node(&child_id) else { continue; }; - // No need to consider this Node for a two-phasing - // measurements as it will float on its own. - if child_data.position.is_absolute() { - continue; + let mut adapted_available_area = *available_area; + + if parent_node.main_alignment.is_spaced() { + // Align the Main axis if necessary + Self::align_position( + &mut adapted_available_area, + &initial_available_area, + &initial_phase_inner_sizes, + &parent_node.main_alignment, + &parent_node.direction, + AlignmentDirection::Main, + initial_phase_sizes.len(), + child_n, + ); } - let inner_area = initial_phase_inner_area; + if parent_node.cross_alignment.is_not_start() { + let initial_phase_size = initial_phase_sizes.get(&child_id); + + if let Some(initial_phase_size) = initial_phase_size { + // Align the Cross axis if necessary + Self::align_content( + &mut adapted_available_area, + available_area, + initial_phase_size, + &parent_node.cross_alignment, + &parent_node.direction, + AlignmentDirection::Cross, + ); + } + } - let (_, child_areas) = measure_node( - *child_id, + // Final measurement + let (child_revalidated, child_areas) = self.measure_node( + child_id, &child_data, - layout, - &inner_area, - &initial_phase_available_area, - measurer, - false, - dom_adapter, - layout_metadata, + inner_area, + &adapted_available_area, + must_cache_children, parent_is_dirty, - Phase::Initial, + Phase::Final, ); - initial_phase_available_area.stack_into_node( + // Stack this child into the parent + Self::stack_node( + available_area, parent_node, - &mut initial_phase_area, - &mut initial_phase_inner_area, + area, + inner_area, &child_areas.area, - &mut initial_phase_inner_sizes, + inner_sizes, &child_data, ); - if parent_node.cross_alignment.is_not_start() || parent_node.main_alignment.is_spaced() - { - initial_phase_sizes.insert(*child_id, child_areas.area.size); + // Cache the child layout if it was mutated and inner nodes must be cache + if child_revalidated && must_cache_children { + // In case of any layout listener, notify it with the new areas. + if child_data.has_layout_references { + if let Some(measurer) = self.measurer { + measurer.notify_layout_references(child_id, &child_areas); + } + } + + // Finally cache this node areas into Torin + self.layout.cache_node(child_id, child_areas); } } + } - if parent_node.main_alignment.is_not_start() { - // 2. Adjust the available and inner areas of the Main axis - available_area.fit_bounds_when_unspecified( - &mut initial_phase_area, - &mut initial_phase_inner_area, - parent_node, - AlignmentDirection::Main, - ); - - // 3. Align the Main axis - available_area.align_content( - &initial_phase_inner_area, - &initial_phase_inner_sizes, - &parent_node.main_alignment, - &parent_node.direction, - AlignmentDirection::Main, - ); + /// Align the content of this node. + fn align_content( + available_area: &mut Area, + inner_area: &Area, + contents_size: &Size2D, + alignment: &Alignment, + direction: &DirectionMode, + alignment_direction: AlignmentDirection, + ) { + let axis = AlignAxis::new(direction, alignment_direction); + + match axis { + AlignAxis::Height => match alignment { + Alignment::Center => { + let new_origin_y = (inner_area.height() / 2.0) - (contents_size.height / 2.0); + + available_area.origin.y = inner_area.min_y() + new_origin_y; + } + Alignment::End => { + available_area.origin.y = inner_area.max_y() - contents_size.height; + } + _ => {} + }, + AlignAxis::Width => match alignment { + Alignment::Center => { + let new_origin_x = (inner_area.width() / 2.0) - (contents_size.width / 2.0); + available_area.origin.x = inner_area.min_x() + new_origin_x; + } + Alignment::End => { + available_area.origin.x = inner_area.max_x() - contents_size.width; + } + _ => {} + }, } + } - if parent_node.cross_alignment.is_not_start() || parent_node.content.is_fit() { - // 4. Adjust the available and inner areas of the Cross axis - available_area.fit_bounds_when_unspecified( - &mut initial_phase_area, - &mut initial_phase_inner_area, - parent_node, - AlignmentDirection::Cross, - ); + /// Align the position of this node. + #[allow(clippy::too_many_arguments)] + fn align_position( + available_area: &mut Area, + initial_available_area: &Area, + inner_sizes: &Size2D, + alignment: &Alignment, + direction: &DirectionMode, + alignment_direction: AlignmentDirection, + siblings_len: usize, + child_position: usize, + ) { + let axis = AlignAxis::new(direction, alignment_direction); + + match axis { + AlignAxis::Height => match alignment { + Alignment::SpaceBetween if child_position > 0 => { + let all_gaps_sizes = initial_available_area.height() - inner_sizes.height; + let gap_size = all_gaps_sizes / (siblings_len - 1) as f32; + available_area.origin.y += gap_size; + } + Alignment::SpaceEvenly => { + let all_gaps_sizes = initial_available_area.height() - inner_sizes.height; + let gap_size = all_gaps_sizes / (siblings_len + 1) as f32; + available_area.origin.y += gap_size; + } + Alignment::SpaceAround => { + let all_gaps_sizes = initial_available_area.height() - inner_sizes.height; + let one_gap_size = all_gaps_sizes / siblings_len as f32; + let gap_size = if child_position == 0 || child_position == siblings_len { + one_gap_size / 2. + } else { + one_gap_size + }; + available_area.origin.y += gap_size; + } + _ => {} + }, + AlignAxis::Width => match alignment { + Alignment::SpaceBetween if child_position > 0 => { + let all_gaps_sizes = initial_available_area.width() - inner_sizes.width; + let gap_size = all_gaps_sizes / (siblings_len - 1) as f32; + available_area.origin.x += gap_size; + } + Alignment::SpaceEvenly => { + let all_gaps_sizes = initial_available_area.width() - inner_sizes.width; + let gap_size = all_gaps_sizes / (siblings_len + 1) as f32; + available_area.origin.x += gap_size; + } + Alignment::SpaceAround => { + let all_gaps_sizes = initial_available_area.width() - inner_sizes.width; + let one_gap_size = all_gaps_sizes / siblings_len as f32; + let gap_size = if child_position == 0 || child_position == siblings_len { + one_gap_size / 2. + } else { + one_gap_size + }; + available_area.origin.x += gap_size; + } + _ => {} + }, } } - let initial_available_area = *available_area; - - // Final phase: measure the children with all the axis and sizes adjusted - for (child_n, child_id) in children.into_iter().enumerate() { - let Some(child_data) = dom_adapter.get_node(&child_id) else { - continue; - }; - - let mut adapted_available_area = *available_area; - - if parent_node.main_alignment.is_spaced() { - // Align the Main axis if necessary - adapted_available_area.align_position( - &initial_available_area, - &initial_phase_inner_sizes, - &parent_node.main_alignment, - &parent_node.direction, - AlignmentDirection::Main, - initial_phase_sizes.len(), - child_n, - ); + /// Stack a Node into another Node + fn stack_node( + available_area: &mut Area, + parent_node: &Node, + parent_area: &mut Area, + inner_area: &mut Area, + content_area: &Area, + inner_sizes: &mut Size2D, + node_data: &Node, + ) { + // No need to stack a node that is positioned absolutely + if node_data.position.is_absolute() { + return; } - if parent_node.cross_alignment.is_not_start() { - let initial_phase_size = initial_phase_sizes.get(&child_id); + match parent_node.direction { + DirectionMode::Horizontal => { + // Move the available area + available_area.origin.x = content_area.max_x(); + available_area.size.width -= content_area.size.width; + + inner_sizes.height = content_area.height().max(inner_sizes.height); + inner_sizes.width += content_area.width(); + + // Keep the biggest height + if parent_node.height.inner_sized() { + parent_area.size.height = parent_area.size.height.max( + content_area.size.height + + parent_node.padding.vertical() + + parent_node.margin.vertical(), + ); + // Keep the inner area in sync + inner_area.size.height = parent_area.size.height + - parent_node.padding.vertical() + - parent_node.margin.vertical(); + } - if let Some(initial_phase_size) = initial_phase_size { - // Align the Cross axis if necessary - adapted_available_area.align_content( - available_area, - initial_phase_size, - &parent_node.cross_alignment, - &parent_node.direction, - AlignmentDirection::Cross, - ); + // Accumulate width + if parent_node.width.inner_sized() { + parent_area.size.width += content_area.size.width; + } } - } + DirectionMode::Vertical => { + // Move the available area + available_area.origin.y = content_area.max_y(); + available_area.size.height -= content_area.size.height; + + inner_sizes.width = content_area.width().max(inner_sizes.width); + inner_sizes.height += content_area.height(); + + // Keep the biggest width + if parent_node.width.inner_sized() { + parent_area.size.width = parent_area.size.width.max( + content_area.size.width + + parent_node.padding.horizontal() + + parent_node.margin.horizontal(), + ); + // Keep the inner area in sync + inner_area.size.width = parent_area.size.width + - parent_node.padding.horizontal() + - parent_node.margin.horizontal(); + } - // Final measurement - let (child_revalidated, child_areas) = measure_node( - child_id, - &child_data, - layout, - inner_area, - &adapted_available_area, - measurer, - must_cache_inner_nodes, - dom_adapter, - layout_metadata, - parent_is_dirty, - Phase::Final, - ); - - // Stack the child into its parent - available_area.stack_into_node( - parent_node, - area, - inner_area, - &child_areas.area, - inner_sizes, - &child_data, - ); - - // Cache the child layout if it was mutated and inner nodes must be cache - if child_revalidated && must_cache_inner_nodes { - if let Some(measurer) = measurer { - if child_data.has_layout_references { - measurer.notify_layout_references(child_id, &child_areas); + // Accumulate height + if parent_node.height.inner_sized() { + parent_area.size.height += content_area.size.height; } } - layout.cache_node(child_id, child_areas); } } + + /// Shrink the available area and inner area of a parent node when for example height is set to "auto", + /// direction is vertical and main_alignment is set to "center" or "end" or the content is set to "fit". + /// The intended usage is to call this after the first measurement and before the second, + /// this way the second measurement will align the content relatively to the parent element instead + /// of overflowing due to being aligned relatively to the upper parent element + fn shrink_area_to_fit_when_unbounded( + available_area: &mut Area, + parent_area: &Area, + inner_area: &mut Area, + parent_node: &Node, + alignment_direction: AlignmentDirection, + ) { + struct NodeData<'a> { + pub inner_origin: &'a mut f32, + pub inner_size: &'a mut f32, + pub area_origin: f32, + pub area_size: f32, + pub one_side_padding: f32, + pub two_sides_padding: f32, + pub one_side_margin: f32, + pub two_sides_margin: f32, + pub available_size: &'a mut f32, + } + + let axis = AlignAxis::new(&parent_node.direction, alignment_direction); + let (is_vertical_not_start, is_horizontal_not_start) = match parent_node.direction { + DirectionMode::Vertical => ( + parent_node.main_alignment.is_not_start(), + parent_node.cross_alignment.is_not_start() || parent_node.content.is_fit(), + ), + DirectionMode::Horizontal => ( + parent_node.cross_alignment.is_not_start() || parent_node.content.is_fit(), + parent_node.main_alignment.is_not_start(), + ), + }; + let NodeData { + inner_origin, + inner_size, + area_origin, + area_size, + one_side_padding, + two_sides_padding, + one_side_margin, + two_sides_margin, + available_size, + } = match axis { + AlignAxis::Height if parent_node.height.inner_sized() && is_vertical_not_start => { + NodeData { + inner_origin: &mut inner_area.origin.y, + inner_size: &mut inner_area.size.height, + area_origin: parent_area.origin.y, + area_size: parent_area.size.height, + one_side_padding: parent_node.padding.top(), + two_sides_padding: parent_node.padding.vertical(), + one_side_margin: parent_node.margin.top(), + two_sides_margin: parent_node.margin.vertical(), + available_size: &mut available_area.size.height, + } + } + AlignAxis::Width if parent_node.width.inner_sized() && is_horizontal_not_start => { + NodeData { + inner_origin: &mut inner_area.origin.x, + inner_size: &mut inner_area.size.width, + area_origin: parent_area.origin.x, + area_size: parent_area.size.width, + one_side_padding: parent_node.padding.left(), + two_sides_padding: parent_node.padding.horizontal(), + one_side_margin: parent_node.margin.left(), + two_sides_margin: parent_node.margin.horizontal(), + available_size: &mut available_area.size.width, + } + } + _ => return, + }; + + // Set the origin of the inner area to the origin of the area plus the padding and margin for the given axis + *inner_origin = area_origin + one_side_padding + one_side_margin; + // Set the size of the inner area to the size of the area minus the padding and margin for the given axis + *inner_size = area_size - two_sides_padding - two_sides_margin; + // Set the same available size as the inner area for the given axis + *available_size = *inner_size; + } } diff --git a/crates/torin/src/torin.rs b/crates/torin/src/torin.rs index c5398ff4c..0760d4777 100644 --- a/crates/torin/src/torin.rs +++ b/crates/torin/src/torin.rs @@ -22,7 +22,7 @@ use crate::{ Size2D, }, measure::{ - measure_node, + MeasureContext, Phase, }, prelude::{ @@ -265,7 +265,7 @@ impl Torin { root_height ); - let metadata = LayoutMetadata { root_area }; + let layout_metadata = LayoutMetadata { root_area }; let mut available_area = layout_node.inner_area; if let Some(root_parent_id) = root_parent_id { @@ -273,16 +273,19 @@ impl Torin { available_area.move_with_offsets(&root_parent.offset_x, &root_parent.offset_y); } - let (root_revalidated, root_layout_node) = measure_node( + let mut measure_context = MeasureContext { + layout: self, + layout_metadata, + dom_adapter, + measurer, + }; + + let (root_revalidated, root_layout_node) = measure_context.measure_node( root_id, &root, - self, &layout_node.inner_area, &available_area, - measurer, true, - dom_adapter, - &metadata, false, Phase::Final, ); diff --git a/crates/torin/tests/alignment.rs b/crates/torin/tests/alignment.rs index 59d7b85b1..9596dd9ac 100644 --- a/crates/torin/tests/alignment.rs +++ b/crates/torin/tests/alignment.rs @@ -278,7 +278,7 @@ pub fn unsized_alignment() { assert_eq!( layout.get(2).unwrap().visible_area(), - Rect::new(Point2D::new(110.0, 25.0), Size2D::new(150.0, 80.0)), + Rect::new(Point2D::new(115.0, 25.0), Size2D::new(150.0, 80.0)), ); } From a0ae9fa7b0ff170a649f3385fb3ecfe10083b0e2 Mon Sep 17 00:00:00 2001 From: marc2332 Date: Tue, 6 Aug 2024 19:52:45 +0200 Subject: [PATCH 06/14] chore: Clean up --- crates/torin/src/measure.rs | 48 ++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/crates/torin/src/measure.rs b/crates/torin/src/measure.rs index e0ae7f745..0e2e00636 100644 --- a/crates/torin/src/measure.rs +++ b/crates/torin/src/measure.rs @@ -331,13 +331,13 @@ where ); // Stack this child into the parent - Self::stack_node( + Self::stack_child( &mut initial_phase_available_area, parent_node, &mut initial_phase_area, &mut initial_phase_inner_area, - &child_areas.area, &mut initial_phase_inner_sizes, + &child_areas.area, &child_data, ); @@ -394,12 +394,12 @@ where if parent_node.main_alignment.is_spaced() { // Align the Main axis if necessary Self::align_position( + AlignmentDirection::Main, &mut adapted_available_area, &initial_available_area, &initial_phase_inner_sizes, &parent_node.main_alignment, &parent_node.direction, - AlignmentDirection::Main, initial_phase_sizes.len(), child_n, ); @@ -433,17 +433,17 @@ where ); // Stack this child into the parent - Self::stack_node( + Self::stack_child( available_area, parent_node, area, inner_area, - &child_areas.area, inner_sizes, + &child_areas.area, &child_data, ); - // Cache the child layout if it was mutated and inner nodes must be cache + // Cache the child layout if it was mutated and children must be cached if child_revalidated && must_cache_children { // In case of any layout listener, notify it with the new areas. if child_data.has_layout_references { @@ -497,12 +497,12 @@ where /// Align the position of this node. #[allow(clippy::too_many_arguments)] fn align_position( + alignment_direction: AlignmentDirection, available_area: &mut Area, initial_available_area: &Area, inner_sizes: &Size2D, alignment: &Alignment, direction: &DirectionMode, - alignment_direction: AlignmentDirection, siblings_len: usize, child_position: usize, ) { @@ -558,34 +558,34 @@ where } } - /// Stack a Node into another Node - fn stack_node( + /// Stack a child Node into its parent + fn stack_child( available_area: &mut Area, parent_node: &Node, parent_area: &mut Area, inner_area: &mut Area, - content_area: &Area, inner_sizes: &mut Size2D, - node_data: &Node, + child_area: &Area, + child_node: &Node, ) { // No need to stack a node that is positioned absolutely - if node_data.position.is_absolute() { + if child_node.position.is_absolute() { return; } match parent_node.direction { DirectionMode::Horizontal => { // Move the available area - available_area.origin.x = content_area.max_x(); - available_area.size.width -= content_area.size.width; + available_area.origin.x = child_area.max_x(); + available_area.size.width -= child_area.size.width; - inner_sizes.height = content_area.height().max(inner_sizes.height); - inner_sizes.width += content_area.width(); + inner_sizes.height = child_area.height().max(inner_sizes.height); + inner_sizes.width += child_area.width(); // Keep the biggest height if parent_node.height.inner_sized() { parent_area.size.height = parent_area.size.height.max( - content_area.size.height + child_area.size.height + parent_node.padding.vertical() + parent_node.margin.vertical(), ); @@ -597,21 +597,21 @@ where // Accumulate width if parent_node.width.inner_sized() { - parent_area.size.width += content_area.size.width; + parent_area.size.width += child_area.size.width; } } DirectionMode::Vertical => { // Move the available area - available_area.origin.y = content_area.max_y(); - available_area.size.height -= content_area.size.height; + available_area.origin.y = child_area.max_y(); + available_area.size.height -= child_area.size.height; - inner_sizes.width = content_area.width().max(inner_sizes.width); - inner_sizes.height += content_area.height(); + inner_sizes.width = child_area.width().max(inner_sizes.width); + inner_sizes.height += child_area.height(); // Keep the biggest width if parent_node.width.inner_sized() { parent_area.size.width = parent_area.size.width.max( - content_area.size.width + child_area.size.width + parent_node.padding.horizontal() + parent_node.margin.horizontal(), ); @@ -623,7 +623,7 @@ where // Accumulate height if parent_node.height.inner_sized() { - parent_area.size.height += content_area.size.height; + parent_area.size.height += child_area.size.height; } } } From 1b08643e048e56e64b1c298c76cc1e92b9febaf7 Mon Sep 17 00:00:00 2001 From: marc2332 Date: Tue, 6 Aug 2024 19:54:00 +0200 Subject: [PATCH 07/14] chore: add clipping back to Button --- crates/components/src/button.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/components/src/button.rs b/crates/components/src/button.rs index 693ed9f92..f2ab2083e 100644 --- a/crates/components/src/button.rs +++ b/crates/components/src/button.rs @@ -181,6 +181,7 @@ pub fn Button( padding: "{padding}", margin: "{margin}", focusable: "true", + overflow: "clip", role: "button", color: "{font_theme.color}", shadow: "{shadow}", From 69538d76251adc59239b9352c9c843eb306218bd Mon Sep 17 00:00:00 2001 From: marc2332 Date: Tue, 6 Aug 2024 19:54:41 +0200 Subject: [PATCH 08/14] chore: Update counter.rs --- examples/counter.rs | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/examples/counter.rs b/examples/counter.rs index 6f0601e62..8768bc3b2 100644 --- a/examples/counter.rs +++ b/examples/counter.rs @@ -18,14 +18,26 @@ fn app() -> Element { width: "100%", main_align: "center", cross_align: "center", + background: "rgb(0, 119, 182)", + color: "white", + shadow: "0 4 20 5 rgb(0, 0, 0, 80)", + label { + font_size: "75", + font_weight: "bold", + "{count}" + } + } + rect { + height: "50%", + width: "100%", direction: "horizontal", Button { onclick: move |_| count += 1, - rect { - width: "25", - height: "20", - background: "red" - } + label { "Increase" } + } + Button { + onclick: move |_| count -= 1, + label { "Decrease" } } } ) From 11dfa203536bd27abc2bf10675e7881d3a6db14a Mon Sep 17 00:00:00 2001 From: marc2332 Date: Tue, 6 Aug 2024 19:55:12 +0200 Subject: [PATCH 09/14] chore: Update counter.rs --- examples/counter.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/counter.rs b/examples/counter.rs index 8768bc3b2..25160e129 100644 --- a/examples/counter.rs +++ b/examples/counter.rs @@ -30,6 +30,8 @@ fn app() -> Element { rect { height: "50%", width: "100%", + main_align: "center", + cross_align: "center", direction: "horizontal", Button { onclick: move |_| count += 1, From 1b4c9fef438c563aa0d483c21eceba6fb9250ae7 Mon Sep 17 00:00:00 2001 From: marc2332 Date: Tue, 6 Aug 2024 21:51:41 +0200 Subject: [PATCH 10/14] fix: inner percentage --- crates/torin/src/measure.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/torin/src/measure.rs b/crates/torin/src/measure.rs index 0e2e00636..c9d6b803f 100644 --- a/crates/torin/src/measure.rs +++ b/crates/torin/src/measure.rs @@ -422,7 +422,7 @@ where } // Final measurement - let (child_revalidated, child_areas) = self.measure_node( + let (child_revalidated, mut child_areas) = self.measure_node( child_id, &child_data, inner_area, @@ -432,6 +432,9 @@ where Phase::Final, ); + // Adjust the size of the area if needed + child_areas.area.adjust_size(&child_data); + // Stack this child into the parent Self::stack_child( available_area, From 749af871907262344b37f2e00e2d4ec05a7111d9 Mon Sep 17 00:00:00 2001 From: marc2332 Date: Tue, 6 Aug 2024 23:14:16 +0200 Subject: [PATCH 11/14] feat: Inline small util functions --- crates/torin/src/geometry.rs | 5 +++++ crates/torin/src/measure.rs | 6 ++++-- crates/torin/src/values/size.rs | 6 ++++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/crates/torin/src/geometry.rs b/crates/torin/src/geometry.rs index 32a5be37c..42ae7918b 100644 --- a/crates/torin/src/geometry.rs +++ b/crates/torin/src/geometry.rs @@ -26,6 +26,7 @@ pub trait AreaModel { } impl AreaModel for Area { + #[inline(always)] fn without_gaps(self, gaps: &Gaps) -> Area { let origin = self.origin; let size = self.size; @@ -38,11 +39,13 @@ impl AreaModel for Area { ) } + #[inline(always)] fn move_with_offsets(&mut self, offset_x: &Length, offset_y: &Length) { self.origin.x += offset_x.get(); self.origin.y += offset_y.get(); } + #[inline(always)] fn adjust_size(&mut self, node: &Node) { if let Size::InnerPercentage(p) = node.width { self.size.width *= p.get() / 100.; @@ -65,6 +68,7 @@ pub enum AlignAxis { } impl AlignAxis { + #[inline(always)] pub fn new(direction: &DirectionMode, alignment_direction: AlignmentDirection) -> Self { match direction { DirectionMode::Vertical => match alignment_direction { @@ -85,6 +89,7 @@ pub trait SizeModel { } impl SizeModel for Size2D { + #[inline(always)] fn with_gaps(self, gap: &Gaps) -> Size2D { Size2D::new(self.width + gap.horizontal(), self.height + gap.vertical()) } diff --git a/crates/torin/src/measure.rs b/crates/torin/src/measure.rs index c9d6b803f..feacc31e8 100644 --- a/crates/torin/src/measure.rs +++ b/crates/torin/src/measure.rs @@ -52,7 +52,6 @@ where { /// Measure a Node. #[allow(clippy::too_many_arguments)] - #[inline(always)] pub fn measure_node( &mut self, // ID for this Node @@ -273,7 +272,6 @@ where /// Measure the children layouts of a Node #[allow(clippy::too_many_arguments)] - #[inline(always)] pub fn measure_children( &mut self, parent_node_id: &Key, @@ -462,6 +460,7 @@ where } /// Align the content of this node. + #[inline(always)] fn align_content( available_area: &mut Area, inner_area: &Area, @@ -499,6 +498,7 @@ where /// Align the position of this node. #[allow(clippy::too_many_arguments)] + #[inline(always)] fn align_position( alignment_direction: AlignmentDirection, available_area: &mut Area, @@ -562,6 +562,7 @@ where } /// Stack a child Node into its parent + #[inline(always)] fn stack_child( available_area: &mut Area, parent_node: &Node, @@ -637,6 +638,7 @@ where /// The intended usage is to call this after the first measurement and before the second, /// this way the second measurement will align the content relatively to the parent element instead /// of overflowing due to being aligned relatively to the upper parent element + #[inline(always)] fn shrink_area_to_fit_when_unbounded( available_area: &mut Area, parent_area: &Area, diff --git a/crates/torin/src/values/size.rs b/crates/torin/src/values/size.rs index 38c7ee46c..021ce539f 100644 --- a/crates/torin/src/values/size.rs +++ b/crates/torin/src/values/size.rs @@ -27,6 +27,7 @@ impl Default for Size { } impl Size { + #[inline(always)] pub fn inner_sized(&self) -> bool { matches!( self, @@ -34,6 +35,7 @@ impl Size { ) } + #[inline(always)] pub fn inner_percentage_sized(&self) -> bool { matches!(self, Self::InnerPercentage(_)) } @@ -58,6 +60,7 @@ impl Size { } } + #[inline(always)] pub fn eval( &self, parent_value: f32, @@ -85,6 +88,7 @@ impl Size { } } + #[inline(always)] #[allow(clippy::too_many_arguments)] pub fn min_max( &self, @@ -142,6 +146,7 @@ impl Size { final_value } + #[inline(always)] pub fn most_fitting_size<'a>(&self, size: &'a f32, available_size: &'a f32) -> &'a f32 { match self { Self::Inner | Self::InnerPercentage(_) => available_size, @@ -151,6 +156,7 @@ impl Size { } impl Scaled for Size { + #[inline(always)] fn scale(&mut self, scale_factor: f32) { match self { Size::Pixels(s) => *s *= scale_factor, From 6d1964d98c2f2b5c6ecf7fe948a93f9174b40170 Mon Sep 17 00:00:00 2001 From: marc2332 Date: Sun, 18 Aug 2024 11:37:34 +0200 Subject: [PATCH 12/14] chore: Inline suggestions --- crates/torin/src/measure.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/torin/src/measure.rs b/crates/torin/src/measure.rs index feacc31e8..073c8b870 100644 --- a/crates/torin/src/measure.rs +++ b/crates/torin/src/measure.rs @@ -52,6 +52,7 @@ where { /// Measure a Node. #[allow(clippy::too_many_arguments)] + #[inline] pub fn measure_node( &mut self, // ID for this Node @@ -272,6 +273,7 @@ where /// Measure the children layouts of a Node #[allow(clippy::too_many_arguments)] + #[inline] pub fn measure_children( &mut self, parent_node_id: &Key, From 379363caed60bcab4e532a8d42e882a9fe563363 Mon Sep 17 00:00:00 2001 From: marc2332 Date: Sun, 18 Aug 2024 11:49:32 +0200 Subject: [PATCH 13/14] chore: Small tweaks --- crates/torin/src/measure.rs | 4 ++-- crates/torin/src/values/size.rs | 9 +++------ 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/crates/torin/src/measure.rs b/crates/torin/src/measure.rs index 073c8b870..502a82087 100644 --- a/crates/torin/src/measure.rs +++ b/crates/torin/src/measure.rs @@ -52,7 +52,7 @@ where { /// Measure a Node. #[allow(clippy::too_many_arguments)] - #[inline] + #[inline(always)] pub fn measure_node( &mut self, // ID for this Node @@ -273,7 +273,7 @@ where /// Measure the children layouts of a Node #[allow(clippy::too_many_arguments)] - #[inline] + #[inline(always)] pub fn measure_children( &mut self, parent_node_id: &Key, diff --git a/crates/torin/src/values/size.rs b/crates/torin/src/values/size.rs index 021ce539f..e76cd4f9c 100644 --- a/crates/torin/src/values/size.rs +++ b/crates/torin/src/values/size.rs @@ -27,7 +27,7 @@ impl Default for Size { } impl Size { - #[inline(always)] + #[inline] pub fn inner_sized(&self) -> bool { matches!( self, @@ -35,7 +35,7 @@ impl Size { ) } - #[inline(always)] + #[inline] pub fn inner_percentage_sized(&self) -> bool { matches!(self, Self::InnerPercentage(_)) } @@ -60,7 +60,6 @@ impl Size { } } - #[inline(always)] pub fn eval( &self, parent_value: f32, @@ -88,7 +87,6 @@ impl Size { } } - #[inline(always)] #[allow(clippy::too_many_arguments)] pub fn min_max( &self, @@ -146,7 +144,7 @@ impl Size { final_value } - #[inline(always)] + #[inline] pub fn most_fitting_size<'a>(&self, size: &'a f32, available_size: &'a f32) -> &'a f32 { match self { Self::Inner | Self::InnerPercentage(_) => available_size, @@ -156,7 +154,6 @@ impl Size { } impl Scaled for Size { - #[inline(always)] fn scale(&mut self, scale_factor: f32) { match self { Size::Pixels(s) => *s *= scale_factor, From 867bdc5c61b45a9ffb0e52222f2a66064da07946 Mon Sep 17 00:00:00 2001 From: marc2332 Date: Sun, 18 Aug 2024 11:58:12 +0200 Subject: [PATCH 14/14] chore: Small tweaks --- crates/torin/src/geometry.rs | 5 ----- crates/torin/src/values/size.rs | 3 --- 2 files changed, 8 deletions(-) diff --git a/crates/torin/src/geometry.rs b/crates/torin/src/geometry.rs index 42ae7918b..32a5be37c 100644 --- a/crates/torin/src/geometry.rs +++ b/crates/torin/src/geometry.rs @@ -26,7 +26,6 @@ pub trait AreaModel { } impl AreaModel for Area { - #[inline(always)] fn without_gaps(self, gaps: &Gaps) -> Area { let origin = self.origin; let size = self.size; @@ -39,13 +38,11 @@ impl AreaModel for Area { ) } - #[inline(always)] fn move_with_offsets(&mut self, offset_x: &Length, offset_y: &Length) { self.origin.x += offset_x.get(); self.origin.y += offset_y.get(); } - #[inline(always)] fn adjust_size(&mut self, node: &Node) { if let Size::InnerPercentage(p) = node.width { self.size.width *= p.get() / 100.; @@ -68,7 +65,6 @@ pub enum AlignAxis { } impl AlignAxis { - #[inline(always)] pub fn new(direction: &DirectionMode, alignment_direction: AlignmentDirection) -> Self { match direction { DirectionMode::Vertical => match alignment_direction { @@ -89,7 +85,6 @@ pub trait SizeModel { } impl SizeModel for Size2D { - #[inline(always)] fn with_gaps(self, gap: &Gaps) -> Size2D { Size2D::new(self.width + gap.horizontal(), self.height + gap.vertical()) } diff --git a/crates/torin/src/values/size.rs b/crates/torin/src/values/size.rs index e76cd4f9c..38c7ee46c 100644 --- a/crates/torin/src/values/size.rs +++ b/crates/torin/src/values/size.rs @@ -27,7 +27,6 @@ impl Default for Size { } impl Size { - #[inline] pub fn inner_sized(&self) -> bool { matches!( self, @@ -35,7 +34,6 @@ impl Size { ) } - #[inline] pub fn inner_percentage_sized(&self) -> bool { matches!(self, Self::InnerPercentage(_)) } @@ -144,7 +142,6 @@ impl Size { final_value } - #[inline] pub fn most_fitting_size<'a>(&self, size: &'a f32, available_size: &'a f32) -> &'a f32 { match self { Self::Inner | Self::InnerPercentage(_) => available_size,