Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Display::Contents (tree-side implementation) #534

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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();
nicoburns marked this conversation as resolved.
Show resolved Hide resolved
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