diff --git a/benches/benches/mixed.rs b/benches/benches/mixed.rs index c6f97b5f0..3b0bb6e03 100644 --- a/benches/benches/mixed.rs +++ b/benches/benches/mixed.rs @@ -158,8 +158,15 @@ fn mixed_benchmark(c: &mut Criterion) { .compute_layout_with_measure( root, Size::MAX_CONTENT, - |known_dimensions, available_space, _node_id, node_context, _style| { - measure_function(known_dimensions, available_space, node_context) + |inputs, _node_id, node_context, style| { + taffy::compute_leaf_layout( + inputs, + style, + |_, _| 0.0, + |known_dimensions, available_space| { + measure_function(known_dimensions, available_space, node_context) + }, + ) }, ) .unwrap(); diff --git a/examples/cosmic_text/src/main.rs b/examples/cosmic_text/src/main.rs index a9b05b11e..4ca834787 100644 --- a/examples/cosmic_text/src/main.rs +++ b/examples/cosmic_text/src/main.rs @@ -108,8 +108,10 @@ fn main() -> Result<(), taffy::TaffyError> { Size::MAX_CONTENT, // Note: this closure is a FnMut closure and can be used to borrow external context for the duration of layout // For example, you may wish to borrow a global font registry and pass it into your text measuring function - |known_dimensions, available_space, _node_id, node_context, _style| { - measure_function(known_dimensions, available_space, node_context, &mut font_system) + |inputs, _node_id, node_context, style| { + taffy::compute_leaf_layout(inputs, style, |_, _| 0.0, |known_dimensions, available_space| { + measure_function(known_dimensions, available_space, node_context, &mut font_system) + }) }, )?; taffy.print_tree(root); diff --git a/examples/measure.rs b/examples/measure.rs index 9f68adb70..9c496b162 100644 --- a/examples/measure.rs +++ b/examples/measure.rs @@ -59,8 +59,15 @@ fn main() -> Result<(), taffy::TaffyError> { Size::MAX_CONTENT, // Note: this closure is a FnMut closure and can be used to borrow external context for the duration of layout // For example, you may wish to borrow a global font registry and pass it into your text measuring function - |known_dimensions, available_space, _node_id, node_context, _style| { - measure_function(known_dimensions, available_space, node_context, &font_metrics) + |inputs, _node_id, node_context, style| { + taffy::compute_leaf_layout( + inputs, + style, + |_, _| 0.0, + |known_dimensions, available_space| { + measure_function(known_dimensions, available_space, node_context, &font_metrics) + }, + ) }, )?; taffy.print_tree(root); diff --git a/src/tree/taffy_tree.rs b/src/tree/taffy_tree.rs index ab9beb038..cb79a37f8 100644 --- a/src/tree/taffy_tree.rs +++ b/src/tree/taffy_tree.rs @@ -264,8 +264,7 @@ impl PrintTree for TaffyTree { /// which makes the lifetimes of the context much more flexible. pub(crate) struct TaffyView<'t, NodeContext, MeasureFunction> where - MeasureFunction: - FnMut(Size>, Size, NodeId, Option<&mut NodeContext>, &Style) -> Size, + MeasureFunction: FnMut(LayoutInput, NodeId, Option<&mut NodeContext>, &Style) -> LayoutOutput, { /// A reference to the TaffyTree pub(crate) taffy: &'t mut TaffyTree, @@ -275,8 +274,7 @@ where impl TaffyView<'_, NodeContext, MeasureFunction> where - MeasureFunction: - FnMut(Size>, Size, NodeId, Option<&mut NodeContext>, &Style) -> Size, + MeasureFunction: FnMut(LayoutInput, NodeId, Option<&mut NodeContext>, &Style) -> LayoutOutput, { #[inline(always)] /// Unified implementation that both `LayoutPartialTree::compute_child_layout` @@ -320,10 +318,7 @@ where let style = &tree.taffy.nodes[node_key].style; let has_context = tree.taffy.nodes[node_key].has_context; let node_context = has_context.then(|| tree.taffy.node_context_data.get_mut(node_key)).flatten(); - let measure_function = |known_dimensions, available_space| { - (tree.measure_function)(known_dimensions, available_space, node_id, node_context, style) - }; - compute_leaf_layout(inputs, style, |_, _| 0.0, measure_function) + (tree.measure_function)(inputs, node_id, node_context, style) } } }) @@ -333,8 +328,7 @@ where // TraversePartialTree impl for TaffyView impl TraversePartialTree for TaffyView<'_, NodeContext, MeasureFunction> where - MeasureFunction: - FnMut(Size>, Size, NodeId, Option<&mut NodeContext>, &Style) -> Size, + MeasureFunction: FnMut(LayoutInput, NodeId, Option<&mut NodeContext>, &Style) -> LayoutOutput, { type ChildIter<'a> = TaffyTreeChildIter<'a> @@ -359,16 +353,14 @@ where // TraverseTree impl for TaffyView impl TraverseTree for TaffyView<'_, NodeContext, MeasureFunction> where - MeasureFunction: - FnMut(Size>, Size, NodeId, Option<&mut NodeContext>, &Style) -> Size + MeasureFunction: FnMut(LayoutInput, NodeId, Option<&mut NodeContext>, &Style) -> LayoutOutput { } // LayoutPartialTree impl for TaffyView impl LayoutPartialTree for TaffyView<'_, NodeContext, MeasureFunction> where - MeasureFunction: - FnMut(Size>, Size, NodeId, Option<&mut NodeContext>, &Style) -> Size, + MeasureFunction: FnMut(LayoutInput, NodeId, Option<&mut NodeContext>, &Style) -> LayoutOutput, { type CoreContainerStyle<'a> = &'a Style @@ -405,8 +397,7 @@ where impl CacheTree for TaffyView<'_, NodeContext, MeasureFunction> where - MeasureFunction: - FnMut(Size>, Size, NodeId, Option<&mut NodeContext>, &Style) -> Size, + MeasureFunction: FnMut(LayoutInput, NodeId, Option<&mut NodeContext>, &Style) -> LayoutOutput, { fn cache_get(&self, node_id: NodeId, input: &LayoutInput) -> Option { self.taffy.nodes[node_id.into()].cache.get(input) @@ -424,8 +415,7 @@ where #[cfg(feature = "block_layout")] impl LayoutBlockContainer for TaffyView<'_, NodeContext, MeasureFunction> where - MeasureFunction: - FnMut(Size>, Size, NodeId, Option<&mut NodeContext>, &Style) -> Size, + MeasureFunction: FnMut(LayoutInput, NodeId, Option<&mut NodeContext>, &Style) -> LayoutOutput, { type BlockContainerStyle<'a> = &'a Style @@ -460,8 +450,7 @@ where #[cfg(feature = "flexbox")] impl LayoutFlexboxContainer for TaffyView<'_, NodeContext, MeasureFunction> where - MeasureFunction: - FnMut(Size>, Size, NodeId, Option<&mut NodeContext>, &Style) -> Size, + MeasureFunction: FnMut(LayoutInput, NodeId, Option<&mut NodeContext>, &Style) -> LayoutOutput, { type FlexboxContainerStyle<'a> = &'a Style @@ -486,8 +475,7 @@ where #[cfg(feature = "grid")] impl LayoutGridContainer for TaffyView<'_, NodeContext, MeasureFunction> where - MeasureFunction: - FnMut(Size>, Size, NodeId, Option<&mut NodeContext>, &Style) -> Size, + MeasureFunction: FnMut(LayoutInput, NodeId, Option<&mut NodeContext>, &Style) -> LayoutOutput, { type GridContainerStyle<'a> = &'a Style @@ -518,8 +506,7 @@ where // RoundTree impl for TaffyView impl RoundTree for TaffyView<'_, NodeContext, MeasureFunction> where - MeasureFunction: - FnMut(Size>, Size, NodeId, Option<&mut NodeContext>, &Style) -> Size, + MeasureFunction: FnMut(LayoutInput, NodeId, Option<&mut NodeContext>, &Style) -> LayoutOutput, { #[inline(always)] fn get_unrounded_layout(&self, node: NodeId) -> Layout { @@ -909,8 +896,7 @@ impl TaffyTree { measure_function: MeasureFunction, ) -> Result<(), TaffyError> where - MeasureFunction: - FnMut(Size>, Size, NodeId, Option<&mut NodeContext>, &Style) -> Size, + MeasureFunction: FnMut(LayoutInput, NodeId, Option<&mut NodeContext>, &Style) -> LayoutOutput, { let use_rounding = self.config.use_rounding; let mut taffy_view = TaffyView { taffy: self, measure_function }; @@ -923,7 +909,9 @@ impl TaffyTree { /// Updates the stored layout of the provided `node` and its children pub fn compute_layout(&mut self, node: NodeId, available_space: Size) -> Result<(), TaffyError> { - self.compute_layout_with_measure(node, available_space, |_, _, _, _, _| Size::ZERO) + self.compute_layout_with_measure(node, available_space, |inputs, _, _, style| { + compute_leaf_layout(inputs, style, |_, _| 0.0, |_, _| Size::ZERO) + }) } /// Prints a debug representation of the tree's layout @@ -935,7 +923,10 @@ impl TaffyTree { /// Returns an instance of LayoutTree representing the TaffyTree #[cfg(test)] pub(crate) fn as_layout_tree(&mut self) -> impl LayoutPartialTree + CacheTree + '_ { - TaffyView { taffy: self, measure_function: |_, _, _, _, _| Size::ZERO } + TaffyView { + taffy: self, + measure_function: |inputs, _, _, style| compute_leaf_layout(inputs, style, |_, _| 0.0, |_, _| Size::ZERO), + } } } @@ -948,13 +939,19 @@ mod tests { use crate::util::sys; fn size_measure_function( - known_dimensions: Size>, - _available_space: Size, + inputs: LayoutInput, _node_id: NodeId, node_context: Option<&mut Size>, - _style: &Style, - ) -> Size { - known_dimensions.unwrap_or(node_context.cloned().unwrap_or(Size::ZERO)) + style: &Style, + ) -> LayoutOutput { + compute_leaf_layout( + inputs, + style, + |_, _| 0.0, + |known_dimensions, _available_space| { + known_dimensions.unwrap_or(node_context.cloned().unwrap_or(Size::ZERO)) + }, + ) } #[test] diff --git a/tests/common/src/lib.rs b/tests/common/src/lib.rs index 653af6938..8e80eb166 100644 --- a/tests/common/src/lib.rs +++ b/tests/common/src/lib.rs @@ -1,4 +1,4 @@ -use taffy::{AvailableSpace, NodeId, Size, Style, TaffyTree}; +use taffy::{compute_leaf_layout, LayoutInput, LayoutOutput, NodeId, Size, Style, TaffyTree}; /// Creates a `TaffyTree` that uses `TestNodeContext`. The purpose of this function is /// to allow `TaffyTree` to be monomophised once in this crate rather than separately for @@ -61,32 +61,38 @@ pub enum TestMeasureData { /// A measure function for tests that works with `TestNodeContext` pub fn test_measure_function( - known_dimensions: Size>, - available_space: Size, + inputs: LayoutInput, _node_id: NodeId, context: Option<&mut TestNodeContext>, - _style: &Style, -) -> Size { - if let Size { width: Some(width), height: Some(height) } = known_dimensions { - return Size { width, height }; - } + style: &Style, +) -> LayoutOutput { + compute_leaf_layout( + inputs, + style, + |_, _| 0.0, + |known_dimensions, available_space| { + if let Size { width: Some(width), height: Some(height) } = known_dimensions { + return Size { width, height }; + } - let Some(context) = context else { return known_dimensions.map(|d| d.unwrap_or(0.0)) }; + let Some(context) = context else { return known_dimensions.map(|d| d.unwrap_or(0.0)) }; - // Increment count - context.count += 1; + // Increment count + context.count += 1; - let compute_size = match &context.measure_data { - TestMeasureData::Zero => Size::ZERO, - TestMeasureData::Fixed(size) => *size, - TestMeasureData::AspectRatio(data) => data.measure(known_dimensions), - TestMeasureData::AhemText(data) => data.measure(known_dimensions, available_space), - }; + let compute_size = match &context.measure_data { + TestMeasureData::Zero => Size::ZERO, + TestMeasureData::Fixed(size) => *size, + TestMeasureData::AspectRatio(data) => data.measure(known_dimensions), + TestMeasureData::AhemText(data) => data.measure(known_dimensions, available_space), + }; - Size { - width: known_dimensions.width.unwrap_or(compute_size.width), - height: known_dimensions.height.unwrap_or(compute_size.height), - } + Size { + width: known_dimensions.width.unwrap_or(compute_size.width), + height: known_dimensions.height.unwrap_or(compute_size.height), + } + }, + ) } /// Measure data for nodes that returns results based on an intrinsic aspect ratio diff --git a/tests/hand_written.rs b/tests/hand_written.rs index 965aa5d28..041348555 100644 --- a/tests/hand_written.rs +++ b/tests/hand_written.rs @@ -1,4 +1,5 @@ mod hand_written { + mod baseline; mod border_and_padding; mod caching; mod measure; diff --git a/tests/hand_written/baseline.rs b/tests/hand_written/baseline.rs new file mode 100644 index 000000000..3602d884d --- /dev/null +++ b/tests/hand_written/baseline.rs @@ -0,0 +1,115 @@ +#[cfg(test)] +mod baseline { + use taffy::prelude::*; + use taffy::{LayoutInput, LayoutOutput, Point}; + + /// A node context that pairs an intrinsic size with a first-baseline offset + /// (measured from the node's top edge in the block axis). + #[derive(Debug, Clone, Copy)] + struct BaselineContext { + size: Size, + baseline_y: f32, + } + + fn baseline_measure_function( + _inputs: LayoutInput, + _node_id: NodeId, + context: Option<&mut BaselineContext>, + _style: &Style, + ) -> LayoutOutput { + let Some(context) = context else { return LayoutOutput::DEFAULT }; + LayoutOutput::from_sizes_and_baselines( + context.size, + Size::ZERO, + Point { x: None, y: Some(context.baseline_y) }, + ) + } + + /// Two flex items with different intrinsic baselines are aligned along their baselines + /// when the container uses `align-items: baseline`. The item with the smaller distance + /// from its top to its baseline should be shifted down so that both baselines coincide. + #[test] + fn flex_baseline_alignment_uses_measure_function_baseline() { + let mut taffy: TaffyTree = TaffyTree::new(); + + // Child A: 50x50 box with baseline 40px from the top + let child_a = taffy + .new_leaf_with_context( + Style::default(), + BaselineContext { size: Size { width: 50.0, height: 50.0 }, baseline_y: 40.0 }, + ) + .unwrap(); + + // Child B: 30x30 box with baseline 20px from the top + let child_b = taffy + .new_leaf_with_context( + Style::default(), + BaselineContext { size: Size { width: 30.0, height: 30.0 }, baseline_y: 20.0 }, + ) + .unwrap(); + + let root = taffy + .new_with_children( + Style { + display: Display::Flex, + flex_direction: FlexDirection::Row, + align_items: Some(AlignItems::Baseline), + size: Size { width: length(200.0), height: length(100.0) }, + ..Default::default() + }, + &[child_a, child_b], + ) + .unwrap(); + + taffy.compute_layout_with_measure(root, Size::MAX_CONTENT, baseline_measure_function).unwrap(); + + let layout_a = taffy.layout(child_a).unwrap(); + let layout_b = taffy.layout(child_b).unwrap(); + + // Child A sets the max baseline (40 px from its top), so it sits at the top of the line. + assert_eq!(layout_a.location.y, 0.0); + // Child B is shifted down by (40 - 20) = 20 px so its baseline aligns with child A's. + assert_eq!(layout_b.location.y, 20.0); + + // Sanity-check: both children's baselines now sit at y = 40 in the container. + assert_eq!(layout_a.location.y + 40.0, layout_b.location.y + 20.0); + } + + /// Sanity-check: without `align-items: baseline`, items align at the cross-start edge + /// regardless of the baseline reported by the measure function. + #[test] + fn flex_baseline_is_ignored_when_alignment_is_not_baseline() { + let mut taffy: TaffyTree = TaffyTree::new(); + + let child_a = taffy + .new_leaf_with_context( + Style::default(), + BaselineContext { size: Size { width: 50.0, height: 50.0 }, baseline_y: 40.0 }, + ) + .unwrap(); + let child_b = taffy + .new_leaf_with_context( + Style::default(), + BaselineContext { size: Size { width: 30.0, height: 30.0 }, baseline_y: 20.0 }, + ) + .unwrap(); + + let root = taffy + .new_with_children( + Style { + display: Display::Flex, + flex_direction: FlexDirection::Row, + align_items: Some(AlignItems::FlexStart), + size: Size { width: length(200.0), height: length(100.0) }, + ..Default::default() + }, + &[child_a, child_b], + ) + .unwrap(); + + taffy.compute_layout_with_measure(root, Size::MAX_CONTENT, baseline_measure_function).unwrap(); + + assert_eq!(taffy.layout(child_a).unwrap().location.y, 0.0); + assert_eq!(taffy.layout(child_b).unwrap().location.y, 0.0); + } +} diff --git a/tests/hand_written/measure.rs b/tests/hand_written/measure.rs index 3aad93fca..cdbcf3264 100644 --- a/tests/hand_written/measure.rs +++ b/tests/hand_written/measure.rs @@ -1,6 +1,7 @@ #[cfg(test)] mod measure { use taffy::prelude::*; + use taffy::{LayoutInput, LayoutOutput}; use taffy_test_helpers::{new_test_tree, test_measure_function, TestNodeContext}; const HUNDRED_HUNDRED: TestNodeContext = TestNodeContext::fixed(100.0, 100.0); @@ -209,15 +210,21 @@ mod measure { let mut taffy: TaffyTree<()> = TaffyTree::new(); fn custom_measure_function( - known_dimensions: Size>, - _available_space: Size, + inputs: LayoutInput, _node_id: NodeId, _node_context: Option<&mut ()>, - _style: &Style, - ) -> taffy::geometry::Size { - let height = known_dimensions.height.unwrap_or(50.0); - let width = known_dimensions.width.unwrap_or(height); - Size { width, height } + style: &Style, + ) -> LayoutOutput { + taffy::compute_leaf_layout( + inputs, + style, + |_, _| 0.0, + |known_dimensions, _available_space| { + let height = known_dimensions.height.unwrap_or(50.0); + let width = known_dimensions.width.unwrap_or(height); + Size { width, height } + }, + ) } let child = taffy.new_leaf_with_context(Style::default(), ()).unwrap();