gioser

tawasuyu/taffy

A high performance rust-powered UI layout library

HTML 5544Rust 88Markdown 11TOML 91926 Definitions

Setup BFC inheritance

3a7cffacad626907022ebdb05ec6baf5a61bfb7b · by Nico Burns · (1 year ago) · parent 22a79ba0e · browse the tree at this commit

Semantic delta · 17

src/compute/block.rs

src/compute/float.rs

src/compute/mod.rs

src/tree/taffy_tree.rs

src/tree/traits.rs

6 files · +195 −67

diff --git a/src/compute/block.rs b/src/compute/block.rs
index 35f5be89f..e16b53eef 100644
--- a/src/compute/block.rs
+++ b/src/compute/block.rs
@@ -11,6 +11,30 @@ use crate::util::MaybeMath;
 use crate::util::{MaybeResolve, ResolveOrZero};
 use crate::{BlockContainerStyle, BlockItemStyle, BoxGenerationMode, BoxSizing, LayoutBlockContainer, TextAlign};
 
+#[cfg(feature = "float_layout")]
+use super::{FloatContext, FloatDirection, FloatedBox};
+
+/// Context for positioning Block and Float boxes within a Block Formatting Context
+pub struct BlockFormattingContext {
+    #[cfg(feature = "float_layout")]
+    float_context: super::FloatContext,
+}
+
+impl BlockFormattingContext {
+    /// Create a new `BlockFormattingContext` with the specified width constraint
+    pub fn new(available_space: AvailableSpace) -> Self {
+        Self {
+            #[cfg(feature = "float_layout")]
+            float_context: FloatContext::new(available_space),
+        }
+    }
+
+    #[cfg(feature = "float_layout")]
+    pub fn place_floated_box(&mut self, floated_box: FloatedBox, float_direction: FloatDirection) -> Point<f32> {
+        self.float_context.place_floated_box(floated_box, float_direction)
+    }
+}
+
 #[cfg(feature = "content_size")]
 use super::common::content_size::compute_content_size_contribution;
 
@@ -65,6 +89,7 @@ pub fn compute_block_layout(
     tree: &mut impl LayoutBlockContainer,
     node_id: NodeId,
     inputs: LayoutInput,
+    bfc: Option<&mut BlockFormattingContext>,
 ) -> LayoutOutput {
     let LayoutInput { known_dimensions, parent_size, run_mode, .. } = inputs;
     let style = tree.get_block_container_style(node_id);
@@ -117,12 +142,28 @@ pub fn compute_block_layout(
         }
     }
 
+    // Unwrap the block formatting context if one was passed, or else create a new one
+    let mut new_bfc: BlockFormattingContext;
+    let bfc_ref: &mut BlockFormattingContext;
+    match bfc {
+        Some(inherited_bfc) => bfc_ref = inherited_bfc,
+        None => {
+            new_bfc = BlockFormattingContext::new(inputs.available_space.width);
+            bfc_ref = &mut new_bfc;
+        }
+    }
+
     debug_log!("BLOCK");
-    compute_inner(tree, node_id, LayoutInput { known_dimensions: styled_based_known_dimensions, ..inputs })
+    compute_inner(tree, node_id, LayoutInput { known_dimensions: styled_based_known_dimensions, ..inputs }, bfc_ref)
 }
 
 /// Computes the layout of [`LayoutBlockContainer`] according to the block layout algorithm
-fn compute_inner(tree: &mut impl LayoutBlockContainer, node_id: NodeId, inputs: LayoutInput) -> LayoutOutput {
+fn compute_inner(
+    tree: &mut impl LayoutBlockContainer,
+    node_id: NodeId,
+    inputs: LayoutInput,
+    bfc: &mut BlockFormattingContext,
+) -> LayoutOutput {
     let LayoutInput {
         known_dimensions, parent_size, available_space, run_mode, vertical_margins_are_collapsible, ..
     } = inputs;
diff --git a/src/compute/float.rs b/src/compute/float.rs
index 9fac7d16c..d53455696 100644
--- a/src/compute/float.rs
+++ b/src/compute/float.rs
@@ -26,11 +26,12 @@
 //!
 //! <https://www.w3.org/TR/CSS22/visuren.html#floats>
 
-use crate::AvailableSpace;
+use crate::{AvailableSpace, Point};
 
 /// A context for placing floated boxes
+#[derive(Debug, Clone)]
 pub struct FloatContext {
-    available_width: f32,
+    available_space: AvailableSpace,
     /// A list of left-floated boxes within the context
     left_floats: Vec<PlacedFloatedBox>,
     /// A list of right-floated boxes within the context
@@ -40,13 +41,14 @@ pub struct FloatContext {
     segments: Vec<Segment>,
 }
 
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
 pub enum FloatDirection {
     Left,
     Right,
 }
 
 /// A floated box to place within the context
-#[derive(Default)]
+#[derive(Debug, Clone, Default)]
 pub struct FloatedBox {
     /// A user defined ID for the box
     id: u64,
@@ -55,19 +57,23 @@ pub struct FloatedBox {
 }
 
 /// A floated box
-#[derive(Default)]
+#[derive(Debug, Clone, Default)]
 struct PlacedFloatedBox {
+    /// A user defined ID for the box
+    id: u64,
+    width: f32,
+    height: f32,
     /// Distance from the edge of the container that the box is floated towards
     /// (distance from the left for left floats, from the right for right floats)
     x_inset: f32,
     y: f32,
-    inner: FloatedBox,
 }
 
 impl PlacedFloatedBox {
-    const DEFAULT: Self = Self { x_inset: 0.0, y: 0.0, inner: FloatedBox { id: 0, width: 0.0, height: 0.0 } };
+    const DEFAULT: Self = Self { id: 0, width: 0.0, height: 0.0, x_inset: 0.0, y: 0.0 };
 }
 
+#[derive(Debug, Clone)]
 struct Segment {
     y_start: f32,
     y_end: f32,
@@ -77,18 +83,57 @@ struct Segment {
 
 impl FloatContext {
     /// Create a new empty `FloatContext`
-    pub fn new(available_width: f32) -> Self {
-        Self { available_width, left_floats: Vec::new(), right_floats: Vec::new(), segments: Vec::new() }
+    pub fn new(available_space: AvailableSpace) -> Self {
+        Self { available_space, left_floats: Vec::new(), right_floats: Vec::new(), segments: Vec::new() }
     }
 
     /// Position a floated box with the context
-    pub fn place_box(&mut self, floated_box: FloatedBox, float_direction: FloatDirection) {
+    pub fn place_floated_box(&mut self, floated_box: FloatedBox, float_direction: FloatDirection) -> Point<f32> {
         let last_float = self.get_float_list(float_direction).last().unwrap_or(&PlacedFloatedBox::DEFAULT);
 
-        let y_threshold = if self.available_width - last_float.inner.width < 0.0 {
-            last_float.y + last_float.inner.height
-        } else {
-            last_float.y
+        let (x_inset, y): (f32, f32) = match self.available_space {
+            AvailableSpace::MinContent => {
+                let x_inset = 0.0;
+                let y = last_float.y + last_float.height;
+                (x_inset, y)
+            }
+            AvailableSpace::MaxContent => {
+                let x_inset = last_float.x_inset + last_float.width;
+                let y = last_float.y;
+                (x_inset, y)
+            }
+            AvailableSpace::Definite(available_width) => {
+                let line_available_width = available_width - last_float.x_inset - last_float.width;
+
+                if line_available_width < floated_box.width {
+                    let x_inset = 0.0;
+                    let y = last_float.y + last_float.height;
+                    (x_inset, y)
+                } else {
+                    let x_inset = last_float.x_inset + last_float.width;
+                    let y = last_float.y;
+                    (x_inset, y)
+                }
+            }
+        };
+
+        let placed_floated_box =
+            PlacedFloatedBox { id: floated_box.id, width: floated_box.width, height: floated_box.height, x_inset, y };
+
+        match float_direction {
+            FloatDirection::Left => self.left_floats.push(placed_floated_box),
+            FloatDirection::Right => self.right_floats.push(placed_floated_box),
+        }
+
+        // Return the (x, y) coordinates of the positioned box
+        return match self.available_space {
+            // Position won't actually be used if we're layouting under a min-content
+            // or max-content constraint, so just return (0, 0)
+            AvailableSpace::MinContent | AvailableSpace::MaxContent => Point::ZERO,
+            AvailableSpace::Definite(width) => match float_direction {
+                FloatDirection::Left => Point { x: x_inset, y },
+                FloatDirection::Right => Point { x: width - x_inset, y },
+            },
         };
     }
 
diff --git a/src/compute/mod.rs b/src/compute/mod.rs
index 45ea25698..fbe9d491e 100644
--- a/src/compute/mod.rs
+++ b/src/compute/mod.rs
@@ -39,7 +39,7 @@ pub(crate) mod grid;
 pub use leaf::compute_leaf_layout;
 
 #[cfg(feature = "block_layout")]
-pub use self::block::compute_block_layout;
+pub use self::block::{compute_block_layout, BlockFormattingContext};
 
 #[cfg(feature = "flexbox")]
 pub use self::flexbox::compute_flexbox_layout;
@@ -47,6 +47,8 @@ pub use self::flexbox::compute_flexbox_layout;
 #[cfg(feature = "grid")]
 pub use self::grid::compute_grid_layout;
 
+#[cfg(feature = "float_layout")]
+use self::float::FloatDirection;
 #[cfg(feature = "float_layout")]
 pub use self::float::{FloatContext, FloatedBox};
 
@@ -168,10 +170,10 @@ pub fn compute_cached_layout<Tree: CacheTree + ?Sized, ComputeFunction>(
     tree: &mut Tree,
     node: NodeId,
     inputs: LayoutInput,
-    mut compute_uncached: ComputeFunction,
+    compute_uncached: ComputeFunction,
 ) -> LayoutOutput
 where
-    ComputeFunction: FnMut(&mut Tree, NodeId, LayoutInput) -> LayoutOutput,
+    ComputeFunction: FnOnce(&mut Tree, NodeId, LayoutInput) -> LayoutOutput,
 {
     debug_push_node!(node);
     let LayoutInput { known_dimensions, available_space, run_mode, .. } = inputs;
diff --git a/src/lib.rs b/src/lib.rs
index 7cd927918..da21ed14d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -123,6 +123,7 @@ pub use crate::tree::TaffyTree;
 #[doc(inline)]
 pub use crate::util::print_tree;
 
+pub use crate::compute::*;
 pub use crate::geometry::*;
 pub use crate::style::*;
 pub use crate::tree::*;
diff --git a/src/tree/taffy_tree.rs b/src/tree/taffy_tree.rs
index af343aebf..e7c8e0097 100644
--- a/src/tree/taffy_tree.rs
+++ b/src/tree/taffy_tree.rs
@@ -18,7 +18,9 @@ use crate::util::sys::{new_vec_with_capacity, ChildrenVec, Vec};
 use crate::compute::{
     compute_cached_layout, compute_hidden_layout, compute_leaf_layout, compute_root_layout, round_layout,
 };
+use crate::BlockFormattingContext;
 use crate::CacheTree;
+
 #[cfg(feature = "block_layout")]
 use crate::{compute::compute_block_layout, LayoutBlockContainer};
 #[cfg(feature = "flexbox")]
@@ -283,6 +285,67 @@ where
     pub(crate) measure_function: MeasureFunction,
 }
 
+impl<NodeContext, MeasureFunction> TaffyView<'_, NodeContext, MeasureFunction>
+where
+    MeasureFunction:
+        FnMut(Size<Option<f32>>, Size<AvailableSpace>, NodeId, Option<&mut NodeContext>, &Style) -> Size<f32>,
+{
+    #[inline(always)]
+    fn compute_child_layout(
+        &mut self,
+        node_id: NodeId,
+        inputs: LayoutInput,
+        #[cfg(feature = "block_layout")] bfc: Option<&mut BlockFormattingContext>,
+    ) -> LayoutOutput {
+        // If RunMode is PerformHiddenLayout then this indicates that an ancestor node is `Display::None`
+        // and thus that we should lay out this node using hidden layout regardless of it's own display style.
+        if inputs.run_mode == RunMode::PerformHiddenLayout {
+            debug_log!("HIDDEN");
+            return compute_hidden_layout(self, node_id);
+        }
+
+        // We run the following wrapped in "compute_cached_layout", which will check the cache for an entry matching the node and inputs and:
+        //   - Return that entry if exists
+        //   - Else call the passed closure (below) to compute the result
+        //
+        // If there was no cache match and a new result needs to be computed then that result will be added to the cache
+        compute_cached_layout(self, node_id, inputs, |tree, node_id, inputs| {
+            let display_mode = tree.taffy.nodes[node_id.into()].style.display;
+            let has_children = tree.child_count(node_id) > 0;
+
+            debug_log!(display_mode);
+            debug_log_node!(
+                inputs.known_dimensions,
+                inputs.parent_size,
+                inputs.available_space,
+                inputs.run_mode,
+                inputs.sizing_mode
+            );
+
+            // Dispatch to a layout algorithm based on the node's display style and whether the node has children or not.
+            match (display_mode, has_children) {
+                (Display::None, _) => compute_hidden_layout(tree, node_id),
+                #[cfg(feature = "block_layout")]
+                (Display::Block, true) => compute_block_layout(tree, node_id, inputs, bfc),
+                #[cfg(feature = "flexbox")]
+                (Display::Flex, true) => compute_flexbox_layout(tree, node_id, inputs),
+                #[cfg(feature = "grid")]
+                (Display::Grid, true) => compute_grid_layout(tree, node_id, inputs),
+                (_, false) => {
+                    let node_key = node_id.into();
+                    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)
+                }
+            }
+        })
+    }
+}
+
 // TraversePartialTree impl for TaffyView
 impl<NodeContext, MeasureFunction> TraversePartialTree for TaffyView<'_, NodeContext, MeasureFunction>
 where
@@ -346,54 +409,8 @@ where
     }
 
     #[inline(always)]
-    fn compute_child_layout(&mut self, node: NodeId, inputs: LayoutInput) -> LayoutOutput {
-        // If RunMode is PerformHiddenLayout then this indicates that an ancestor node is `Display::None`
-        // and thus that we should lay out this node using hidden layout regardless of it's own display style.
-        if inputs.run_mode == RunMode::PerformHiddenLayout {
-            debug_log!("HIDDEN");
-            return compute_hidden_layout(self, node);
-        }
-
-        // We run the following wrapped in "compute_cached_layout", which will check the cache for an entry matching the node and inputs and:
-        //   - Return that entry if exists
-        //   - Else call the passed closure (below) to compute the result
-        //
-        // If there was no cache match and a new result needs to be computed then that result will be added to the cache
-        compute_cached_layout(self, node, inputs, |tree, node, inputs| {
-            let display_mode = tree.taffy.nodes[node.into()].style.display;
-            let has_children = tree.child_count(node) > 0;
-
-            debug_log!(display_mode);
-            debug_log_node!(
-                inputs.known_dimensions,
-                inputs.parent_size,
-                inputs.available_space,
-                inputs.run_mode,
-                inputs.sizing_mode
-            );
-
-            // Dispatch to a layout algorithm based on the node's display style and whether the node has children or not.
-            match (display_mode, has_children) {
-                (Display::None, _) => compute_hidden_layout(tree, node),
-                #[cfg(feature = "block_layout")]
-                (Display::Block, true) => compute_block_layout(tree, node, inputs),
-                #[cfg(feature = "flexbox")]
-                (Display::Flex, true) => compute_flexbox_layout(tree, node, inputs),
-                #[cfg(feature = "grid")]
-                (Display::Grid, true) => compute_grid_layout(tree, node, inputs),
-                (_, false) => {
-                    let node_key = node.into();
-                    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, node_context, style)
-                    };
-                    // TODO: implement calc() in high-level API
-                    compute_leaf_layout(inputs, style, |_, _| 0.0, measure_function)
-                }
-            }
-        })
+    fn compute_child_layout(&mut self, node_id: NodeId, inputs: LayoutInput) -> LayoutOutput {
+        self.compute_child_layout(node_id, inputs, None)
     }
 }
 
@@ -452,6 +469,16 @@ where
     fn get_block_child_style(&self, child_node_id: NodeId) -> Self::BlockItemStyle<'_> {
         self.get_core_container_style(child_node_id)
     }
+
+    #[inline(always)]
+    fn compute_block_child_layout(
+        &mut self,
+        node_id: NodeId,
+        inputs: LayoutInput,
+        bfc: Option<&mut BlockFormattingContext>,
+    ) -> LayoutOutput {
+        self.compute_child_layout(node_id, inputs, bfc)
+    }
 }
 
 #[cfg(feature = "flexbox")]
diff --git a/src/tree/traits.rs b/src/tree/traits.rs
index d90d1e9e0..52c1a2a0f 100644
--- a/src/tree/traits.rs
+++ b/src/tree/traits.rs
@@ -137,7 +137,7 @@ use crate::style::{FlexboxContainerStyle, FlexboxItemStyle};
 use crate::style::{GridContainerStyle, GridItemStyle};
 use crate::CheapCloneStr;
 #[cfg(feature = "block_layout")]
-use crate::{BlockContainerStyle, BlockItemStyle};
+use crate::{BlockContainerStyle, BlockFormattingContext, BlockItemStyle};
 
 #[cfg(all(feature = "grid", feature = "detailed_layout_info"))]
 use crate::compute::grid::DetailedGridInfo;
@@ -313,6 +313,18 @@ pub trait LayoutBlockContainer: LayoutPartialTree {
 
     /// Get the child's styles
     fn get_block_child_style(&self, child_node_id: NodeId) -> Self::BlockItemStyle<'_>;
+
+    /// Compute the specified node's size or full layout given the specified constraints
+    #[cfg(feature = "block_layout")]
+    fn compute_block_child_layout(
+        &mut self,
+        node_id: NodeId,
+        inputs: LayoutInput,
+        bfc: Option<&mut BlockFormattingContext>,
+    ) -> LayoutOutput {
+        let _ = bfc;
+        self.compute_child_layout(node_id, inputs)
+    }
 }
 
 // --- PRIVATE TRAITS