Skip to content

Commit

Permalink
Implement Display::Contents
Browse files Browse the repository at this point in the history
Add Display::Contents variant to Display enum

Modify Taffy children iterator to recurse into Display::Contents nodes

Add `Display::Contents` support to the gentest script

Add basic tests for `Display::Contents`

Fix clippy lints and document TaffyChildIter next method

Fix Yoga benchmark helpers

smallvec WIP

Precompute display:contents vec

Remove smallvec + lazy display:contents iter implementation

Remove TaffySliceIter

Fix clippy lints

Use RefMut::map to simplify iterator

Reuse Vec in children iterator cache

Use a struct for the children cache

Change order of find_child_recursive parameters

Add Display::Contents to the release notes

Fix child and child_count methods to use resolved children
  • Loading branch information
nicoburns committed Oct 29, 2023
1 parent c16e2b6 commit e396799
Show file tree
Hide file tree
Showing 15 changed files with 515 additions and 12 deletions.
1 change: 1 addition & 0 deletions RELEASES.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ Example usage change:
### Added

- Support for [CSS Block layout](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flow_Layout/Block_and_Inline_Layout_in_Normal_Flow#elements_participating_in_a_block_formatting_context) has been added. This can be used via the new `Display::Block` variant of the `Display` enum. Note that inline, inline-block and float have *not* been implemented. The use case supported is block container nodes which contain block-level children.
- Support for [`Display::Contents`](https://css-tricks.com/get-ready-for-display-contents/)
- Support for running each layout algorithm individually on a single node via the following top-level functions:
- `compute_flexbox_layout`
- `compute_grid_layout`
Expand Down
1 change: 1 addition & 0 deletions benches/src/taffy_03_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ fn convert_display(input: taffy::style::Display) -> taffy_03::style::Display {
taffy::style::Display::None => taffy_03::style::Display::None,
taffy::style::Display::Flex => taffy_03::style::Display::Flex,
taffy::style::Display::Grid => taffy_03::style::Display::Grid,
taffy::style::Display::Contents => panic!("Contents layout not implemented in taffy 0.3"),
taffy::style::Display::Block => panic!("Block layout not implemented in taffy 0.3"),
}
}
Expand Down
1 change: 1 addition & 0 deletions benches/src/yoga_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ fn apply_taffy_style(node: &mut yg::Node, style: &tf::Style) {
node.set_display(match style.display {
tf::Display::None => yg::Display::None,
tf::Display::Flex => yg::Display::Flex,
tf::Display::Contents => panic!("Yoga does not support display: contents"),
tf::Display::Grid => panic!("Yoga does not support CSS Grid layout"),
tf::Display::Block => panic!("Yoga does not support CSS Block layout"),
});
Expand Down
1 change: 1 addition & 0 deletions scripts/gentest/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,7 @@ fn generate_node(ident: &str, node: &Value) -> TokenStream {
let display = match style["display"] {
Value::String(ref value) => match value.as_ref() {
"none" => quote!(display: taffy::style::Display::None,),
"contents" => quote!(display: taffy::style::Display::Contents,),
"block" => quote!(display: taffy::style::Display::Block,),
"grid" => quote!(display: taffy::style::Display::Grid,),
_ => quote!(display: taffy::style::Display::Flex,),
Expand Down
4 changes: 4 additions & 0 deletions src/style/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ pub enum Display {
/// The children will follow the CSS Grid layout algorithm
#[cfg(feature = "grid")]
Grid,
/// The node will behave as if it does not exist, and it's children will be laid
/// out as if they were direct children of the node's parent.
Contents,
/// The children will not be laid out, and will follow absolute positioning
None,
}
Expand All @@ -70,6 +73,7 @@ impl core::fmt::Display for Display {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Display::None => write!(f, "NONE"),
Display::Contents => write!(f, "CONTENTS"),
#[cfg(feature = "block_layout")]
Display::Block => write!(f, "BLOCK"),
#[cfg(feature = "flexbox")]
Expand Down
94 changes: 83 additions & 11 deletions src/tree/taffy_tree/tree.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
//! UI node types and related data structures.
//!
//! Layouts are composed of multiple nodes, which live in a tree-like data structure.
use core::cell::{RefCell, RefMut};

use slotmap::{DefaultKey, SlotMap, SparseSecondaryMap};

use crate::geometry::Size;
Expand Down Expand Up @@ -31,6 +33,21 @@ impl Default for TaffyConfig {
}
}

/// Used to cache the resolved children of a node (taking into account `Display::contents`) during layout
/// so that repeated calls to the children method don't need to re-resolve the list.
struct ChildrenCache {
/// The NodeId of the node whose children we are caching (the cache key)
node_id: NodeId,
/// The actual list of child ids
children: Vec<NodeId>,
}
impl ChildrenCache {
/// Create a new empty cache
fn new() -> ChildrenCache {
ChildrenCache { node_id: NodeId::new(0), children: Vec::new() }
}
}

/// A tree of UI nodes suitable for UI layout
pub struct Taffy<NodeContext = ()> {
/// The [`NodeData`] for each node stored in this tree
Expand Down Expand Up @@ -59,13 +76,33 @@ impl Default for Taffy {
}
}

/// Iterator that wraps a slice of nodes, lazily converting them to u64
pub(crate) struct TaffyChildIter<'a>(core::slice::Iter<'a, NodeId>);
impl<'a> Iterator for TaffyChildIter<'a> {
/// Iterator over the Vec in a RefMut<'a, Vec<NodeId>>
pub struct RefCellVecIter<'a> {
/// The items to iterate over
children: RefMut<'a, Vec<NodeId>>,
/// The next index to return
index: usize,
}
impl<'a> Iterator for RefCellVecIter<'a> {
type Item = NodeId;

fn next(&mut self) -> Option<Self::Item> {
self.0.next().copied()
let item = self.children.get(self.index).copied();
self.index += 1;
item
}
}

/// Iterates over children, checking the Display type of the node
/// If the node is `Display::Contents`, then we recurse it's children, else we simply push the `NodeId` into the list
fn find_children_recursive<NodeContext>(tree: &Taffy<NodeContext>, node: NodeId, out: &mut Vec<NodeId>) {
for child_id in tree.children[node.into()].iter() {
let child_key: DefaultKey = (*child_id).into();
let display = tree.nodes[child_key].style.display;
match display {
Display::Contents => find_children_recursive(tree, *child_id, out),
_ => out.push(*child_id),
}
}
}

Expand All @@ -80,27 +117,57 @@ where
pub(crate) taffy: &'t mut Taffy<NodeContext>,
/// The context provided for passing to measure functions if layout is run over this struct
pub(crate) measure_function: MeasureFunction,
/// Used to cache the resolved children of a node (taking into account `Display::contents`) during layout
/// so that repeated calls to the children method don't need to re-resolve the list.
node_children_cache: RefCell<ChildrenCache>,
}

impl<'t, NodeContext, MeasureFunction> TaffyView<'t, NodeContext, MeasureFunction>
where
MeasureFunction: FnMut(Size<Option<f32>>, Size<AvailableSpace>, NodeId, Option<&mut NodeContext>) -> Size<f32>,
{
/// Create a new TaffyView from a Taffy and a measure function
pub(crate) fn new(taffy: &'t mut Taffy<NodeContext>, measure_function: MeasureFunction) -> Self {
TaffyView { taffy, measure_function, node_children_cache: RefCell::new(ChildrenCache::new()) }
}

/// Returns the resolved children, taking into account `Display::Contents`
/// Will use cached result if available, else compute and cache.
fn resolve_children(&self, node: NodeId) -> RefMut<'_, Vec<NodeId>> {
let mut cache = self.node_children_cache.borrow_mut();

// If the cache key does not match the requested node_id, then recompute the children for
// the requested node and update the cache in-place.
if cache.node_id != node {
cache.node_id = node;
cache.children.clear();
find_children_recursive(self.taffy, node, &mut cache.children);
}

// In all cases, return a reference into the cache
RefMut::map(cache, |c| &mut c.children)
}
}

impl<'t, NodeContext, MeasureFunction> PartialLayoutTree for TaffyView<'t, NodeContext, MeasureFunction>
where
MeasureFunction: FnMut(Size<Option<f32>>, Size<AvailableSpace>, NodeId, Option<&mut NodeContext>) -> Size<f32>,
{
type ChildIter<'a> = TaffyChildIter<'a> where Self: 'a;
type ChildIter<'a> = RefCellVecIter<'a> where Self: 'a;

#[inline(always)]
fn child_ids(&self, node: NodeId) -> Self::ChildIter<'_> {
TaffyChildIter(self.taffy.children[node.into()].iter())
RefCellVecIter { children: self.resolve_children(node), index: 0 }
}

#[inline(always)]
fn child_count(&self, node: NodeId) -> usize {
self.taffy.children[node.into()].len()
self.resolve_children(node).len()
}

#[inline(always)]
fn get_child_id(&self, node: NodeId, id: usize) -> NodeId {
self.taffy.children[node.into()][id]
self.resolve_children(node)[id]
}

#[inline(always)]
Expand Down Expand Up @@ -148,6 +215,11 @@ where
// 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),
(Display::Contents, _) => {
*tree.get_unrounded_layout_mut(node) = Layout::with_order(0);
tree.get_cache_mut(node).clear();
LayoutOutput::HIDDEN
}
#[cfg(feature = "block_layout")]
(Display::Block, true) => compute_block_layout(tree, node, inputs),
#[cfg(feature = "flexbox")]
Expand Down Expand Up @@ -505,7 +577,7 @@ impl<NodeContext> Taffy<NodeContext> {
MeasureFunction: FnMut(Size<Option<f32>>, Size<AvailableSpace>, NodeId, Option<&mut NodeContext>) -> Size<f32>,
{
let use_rounding = self.config.use_rounding;
let mut taffy_view = TaffyView { taffy: self, measure_function };
let mut taffy_view = TaffyView::new(self, measure_function);
compute_layout(&mut taffy_view, node_id, available_space);
if use_rounding {
round_layout(&mut taffy_view, node_id);
Expand All @@ -521,14 +593,14 @@ impl<NodeContext> Taffy<NodeContext> {
/// Prints a debug representation of the tree's layout
#[cfg(feature = "std")]
pub fn print_tree(&mut self, root: NodeId) {
let taffy_view = TaffyView { taffy: self, measure_function: |_, _, _, _| Size::ZERO };
let taffy_view = TaffyView::new(self, |_, _, _, _| Size::ZERO);
crate::util::print_tree(&taffy_view, root)
}

/// Returns an instance of LayoutTree representing the Taffy
#[cfg(test)]
pub(crate) fn as_layout_tree(&mut self) -> impl LayoutTree + '_ {
TaffyView { taffy: self, measure_function: |_, _, _, _| Size::ZERO }
TaffyView::new(self, |_, _, _, _| Size::ZERO)
}
}

Expand Down
4 changes: 3 additions & 1 deletion src/util/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ fn print_node(tree: &impl LayoutTree, node: NodeId, has_sibling: bool, lines_str

let display = match (num_children, style.display) {
(_, style::Display::None) => "NONE",
(_, style::Display::Contents) => "CONTENTS",
(0, _) => "LEAF",
#[cfg(feature = "block_layout")]
(_, style::Display::Block) => "BLOCK",
Expand All @@ -48,7 +49,8 @@ fn print_node(tree: &impl LayoutTree, node: NodeId, has_sibling: bool, lines_str
let new_string = lines_string + bar;

// Recurse into children
for (index, child) in tree.child_ids(node).enumerate() {
let child_ids: Vec<NodeId> = tree.child_ids(node).collect();
for (index, child) in child_ids.into_iter().enumerate() {
let has_sibling = index < num_children - 1;
print_node(tree, child, has_sibling, new_string.clone());
}
Expand Down
23 changes: 23 additions & 0 deletions test_fixtures/contents/contents_flex_basic.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<script src="../../scripts/gentest/test_helper.js"></script>
<link rel="stylesheet" type="text/css" href="../../scripts/gentest/test_base_style.css">
<title>
Test description
</title>
</head>
<body>

<div id="test-root" style="display: flex; width: 400px; height: 300px; justify-content: space-between;">
<div style="width: 30px;"></div>
<div style="width: 30px;"></div>
<div style="width: 30px;"></div>
<div style="display: contents;">
<div style="width: 30px;"></div>
<div style="width: 30px;"></div>
</div>
</div>

</body>
</html>
26 changes: 26 additions & 0 deletions test_fixtures/contents/contents_flex_nested.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<!DOCTYPE html>
<html lang="en">
<head>
<script src="../../scripts/gentest/test_helper.js"></script>
<link rel="stylesheet" type="text/css" href="../../scripts/gentest/test_base_style.css">
<title>
Test description
</title>
</head>
<body>

<div id="test-root" style="display: flex; width: 400px; height: 300px; justify-content: space-between;">
<div style="width: 30px;"></div>
<div style="width: 30px;"></div>
<div style="width: 30px;"></div>
<div style="display: contents;">
<div style="display: contents;">
<div style="width: 30px;"></div>
<div style="width: 30px;"></div>
</div>
<div style="width: 30px;"></div>
</div>
</div>

</body>
</html>
28 changes: 28 additions & 0 deletions test_fixtures/contents/contents_flex_nested2.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<!DOCTYPE html>
<html lang="en">
<head>
<script src="../../scripts/gentest/test_helper.js"></script>
<link rel="stylesheet" type="text/css" href="../../scripts/gentest/test_base_style.css">
<title>
Test description
</title>
</head>
<body>

<div id="test-root" style="display: flex; width: 400px; height: 300px; justify-content: space-between;">
<div style="width: 30px;"></div>
<div style="width: 30px;"></div>
<div style="display: contents;">
<div style="width: 30px;"></div>
<div style="display: contents;">
<div style="display: contents;">
<div style="width: 30px;"></div>
<div style="width: 30px;"></div>
</div>
<div style="width: 30px;"></div>
</div>
</div>
</div>

</body>
</html>
Loading

0 comments on commit e396799

Please sign in to comment.