Skip to content

Commit e396799

Browse files
committed
Implement Display::Contents
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
1 parent c16e2b6 commit e396799

File tree

15 files changed

+515
-12
lines changed

15 files changed

+515
-12
lines changed

RELEASES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ Example usage change:
8787
### Added
8888

8989
- 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.
90+
- Support for [`Display::Contents`](https://css-tricks.com/get-ready-for-display-contents/)
9091
- Support for running each layout algorithm individually on a single node via the following top-level functions:
9192
- `compute_flexbox_layout`
9293
- `compute_grid_layout`

benches/src/taffy_03_helpers.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ fn convert_display(input: taffy::style::Display) -> taffy_03::style::Display {
147147
taffy::style::Display::None => taffy_03::style::Display::None,
148148
taffy::style::Display::Flex => taffy_03::style::Display::Flex,
149149
taffy::style::Display::Grid => taffy_03::style::Display::Grid,
150+
taffy::style::Display::Contents => panic!("Contents layout not implemented in taffy 0.3"),
150151
taffy::style::Display::Block => panic!("Block layout not implemented in taffy 0.3"),
151152
}
152153
}

benches/src/yoga_helpers.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ fn apply_taffy_style(node: &mut yg::Node, style: &tf::Style) {
187187
node.set_display(match style.display {
188188
tf::Display::None => yg::Display::None,
189189
tf::Display::Flex => yg::Display::Flex,
190+
tf::Display::Contents => panic!("Yoga does not support display: contents"),
190191
tf::Display::Grid => panic!("Yoga does not support CSS Grid layout"),
191192
tf::Display::Block => panic!("Yoga does not support CSS Block layout"),
192193
});

scripts/gentest/src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,7 @@ fn generate_node(ident: &str, node: &Value) -> TokenStream {
352352
let display = match style["display"] {
353353
Value::String(ref value) => match value.as_ref() {
354354
"none" => quote!(display: taffy::style::Display::None,),
355+
"contents" => quote!(display: taffy::style::Display::Contents,),
355356
"block" => quote!(display: taffy::style::Display::Block,),
356357
"grid" => quote!(display: taffy::style::Display::Grid,),
357358
_ => quote!(display: taffy::style::Display::Flex,),

src/style/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ pub enum Display {
4444
/// The children will follow the CSS Grid layout algorithm
4545
#[cfg(feature = "grid")]
4646
Grid,
47+
/// The node will behave as if it does not exist, and it's children will be laid
48+
/// out as if they were direct children of the node's parent.
49+
Contents,
4750
/// The children will not be laid out, and will follow absolute positioning
4851
None,
4952
}
@@ -70,6 +73,7 @@ impl core::fmt::Display for Display {
7073
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
7174
match self {
7275
Display::None => write!(f, "NONE"),
76+
Display::Contents => write!(f, "CONTENTS"),
7377
#[cfg(feature = "block_layout")]
7478
Display::Block => write!(f, "BLOCK"),
7579
#[cfg(feature = "flexbox")]

src/tree/taffy_tree/tree.rs

Lines changed: 83 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
//! UI node types and related data structures.
22
//!
33
//! Layouts are composed of multiple nodes, which live in a tree-like data structure.
4+
use core::cell::{RefCell, RefMut};
5+
46
use slotmap::{DefaultKey, SlotMap, SparseSecondaryMap};
57

68
use crate::geometry::Size;
@@ -31,6 +33,21 @@ impl Default for TaffyConfig {
3133
}
3234
}
3335

36+
/// Used to cache the resolved children of a node (taking into account `Display::contents`) during layout
37+
/// so that repeated calls to the children method don't need to re-resolve the list.
38+
struct ChildrenCache {
39+
/// The NodeId of the node whose children we are caching (the cache key)
40+
node_id: NodeId,
41+
/// The actual list of child ids
42+
children: Vec<NodeId>,
43+
}
44+
impl ChildrenCache {
45+
/// Create a new empty cache
46+
fn new() -> ChildrenCache {
47+
ChildrenCache { node_id: NodeId::new(0), children: Vec::new() }
48+
}
49+
}
50+
3451
/// A tree of UI nodes suitable for UI layout
3552
pub struct Taffy<NodeContext = ()> {
3653
/// The [`NodeData`] for each node stored in this tree
@@ -59,13 +76,33 @@ impl Default for Taffy {
5976
}
6077
}
6178

62-
/// Iterator that wraps a slice of nodes, lazily converting them to u64
63-
pub(crate) struct TaffyChildIter<'a>(core::slice::Iter<'a, NodeId>);
64-
impl<'a> Iterator for TaffyChildIter<'a> {
79+
/// Iterator over the Vec in a RefMut<'a, Vec<NodeId>>
80+
pub struct RefCellVecIter<'a> {
81+
/// The items to iterate over
82+
children: RefMut<'a, Vec<NodeId>>,
83+
/// The next index to return
84+
index: usize,
85+
}
86+
impl<'a> Iterator for RefCellVecIter<'a> {
6587
type Item = NodeId;
6688

6789
fn next(&mut self) -> Option<Self::Item> {
68-
self.0.next().copied()
90+
let item = self.children.get(self.index).copied();
91+
self.index += 1;
92+
item
93+
}
94+
}
95+
96+
/// Iterates over children, checking the Display type of the node
97+
/// If the node is `Display::Contents`, then we recurse it's children, else we simply push the `NodeId` into the list
98+
fn find_children_recursive<NodeContext>(tree: &Taffy<NodeContext>, node: NodeId, out: &mut Vec<NodeId>) {
99+
for child_id in tree.children[node.into()].iter() {
100+
let child_key: DefaultKey = (*child_id).into();
101+
let display = tree.nodes[child_key].style.display;
102+
match display {
103+
Display::Contents => find_children_recursive(tree, *child_id, out),
104+
_ => out.push(*child_id),
105+
}
69106
}
70107
}
71108

@@ -80,27 +117,57 @@ where
80117
pub(crate) taffy: &'t mut Taffy<NodeContext>,
81118
/// The context provided for passing to measure functions if layout is run over this struct
82119
pub(crate) measure_function: MeasureFunction,
120+
/// Used to cache the resolved children of a node (taking into account `Display::contents`) during layout
121+
/// so that repeated calls to the children method don't need to re-resolve the list.
122+
node_children_cache: RefCell<ChildrenCache>,
123+
}
124+
125+
impl<'t, NodeContext, MeasureFunction> TaffyView<'t, NodeContext, MeasureFunction>
126+
where
127+
MeasureFunction: FnMut(Size<Option<f32>>, Size<AvailableSpace>, NodeId, Option<&mut NodeContext>) -> Size<f32>,
128+
{
129+
/// Create a new TaffyView from a Taffy and a measure function
130+
pub(crate) fn new(taffy: &'t mut Taffy<NodeContext>, measure_function: MeasureFunction) -> Self {
131+
TaffyView { taffy, measure_function, node_children_cache: RefCell::new(ChildrenCache::new()) }
132+
}
133+
134+
/// Returns the resolved children, taking into account `Display::Contents`
135+
/// Will use cached result if available, else compute and cache.
136+
fn resolve_children(&self, node: NodeId) -> RefMut<'_, Vec<NodeId>> {
137+
let mut cache = self.node_children_cache.borrow_mut();
138+
139+
// If the cache key does not match the requested node_id, then recompute the children for
140+
// the requested node and update the cache in-place.
141+
if cache.node_id != node {
142+
cache.node_id = node;
143+
cache.children.clear();
144+
find_children_recursive(self.taffy, node, &mut cache.children);
145+
}
146+
147+
// In all cases, return a reference into the cache
148+
RefMut::map(cache, |c| &mut c.children)
149+
}
83150
}
84151

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

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

96163
#[inline(always)]
97164
fn child_count(&self, node: NodeId) -> usize {
98-
self.taffy.children[node.into()].len()
165+
self.resolve_children(node).len()
99166
}
100167

101168
#[inline(always)]
102169
fn get_child_id(&self, node: NodeId, id: usize) -> NodeId {
103-
self.taffy.children[node.into()][id]
170+
self.resolve_children(node)[id]
104171
}
105172

106173
#[inline(always)]
@@ -148,6 +215,11 @@ where
148215
// Dispatch to a layout algorithm based on the node's display style and whether the node has children or not.
149216
match (display_mode, has_children) {
150217
(Display::None, _) => compute_hidden_layout(tree, node),
218+
(Display::Contents, _) => {
219+
*tree.get_unrounded_layout_mut(node) = Layout::with_order(0);
220+
tree.get_cache_mut(node).clear();
221+
LayoutOutput::HIDDEN
222+
}
151223
#[cfg(feature = "block_layout")]
152224
(Display::Block, true) => compute_block_layout(tree, node, inputs),
153225
#[cfg(feature = "flexbox")]
@@ -505,7 +577,7 @@ impl<NodeContext> Taffy<NodeContext> {
505577
MeasureFunction: FnMut(Size<Option<f32>>, Size<AvailableSpace>, NodeId, Option<&mut NodeContext>) -> Size<f32>,
506578
{
507579
let use_rounding = self.config.use_rounding;
508-
let mut taffy_view = TaffyView { taffy: self, measure_function };
580+
let mut taffy_view = TaffyView::new(self, measure_function);
509581
compute_layout(&mut taffy_view, node_id, available_space);
510582
if use_rounding {
511583
round_layout(&mut taffy_view, node_id);
@@ -521,14 +593,14 @@ impl<NodeContext> Taffy<NodeContext> {
521593
/// Prints a debug representation of the tree's layout
522594
#[cfg(feature = "std")]
523595
pub fn print_tree(&mut self, root: NodeId) {
524-
let taffy_view = TaffyView { taffy: self, measure_function: |_, _, _, _| Size::ZERO };
596+
let taffy_view = TaffyView::new(self, |_, _, _, _| Size::ZERO);
525597
crate::util::print_tree(&taffy_view, root)
526598
}
527599

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

src/util/debug.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ fn print_node(tree: &impl LayoutTree, node: NodeId, has_sibling: bool, lines_str
2323

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

5051
// Recurse into children
51-
for (index, child) in tree.child_ids(node).enumerate() {
52+
let child_ids: Vec<NodeId> = tree.child_ids(node).collect();
53+
for (index, child) in child_ids.into_iter().enumerate() {
5254
let has_sibling = index < num_children - 1;
5355
print_node(tree, child, has_sibling, new_string.clone());
5456
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<script src="../../scripts/gentest/test_helper.js"></script>
5+
<link rel="stylesheet" type="text/css" href="../../scripts/gentest/test_base_style.css">
6+
<title>
7+
Test description
8+
</title>
9+
</head>
10+
<body>
11+
12+
<div id="test-root" style="display: flex; width: 400px; height: 300px; justify-content: space-between;">
13+
<div style="width: 30px;"></div>
14+
<div style="width: 30px;"></div>
15+
<div style="width: 30px;"></div>
16+
<div style="display: contents;">
17+
<div style="width: 30px;"></div>
18+
<div style="width: 30px;"></div>
19+
</div>
20+
</div>
21+
22+
</body>
23+
</html>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<script src="../../scripts/gentest/test_helper.js"></script>
5+
<link rel="stylesheet" type="text/css" href="../../scripts/gentest/test_base_style.css">
6+
<title>
7+
Test description
8+
</title>
9+
</head>
10+
<body>
11+
12+
<div id="test-root" style="display: flex; width: 400px; height: 300px; justify-content: space-between;">
13+
<div style="width: 30px;"></div>
14+
<div style="width: 30px;"></div>
15+
<div style="width: 30px;"></div>
16+
<div style="display: contents;">
17+
<div style="display: contents;">
18+
<div style="width: 30px;"></div>
19+
<div style="width: 30px;"></div>
20+
</div>
21+
<div style="width: 30px;"></div>
22+
</div>
23+
</div>
24+
25+
</body>
26+
</html>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<script src="../../scripts/gentest/test_helper.js"></script>
5+
<link rel="stylesheet" type="text/css" href="../../scripts/gentest/test_base_style.css">
6+
<title>
7+
Test description
8+
</title>
9+
</head>
10+
<body>
11+
12+
<div id="test-root" style="display: flex; width: 400px; height: 300px; justify-content: space-between;">
13+
<div style="width: 30px;"></div>
14+
<div style="width: 30px;"></div>
15+
<div style="display: contents;">
16+
<div style="width: 30px;"></div>
17+
<div style="display: contents;">
18+
<div style="display: contents;">
19+
<div style="width: 30px;"></div>
20+
<div style="width: 30px;"></div>
21+
</div>
22+
<div style="width: 30px;"></div>
23+
</div>
24+
</div>
25+
</div>
26+
27+
</body>
28+
</html>

0 commit comments

Comments
 (0)