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 5767f9f95..32a5be37c 100644 --- a/crates/torin/src/geometry.rs +++ b/crates/torin/src/geometry.rs @@ -1,11 +1,8 @@ -use crate::{ - node::Node, - prelude::{ - Alignment, - DirectionMode, - Gaps, - Size, - }, +use crate::prelude::{ + DirectionMode, + Gaps, + Node, + Size, }; #[derive(PartialEq)] @@ -18,157 +15,34 @@ 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. - 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, - ); - + /// Adjust the size given the Node data fn adjust_size(&mut self, node: &Node); } 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(); } - fn align_content( - &mut self, - available_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 = - (available_area.height() / 2.0) - (contents_size.height / 2.0); - - self.origin.y = available_area.min_y() + new_origin_y; - } - Alignment::End => { - self.origin.y = available_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; - } - Alignment::End => { - self.origin.x = available_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; - } - _ => {} - }, - } - } - fn adjust_size(&mut self, node: &Node) { if let Size::InnerPercentage(p) = node.width { self.size.width *= p.get() / 100.; @@ -179,22 +53,6 @@ impl AreaModel for Area { } } -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 { Main, Cross, @@ -205,3 +63,29 @@ pub enum AlignAxis { Height, 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; +} + +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/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 b667b8273..502a82087 100644 --- a/crates/torin/src/measure.rs +++ b/crates/torin/src/measure.rs @@ -12,11 +12,13 @@ use crate::{ Area, Size2D, }, - measure_mode::MeasureMode, node::Node, prelude::{ + AlignAxis, + Alignment, AlignmentDirection, AreaModel, + DirectionMode, LayoutMetadata, Torin, }, @@ -30,420 +32,690 @@ 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, - - invalidated_tree: bool, - - phase: Phase, -) -> (bool, LayoutNode) { - let must_revalidate = invalidated_tree - || 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) - } - } 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() { - 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, + // 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, ); } - 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, - ); - } - 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) - .after_gaps(&node.padding) - .after_gaps(&node.margin); - let mut inner_sizes = Size2D::default(); + ( + must_cache_children, + LayoutNode { + area, + margin: node.margin, + inner_area, + inner_sizes, + data: node_data, + }, + ) + } else { + let layout_node = self.layout.get(node_id).unwrap().clone(); - 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; + 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 mut measurement_mode = MeasureMode::ParentIsNotCached { - area: &mut area, - inner_area: &mut inner_area, + let measure_inner_children = if let Some(measurer) = self.measurer { + measurer.should_measure_inner_children(node_id) + } else { + true }; - // 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 measurement_mode, - dom_adapter, - layout_metadata, - 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, + ); + } + + (false, layout_node) } + } - ( - 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(); + /// 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; + } - let mut inner_sizes = layout_node.inner_sizes; - let mut available_area = layout_node.inner_area; + let inner_area = initial_phase_inner_area; - available_area.move_with_offsets(&node.offset_x, &node.offset_y); + let (_, child_areas) = self.measure_node( + *child_id, + &child_data, + &inner_area, + &initial_phase_available_area, + false, + parent_is_dirty, + Phase::Initial, + ); - let mut measurement_mode = MeasureMode::ParentIsCached { - inner_area: &layout_node.inner_area, - }; + // Stack this child into the parent + Self::stack_child( + &mut initial_phase_available_area, + parent_node, + &mut initial_phase_area, + &mut initial_phase_inner_area, + &mut initial_phase_inner_sizes, + &child_areas.area, + &child_data, + ); - let measure_inner_children = if let Some(measurer) = measurer { - measurer.should_measure_inner_children(node_id) - } else { - true - }; + if parent_node.cross_alignment.is_not_start() + || parent_node.main_alignment.is_spaced() + { + initial_phase_sizes.insert(*child_id, child_areas.area.size); + } + } - if measure_inner_children { - measure_inner_nodes( - &node_id, - node, - layout, - &mut available_area, - &mut inner_sizes, - measurer, - must_cache_inner_nodes, - &mut measurement_mode, - dom_adapter, - layout_metadata, - false, - ); + 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, + ); + + // 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 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, - mode: &mut MeasureMode, - // Adapter for the provided DOM - dom_adapter: &mut impl DOMAdapter, - - layout_metadata: &LayoutMetadata, - - invalidated_tree: 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_mode = mode.to_owned(); - let mut initial_phase_mode = initial_phase_mode.to_mut(); - 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; }; - 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( + AlignmentDirection::Main, + &mut adapted_available_area, + &initial_available_area, + &initial_phase_inner_sizes, + &parent_node.main_alignment, + &parent_node.direction, + initial_phase_sizes.len(), + child_n, + ); } - let inner_area = *initial_phase_mode.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, mut child_areas) = self.measure_node( + child_id, &child_data, - layout, - &inner_area, - &initial_phase_available_area, - measurer, - false, - dom_adapter, - layout_metadata, - invalidated_tree, - Phase::Initial, + inner_area, + &adapted_available_area, + must_cache_children, + parent_is_dirty, + Phase::Final, ); - initial_phase_mode.stack_into_node( + // 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, parent_node, - &mut initial_phase_available_area, + area, + inner_area, + inner_sizes, &child_areas.area, - &mut initial_phase_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 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 { + 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 - initial_phase_mode.fit_bounds_when_unspecified( - parent_node, - AlignmentDirection::Main, - available_area, - ); - - // 3. Align the Main axis - available_area.align_content( - initial_phase_mode.inner_area(), - &initial_phase_inner_sizes, - &parent_node.main_alignment, - &parent_node.direction, - AlignmentDirection::Main, - ); + /// Align the content of this node. + #[inline(always)] + 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 - initial_phase_mode.fit_bounds_when_unspecified( - parent_node, - AlignmentDirection::Cross, - available_area, - ); + /// Align the position of this node. + #[allow(clippy::too_many_arguments)] + #[inline(always)] + fn align_position( + alignment_direction: AlignmentDirection, + available_area: &mut Area, + initial_available_area: &Area, + inner_sizes: &Size2D, + alignment: &Alignment, + direction: &DirectionMode, + 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 child Node into its parent + #[inline(always)] + fn stack_child( + available_area: &mut Area, + parent_node: &Node, + parent_area: &mut Area, + inner_area: &mut Area, + inner_sizes: &mut Size2D, + child_area: &Area, + child_node: &Node, + ) { + // No need to stack a node that is positioned absolutely + if child_node.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 = child_area.max_x(); + available_area.size.width -= child_area.size.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( + child_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 += child_area.size.width; + } } - } + DirectionMode::Vertical => { + // Move the available area + available_area.origin.y = child_area.max_y(); + available_area.size.height -= child_area.size.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( + child_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(); + } - let inner_area = *mode.inner_area(); - - // Final measurement - let (child_revalidated, mut child_areas) = measure_node( - child_id, - &child_data, - layout, - &inner_area, - &adapted_available_area, - measurer, - must_cache_inner_nodes, - dom_adapter, - layout_metadata, - invalidated_tree, - Phase::Final, - ); - - // Adjust the size of the area if needed - child_areas.area.adjust_size(&child_data); - - // Stack the child into its parent - mode.stack_into_node( - parent_node, - available_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 += child_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 + #[inline(always)] + 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/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 } - } - } - } -} diff --git a/crates/torin/src/torin.rs b/crates/torin/src/torin.rs index accb34be3..5d3ad4204 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, mut root_layout_node) = measure_node( + let mut measure_context = MeasureContext { + layout: self, + layout_metadata, + dom_adapter, + measurer, + }; + + let (root_revalidated, mut 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, );