Vendor dependencies for 0.3.0 release

This commit is contained in:
2025-09-27 10:29:08 -05:00
parent 0c8d39d483
commit 82ab7f317b
26803 changed files with 16134934 additions and 0 deletions

1
vendor/taffy/.cargo-checksum.json vendored Normal file

File diff suppressed because one or more lines are too long

142
vendor/taffy/Cargo.lock generated vendored Normal file
View File

@@ -0,0 +1,142 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "arrayvec"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
[[package]]
name = "document-features"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb6969eaabd2421f8a2775cfd2471a2b634372b4a25d41e3bd647b79912850a0"
dependencies = [
"litrs",
]
[[package]]
name = "grid"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36119f3a540b086b4e436bb2b588cf98a68863470e0e880f4d0842f112a3183a"
[[package]]
name = "itoa"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
[[package]]
name = "litrs"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "proc-macro2"
version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
dependencies = [
"proc-macro2",
]
[[package]]
name = "ryu"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]]
name = "serde"
version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.135"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b0d7ba2887406110130a978386c4e1befb98c674b4fba677954e4db976630d9"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
]
[[package]]
name = "slotmap"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a"
dependencies = [
"version_check",
]
[[package]]
name = "syn"
version = "2.0.96"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "taffy"
version = "0.7.7"
dependencies = [
"arrayvec",
"document-features",
"grid",
"serde",
"serde_json",
"slotmap",
]
[[package]]
name = "unicode-ident"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"

153
vendor/taffy/Cargo.toml vendored Normal file
View File

@@ -0,0 +1,153 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
rust-version = "1.65"
name = "taffy"
version = "0.7.7"
authors = [
"Alice Cecile <alice.i.cecile@gmail.com>",
"Johnathan Kelley <jkelleyrtp@gmail.com>",
"Nico Burns <nico@nicoburns.com>",
]
build = false
include = [
"src/**/*",
"examples/**/*",
"Cargo.toml",
"README.md",
]
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "A flexible UI layout library "
readme = "README.md"
keywords = [
"cross-platform",
"layout",
"flexbox",
"css-grid",
"grid",
]
categories = ["gui"]
license = "MIT"
repository = "https://github.com/DioxusLabs/taffy"
[package.metadata.docs.rs]
all-features = true
cargo-args = [
"-Zunstable-options",
"-Zrustdoc-scrape-examples",
]
rustdoc-args = [
"--cfg",
"docsrs",
]
[features]
alloc = ["serde?/alloc"]
block_layout = []
content_size = []
debug = ["std"]
default = [
"std",
"taffy_tree",
"flexbox",
"grid",
"block_layout",
"content_size",
"detailed_layout_info",
]
detailed_layout_info = []
flexbox = []
grid = [
"alloc",
"dep:grid",
]
profile = ["std"]
serde = ["dep:serde"]
std = [
"grid?/std",
"serde?/std",
"slotmap?/std",
]
taffy_tree = ["dep:slotmap"]
[lib]
name = "taffy"
path = "src/lib.rs"
[[example]]
name = "basic"
path = "examples/basic.rs"
doc-scrape-examples = true
[[example]]
name = "custom_tree_owned_partial"
path = "examples/custom_tree_owned_partial.rs"
[[example]]
name = "custom_tree_owned_unsafe"
path = "examples/custom_tree_owned_unsafe.rs"
[[example]]
name = "custom_tree_vec"
path = "examples/custom_tree_vec.rs"
[[example]]
name = "flexbox_gap"
path = "examples/flexbox_gap.rs"
[[example]]
name = "grid_holy_grail"
path = "examples/grid_holy_grail.rs"
[[example]]
name = "measure"
path = "examples/measure.rs"
[[example]]
name = "nested"
path = "examples/nested.rs"
[dependencies.arrayvec]
version = "0.7"
default-features = false
[dependencies.document-features]
version = "0.2.7"
optional = true
[dependencies.grid]
version = "0.15.0"
optional = true
default-features = false
[dependencies.serde]
version = "1.0"
features = ["serde_derive"]
optional = true
default-features = false
[dependencies.slotmap]
version = "1.0.6"
optional = true
default-features = false
[dev-dependencies.serde_json]
version = "1.0.93"
[profile.release]
lto = true
panic = "abort"

123
vendor/taffy/README.md vendored Normal file
View File

@@ -0,0 +1,123 @@
# Taffy
[![GitHub CI](https://github.com/DioxusLabs/taffy/actions/workflows/ci.yml/badge.svg)](https://github.com/DioxusLabs/taffy/actions/workflows/ci.yml)
[![crates.io](https://img.shields.io/crates/v/taffy.svg)](https://crates.io/crates/taffy)
[![docs.rs](https://img.shields.io/docsrs/taffy)](https://docs.rs/taffy)
Taffy is a flexible, high-performance, cross-platform UI layout library written in [Rust](https://www.rust-lang.org).
It currently implements the CSS **Block**, **Flexbox** and **CSS Grid** layout algorithms. Support for other paradigms is planned. For more information on this and other future development plans see the [roadmap issue](https://github.com/DioxusLabs/taffy/issues/345).
This crate is a collaborative, cross-team project, and is designed to be used as a dependency for other UI and GUI libraries.
Right now, it powers:
- [Servo](https://github.com/servo/servo): an alternative web browser
- [Blitz](https://github.com/DioxusLabs/blitz): a radically modular web engine
- [Bevy](https://bevyengine.org/): an ergonomic, ECS-first Rust game engine
- The [Lapce](https://lapce.dev/) text editor via the [Floem](https://github.com/lapce/floem) UI framework
- The [Zed](https://zed.dev/) text editor via the [GPUI](https://github.com/zed-industries/zed/tree/main/crates/gpui) UI framework
## Usage
```rust
use taffy::prelude::*;
// First create an instance of TaffyTree
let mut tree : TaffyTree<()> = TaffyTree::new();
// Create a tree of nodes using `TaffyTree.new_leaf` and `TaffyTree.new_with_children`.
// These functions both return a node id which can be used to refer to that node
// The Style struct is used to specify styling information
let header_node = tree
.new_leaf(
Style {
size: Size { width: length(800.0), height: length(100.0) },
..Default::default()
},
).unwrap();
let body_node = tree
.new_leaf(
Style {
size: Size { width: length(800.0), height: auto() },
flex_grow: 1.0,
..Default::default()
},
).unwrap();
let root_node = tree
.new_with_children(
Style {
flex_direction: FlexDirection::Column,
size: Size { width: length(800.0), height: length(600.0) },
..Default::default()
},
&[header_node, body_node],
)
.unwrap();
// Call compute_layout on the root of your tree to run the layout algorithm
tree.compute_layout(root_node, Size::MAX_CONTENT).unwrap();
// Inspect the computed layout using `TaffyTree.layout`
assert_eq!(tree.layout(root_node).unwrap().size.width, 800.0);
assert_eq!(tree.layout(root_node).unwrap().size.height, 600.0);
assert_eq!(tree.layout(header_node).unwrap().size.width, 800.0);
assert_eq!(tree.layout(header_node).unwrap().size.height, 100.0);
assert_eq!(tree.layout(body_node).unwrap().size.width, 800.0);
assert_eq!(tree.layout(body_node).unwrap().size.height, 500.0); // This value was not set explicitly, but was computed by Taffy
```
## Bindings to other languages
- Python via [stretchable](https://github.com/mortencombat/stretchable)
- [WIP C bindings](https://github.com/DioxusLabs/taffy/pull/404)
- [WIP WASM bindings](https://github.com/DioxusLabs/taffy/pull/394)
## Learning Resources
Taffy implements the Flexbox and CSS Grid specifications faithfully, so documentation designed for the web should translate cleanly to Taffy's implementation. For reference documentation on individual style properties we recommend the MDN documentation (for example [this page](https://developer.mozilla.org/en-US/docs/Web/CSS/width) on the `width` property). Such pages can usually be found by searching for "MDN property-name" using a search engine.
If you are interested in guide-level documentation on CSS layout, then we recommend the following resources:
### Flexbox
- [Flexbox Froggy](https://flexboxfroggy.com/). This is an interactive tutorial/game that allows you to learn the essential parts of Flexbox in a fun engaging way.
- [A Complete Guide To Flexbox](https://css-tricks.com/snippets/css/a-guide-to-flexbox/) by CSS Tricks. This is detailed guide with illustrations and comprehensive written explanation of the different Flexbox properties and how they work.
### CSS Grid
- [CSS Grid Garden](https://cssgridgarden.com/). This is an interactive tutorial/game that allows you to learn the essential parts of CSS Grid in a fun engaging way.
- [A Complete Guide To CSS Grid](https://css-tricks.com/snippets/css/complete-guide-grid/) by CSS Tricks. This is detailed guide with illustrations and comprehensive written explanation of the different CSS Grid properties and how they work.
## Benchmarks (vs. [Yoga](https://github.com/facebook/yoga))
- Run on a 2021 MacBook Pro with M1 Pro processor using [criterion](https://github.com/bheisler/criterion.rs)
- The benchmarks measure layout computation only. They do not measure tree creation.
- Yoga benchmarks were run via the [yoga](https://github.com/bschwind/yoga-rs) crate (Rust bindings)
- Most popular websites seem to have between 3,000 and 10,000 nodes (although they also require text layout, which neither yoga nor taffy implement).
Note that the table below contains multiple different units (milliseconds vs. microseconds)
| Benchmark | Node Count | Depth | Yoga ([ba27f9d]) | Taffy ([71027a8]) |
| --- | --- | --- | --- | --- |
| yoga 'huge nested' | 1,000 | 3 | 364.60 µs | 329.04 µs |
| yoga 'huge nested' | 10,000 | 4 | 4.1988 ms | 4.3486 ms |
| yoga 'huge nested' | 100,000 | 5 | 45.804 ms | 38.559 ms |
| big trees (wide) | 1,000 | 1 | 737.77 µs | 505.99 µs |
| big trees (wide) | 10,000 | 1 | 7.1007 ms | 8.3395 ms |
| big trees (wide) | 100,000 | 1 | 135.78 ms | 247.42 ms |
| big trees (deep) | 4,000 | 12 | 2.2333 ms | 1.7400 ms |
| big trees (deep) | 10,000 | 14 | 5.9477 ms | 4.4445 ms |
| big trees (deep) | 100,000 | 17 | 76.755 ms | 63.778 ms |
| super deep | 1,000 | 1,000 | 555.32 µs | 472.85 µs |
[ba27f9d]: https://github.com/facebook/yoga/commit/ba27f9d1ecfa7518019845b84b035d3d4a2a6658
[71027a8]: https://github.com/DioxusLabs/taffy/commit/71027a8de03b343e120852b84bb7dca9fb4651c5
## Contributions
[Contributions welcome](https://github.com/DioxusLabs/taffy/blob/main/CONTRIBUTING.md):
if you'd like to use, improve or build `taffy`, feel free to join the conversation, open an [issue](https://github.com/DioxusLabs/taffy/issues) or submit a [PR](https://github.com/DioxusLabs/taffy/pulls).
If you have questions about how to use `taffy`, open a [discussion](https://github.com/DioxusLabs/taffy/discussions) so we can answer your questions in a way that others can find.

34
vendor/taffy/examples/basic.rs vendored Normal file
View File

@@ -0,0 +1,34 @@
use taffy::prelude::*;
fn main() -> Result<(), taffy::TaffyError> {
let mut taffy: TaffyTree<()> = TaffyTree::new();
let child = taffy.new_leaf(Style {
size: Size { width: Dimension::Percent(0.5), height: Dimension::Auto },
..Default::default()
})?;
let node = taffy.new_with_children(
Style {
size: Size { width: Dimension::Length(100.0), height: Dimension::Length(100.0) },
justify_content: Some(JustifyContent::Center),
..Default::default()
},
&[child],
)?;
println!("Compute layout with 100x100 viewport:");
taffy.compute_layout(
node,
Size { height: AvailableSpace::Definite(100.0), width: AvailableSpace::Definite(100.0) },
)?;
println!("node: {:#?}", taffy.layout(node)?);
println!("child: {:#?}", taffy.layout(child)?);
println!("Compute layout with undefined (infinite) viewport:");
taffy.compute_layout(node, Size::MAX_CONTENT)?;
println!("node: {:#?}", taffy.layout(node)?);
println!("child: {:#?}", taffy.layout(child)?);
Ok(())
}

18
vendor/taffy/examples/common/image.rs vendored Normal file
View File

@@ -0,0 +1,18 @@
use taffy::geometry::Size;
pub struct ImageContext {
pub width: f32,
pub height: f32,
}
pub fn image_measure_function(
known_dimensions: taffy::geometry::Size<Option<f32>>,
image_context: &ImageContext,
) -> taffy::geometry::Size<f32> {
match (known_dimensions.width, known_dimensions.height) {
(Some(width), Some(height)) => Size { width, height },
(Some(width), None) => Size { width, height: (width / image_context.width) * image_context.height },
(None, Some(height)) => Size { width: (height / image_context.height) * image_context.width, height },
(None, None) => Size { width: image_context.width, height: image_context.height },
}
}

75
vendor/taffy/examples/common/text.rs vendored Normal file
View File

@@ -0,0 +1,75 @@
pub const LOREM_IPSUM : &str = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
pub struct FontMetrics {
pub char_width: f32,
pub char_height: f32,
}
#[allow(dead_code)]
pub enum WritingMode {
Horizontal,
Vertical,
}
pub struct TextContext {
pub text_content: String,
pub writing_mode: WritingMode,
}
pub fn text_measure_function(
known_dimensions: taffy::geometry::Size<Option<f32>>,
available_space: taffy::geometry::Size<taffy::style::AvailableSpace>,
text_context: &TextContext,
font_metrics: &FontMetrics,
) -> taffy::geometry::Size<f32> {
use taffy::geometry::AbsoluteAxis;
use taffy::prelude::*;
let inline_axis = match text_context.writing_mode {
WritingMode::Horizontal => AbsoluteAxis::Horizontal,
WritingMode::Vertical => AbsoluteAxis::Vertical,
};
let block_axis = inline_axis.other_axis();
let words: Vec<&str> = text_context.text_content.split_whitespace().collect();
if words.is_empty() {
return Size::ZERO;
}
let min_line_length: usize = words.iter().map(|line| line.len()).max().unwrap_or(0);
let max_line_length: usize = words.iter().map(|line| line.len()).sum();
let inline_size =
known_dimensions.get_abs(inline_axis).unwrap_or_else(|| match available_space.get_abs(inline_axis) {
AvailableSpace::MinContent => min_line_length as f32 * font_metrics.char_width,
AvailableSpace::MaxContent => max_line_length as f32 * font_metrics.char_width,
AvailableSpace::Definite(inline_size) => inline_size
.min(max_line_length as f32 * font_metrics.char_width)
.max(min_line_length as f32 * font_metrics.char_width),
});
let block_size = known_dimensions.get_abs(block_axis).unwrap_or_else(|| {
let inline_line_length = (inline_size / font_metrics.char_width).floor() as usize;
let mut line_count = 1;
let mut current_line_length = 0;
for word in &words {
if current_line_length == 0 {
// first word
current_line_length = word.len();
} else if current_line_length + word.len() + 1 > inline_line_length {
// every word past the first needs to check for line length including the space between words
// note: a real implementation of this should handle whitespace characters other than ' '
// and do something more sophisticated for long words
line_count += 1;
current_line_length = word.len();
} else {
// add the word and a space
current_line_length += word.len() + 1;
};
}
(line_count as f32) * font_metrics.char_height
});
match text_context.writing_mode {
WritingMode::Horizontal => Size { width: inline_size, height: block_size },
WritingMode::Vertical => Size { width: block_size, height: inline_size },
}
}

View File

@@ -0,0 +1,256 @@
//! ## Example: Partial Tree with Directly Owned Children
//!
//! The following example demonstrate an implementation of Taffy's Partial trait and usage of the low-level compute APIs.
//! This example uses directly owned children with NodeId's being index's into vec on parent node.
//! Since an iterator created from a node can't access grandchildren, we are limited to only implement `TraversePartialTree`.
//! See the [`crate::tree::traits`] module for more details about the low-level traits.
mod common {
pub mod image;
pub mod text;
}
use common::image::{image_measure_function, ImageContext};
use common::text::{text_measure_function, FontMetrics, TextContext, WritingMode, LOREM_IPSUM};
use taffy::{
compute_cached_layout, compute_flexbox_layout, compute_grid_layout, compute_leaf_layout, compute_root_layout,
prelude::*, Cache, CacheTree, Layout, Style,
};
#[derive(Debug, Copy, Clone)]
#[allow(dead_code)]
enum NodeKind {
Flexbox,
Grid,
Text,
Image,
}
struct Node {
kind: NodeKind,
style: Style,
text_data: Option<TextContext>,
image_data: Option<ImageContext>,
cache: Cache,
layout: Layout,
children: Vec<Node>,
}
impl Default for Node {
fn default() -> Self {
Node {
kind: NodeKind::Flexbox,
style: Style::default(),
text_data: None,
image_data: None,
cache: Cache::new(),
layout: Layout::with_order(0),
children: Vec::new(),
}
}
}
#[allow(dead_code)]
impl Node {
pub fn new_row(style: Style) -> Node {
Node {
kind: NodeKind::Flexbox,
style: Style { display: Display::Flex, flex_direction: FlexDirection::Row, ..style },
..Node::default()
}
}
pub fn new_column(style: Style) -> Node {
Node {
kind: NodeKind::Flexbox,
style: Style { display: Display::Flex, flex_direction: FlexDirection::Column, ..style },
..Node::default()
}
}
pub fn new_grid(style: Style) -> Node {
Node { kind: NodeKind::Grid, style: Style { display: Display::Grid, ..style }, ..Node::default() }
}
pub fn new_text(style: Style, text_data: TextContext) -> Node {
Node { kind: NodeKind::Text, style, text_data: Some(text_data), ..Node::default() }
}
pub fn new_image(style: Style, image_data: ImageContext) -> Node {
Node { kind: NodeKind::Image, style, image_data: Some(image_data), ..Node::default() }
}
pub fn append_child(&mut self, node: Node) {
self.children.push(node);
}
pub fn compute_layout(&mut self, available_space: Size<AvailableSpace>) {
compute_root_layout(self, NodeId::from(usize::MAX), available_space);
}
/// The methods on LayoutPartialTree need to be able to access:
///
/// - The node being laid out
/// - Direct children of the node being laid out
///
/// Each must have an ID. For children we simply use it's index. For the node itself
/// we use usize::MAX on the assumption that there will never be that many children.
fn node_from_id(&self, node_id: NodeId) -> &Node {
let idx = usize::from(node_id);
if idx == usize::MAX {
self
} else {
&self.children[idx]
}
}
fn node_from_id_mut(&mut self, node_id: NodeId) -> &mut Node {
let idx = usize::from(node_id);
if idx == usize::MAX {
self
} else {
&mut self.children[idx]
}
}
}
struct ChildIter(std::ops::Range<usize>);
impl Iterator for ChildIter {
type Item = NodeId;
fn next(&mut self) -> Option<Self::Item> {
self.0.next().map(NodeId::from)
}
}
impl taffy::TraversePartialTree for Node {
type ChildIter<'a> = ChildIter;
fn child_ids(&self, _node_id: NodeId) -> Self::ChildIter<'_> {
ChildIter(0..self.children.len())
}
fn child_count(&self, _node_id: NodeId) -> usize {
self.children.len()
}
fn get_child_id(&self, _node_id: NodeId, index: usize) -> NodeId {
NodeId::from(index)
}
}
impl taffy::LayoutPartialTree for Node {
type CoreContainerStyle<'a>
= &'a Style
where
Self: 'a;
fn get_core_container_style(&self, node_id: NodeId) -> Self::CoreContainerStyle<'_> {
&self.node_from_id(node_id).style
}
fn set_unrounded_layout(&mut self, node_id: NodeId, layout: &Layout) {
self.node_from_id_mut(node_id).layout = *layout
}
fn compute_child_layout(&mut self, node_id: NodeId, inputs: taffy::tree::LayoutInput) -> taffy::tree::LayoutOutput {
compute_cached_layout(self, node_id, inputs, |parent, node_id, inputs| {
let node = parent.node_from_id_mut(node_id);
let font_metrics = FontMetrics { char_width: 10.0, char_height: 10.0 };
match node.kind {
NodeKind::Flexbox => compute_flexbox_layout(node, node_id, inputs),
NodeKind::Grid => compute_grid_layout(node, node_id, inputs),
NodeKind::Text => compute_leaf_layout(inputs, &node.style, |known_dimensions, available_space| {
text_measure_function(
known_dimensions,
available_space,
node.text_data.as_ref().unwrap(),
&font_metrics,
)
}),
NodeKind::Image => compute_leaf_layout(inputs, &node.style, |known_dimensions, _available_space| {
image_measure_function(known_dimensions, node.image_data.as_ref().unwrap())
}),
}
})
}
}
impl CacheTree for Node {
fn cache_get(
&self,
node_id: NodeId,
known_dimensions: Size<Option<f32>>,
available_space: Size<AvailableSpace>,
run_mode: taffy::RunMode,
) -> Option<taffy::LayoutOutput> {
self.node_from_id(node_id).cache.get(known_dimensions, available_space, run_mode)
}
fn cache_store(
&mut self,
node_id: NodeId,
known_dimensions: Size<Option<f32>>,
available_space: Size<AvailableSpace>,
run_mode: taffy::RunMode,
layout_output: taffy::LayoutOutput,
) {
self.node_from_id_mut(node_id).cache.store(known_dimensions, available_space, run_mode, layout_output)
}
fn cache_clear(&mut self, node_id: NodeId) {
self.node_from_id_mut(node_id).cache.clear()
}
}
impl taffy::LayoutFlexboxContainer for Node {
type FlexboxContainerStyle<'a>
= &'a Style
where
Self: 'a;
type FlexboxItemStyle<'a>
= &'a Style
where
Self: 'a;
fn get_flexbox_container_style(&self, node_id: NodeId) -> Self::FlexboxContainerStyle<'_> {
&self.node_from_id(node_id).style
}
fn get_flexbox_child_style(&self, child_node_id: NodeId) -> Self::FlexboxItemStyle<'_> {
&self.node_from_id(child_node_id).style
}
}
impl taffy::LayoutGridContainer for Node {
type GridContainerStyle<'a>
= &'a Style
where
Self: 'a;
type GridItemStyle<'a>
= &'a Style
where
Self: 'a;
fn get_grid_container_style(&self, node_id: NodeId) -> Self::GridContainerStyle<'_> {
&self.node_from_id(node_id).style
}
fn get_grid_child_style(&self, child_node_id: NodeId) -> Self::GridItemStyle<'_> {
&self.node_from_id(child_node_id).style
}
}
fn main() -> Result<(), taffy::TaffyError> {
let mut root = Node::new_column(Style::DEFAULT);
let text_node = Node::new_text(
Style::default(),
TextContext { text_content: LOREM_IPSUM.into(), writing_mode: WritingMode::Horizontal },
);
root.append_child(text_node);
let image_node = Node::new_image(Style::default(), ImageContext { width: 400.0, height: 300.0 });
root.append_child(image_node);
// Compute layout
root.compute_layout(Size::MAX_CONTENT);
Ok(())
}

View File

@@ -0,0 +1,279 @@
mod common {
pub mod image;
pub mod text;
}
use common::image::{image_measure_function, ImageContext};
use common::text::{text_measure_function, FontMetrics, TextContext, WritingMode, LOREM_IPSUM};
use taffy::tree::Cache;
use taffy::util::print_tree;
use taffy::{
compute_cached_layout, compute_flexbox_layout, compute_grid_layout, compute_leaf_layout, compute_root_layout,
prelude::*, round_layout, CacheTree,
};
#[derive(Debug, Copy, Clone)]
#[allow(dead_code)]
enum NodeKind {
Flexbox,
Grid,
Text,
Image,
}
struct Node {
kind: NodeKind,
style: Style,
text_data: Option<TextContext>,
image_data: Option<ImageContext>,
cache: Cache,
unrounded_layout: Layout,
final_layout: Layout,
children: Vec<Node>,
}
impl Default for Node {
fn default() -> Self {
Node {
kind: NodeKind::Flexbox,
style: Style::default(),
text_data: None,
image_data: None,
cache: Cache::new(),
unrounded_layout: Layout::with_order(0),
final_layout: Layout::with_order(0),
children: Vec::new(),
}
}
}
#[allow(dead_code)]
impl Node {
pub fn new_row(style: Style) -> Node {
Node {
kind: NodeKind::Flexbox,
style: Style { display: Display::Flex, flex_direction: FlexDirection::Row, ..style },
..Node::default()
}
}
pub fn new_column(style: Style) -> Node {
Node {
kind: NodeKind::Flexbox,
style: Style { display: Display::Flex, flex_direction: FlexDirection::Column, ..style },
..Node::default()
}
}
pub fn new_grid(style: Style) -> Node {
Node { kind: NodeKind::Grid, style: Style { display: Display::Grid, ..style }, ..Node::default() }
}
pub fn new_text(style: Style, text_data: TextContext) -> Node {
Node { kind: NodeKind::Text, style, text_data: Some(text_data), ..Node::default() }
}
pub fn new_image(style: Style, image_data: ImageContext) -> Node {
Node { kind: NodeKind::Image, style, image_data: Some(image_data), ..Node::default() }
}
pub fn append_child(&mut self, node: Node) {
self.children.push(node);
}
unsafe fn as_id(&self) -> NodeId {
NodeId::from(self as *const Node as usize)
}
pub fn compute_layout(&mut self, available_space: Size<AvailableSpace>, use_rounding: bool) {
let root_node_id = unsafe { self.as_id() };
compute_root_layout(&mut StatelessLayoutTree, root_node_id, available_space);
if use_rounding {
round_layout(&mut StatelessLayoutTree, root_node_id)
}
}
pub fn print_tree(&mut self) {
print_tree(&StatelessLayoutTree, unsafe { self.as_id() });
}
}
struct ChildIter<'a>(std::slice::Iter<'a, Node>);
impl Iterator for ChildIter<'_> {
type Item = NodeId;
fn next(&mut self) -> Option<Self::Item> {
self.0.next().map(|c| NodeId::from(c as *const Node as usize))
}
}
#[inline(always)]
unsafe fn node_from_id<'a>(node_id: NodeId) -> &'a Node {
&*(usize::from(node_id) as *const Node)
}
#[inline(always)]
unsafe fn node_from_id_mut<'a>(node_id: NodeId) -> &'a mut Node {
&mut *(usize::from(node_id) as *mut Node)
}
struct StatelessLayoutTree;
impl TraversePartialTree for StatelessLayoutTree {
type ChildIter<'a> = ChildIter<'a>;
fn child_ids(&self, node_id: NodeId) -> Self::ChildIter<'_> {
unsafe { ChildIter(node_from_id(node_id).children.iter()) }
}
fn child_count(&self, node_id: NodeId) -> usize {
unsafe { node_from_id(node_id).children.len() }
}
fn get_child_id(&self, node_id: NodeId, index: usize) -> NodeId {
unsafe { node_from_id(node_id).children[index].as_id() }
}
}
impl TraverseTree for StatelessLayoutTree {}
impl LayoutPartialTree for StatelessLayoutTree {
type CoreContainerStyle<'a>
= &'a Style
where
Self: 'a;
fn get_core_container_style(&self, node_id: NodeId) -> Self::CoreContainerStyle<'_> {
unsafe { &node_from_id(node_id).style }
}
fn set_unrounded_layout(&mut self, node_id: NodeId, layout: &Layout) {
unsafe { node_from_id_mut(node_id).unrounded_layout = *layout };
}
fn compute_child_layout(&mut self, node_id: NodeId, inputs: taffy::tree::LayoutInput) -> taffy::tree::LayoutOutput {
compute_cached_layout(self, node_id, inputs, |tree, node_id, inputs| {
let node = unsafe { node_from_id_mut(node_id) };
let font_metrics = FontMetrics { char_width: 10.0, char_height: 10.0 };
match node.kind {
NodeKind::Flexbox => compute_flexbox_layout(tree, node_id, inputs),
NodeKind::Grid => compute_grid_layout(tree, node_id, inputs),
NodeKind::Text => compute_leaf_layout(inputs, &node.style, |known_dimensions, available_space| {
text_measure_function(
known_dimensions,
available_space,
node.text_data.as_ref().unwrap(),
&font_metrics,
)
}),
NodeKind::Image => compute_leaf_layout(inputs, &node.style, |known_dimensions, _available_space| {
image_measure_function(known_dimensions, node.image_data.as_ref().unwrap())
}),
}
})
}
}
impl CacheTree for StatelessLayoutTree {
fn cache_get(
&self,
node_id: NodeId,
known_dimensions: Size<Option<f32>>,
available_space: Size<AvailableSpace>,
run_mode: taffy::RunMode,
) -> Option<taffy::LayoutOutput> {
unsafe { node_from_id(node_id) }.cache.get(known_dimensions, available_space, run_mode)
}
fn cache_store(
&mut self,
node_id: NodeId,
known_dimensions: Size<Option<f32>>,
available_space: Size<AvailableSpace>,
run_mode: taffy::RunMode,
layout_output: taffy::LayoutOutput,
) {
unsafe { node_from_id_mut(node_id) }.cache.store(known_dimensions, available_space, run_mode, layout_output)
}
fn cache_clear(&mut self, node_id: NodeId) {
unsafe { node_from_id_mut(node_id) }.cache.clear()
}
}
impl taffy::LayoutFlexboxContainer for StatelessLayoutTree {
type FlexboxContainerStyle<'a>
= &'a Style
where
Self: 'a;
type FlexboxItemStyle<'a>
= &'a Style
where
Self: 'a;
fn get_flexbox_container_style(&self, node_id: NodeId) -> Self::FlexboxContainerStyle<'_> {
unsafe { &node_from_id(node_id).style }
}
fn get_flexbox_child_style(&self, child_node_id: NodeId) -> Self::FlexboxItemStyle<'_> {
unsafe { &node_from_id(child_node_id).style }
}
}
impl taffy::LayoutGridContainer for StatelessLayoutTree {
type GridContainerStyle<'a>
= &'a Style
where
Self: 'a;
type GridItemStyle<'a>
= &'a Style
where
Self: 'a;
fn get_grid_container_style(&self, node_id: NodeId) -> Self::GridContainerStyle<'_> {
unsafe { &node_from_id(node_id).style }
}
fn get_grid_child_style(&self, child_node_id: NodeId) -> Self::GridItemStyle<'_> {
unsafe { &node_from_id(child_node_id).style }
}
}
impl RoundTree for StatelessLayoutTree {
fn get_unrounded_layout(&self, node_id: NodeId) -> &Layout {
unsafe { &node_from_id_mut(node_id).unrounded_layout }
}
fn set_final_layout(&mut self, node_id: NodeId, layout: &Layout) {
unsafe { node_from_id_mut(node_id).final_layout = *layout }
}
}
impl PrintTree for StatelessLayoutTree {
fn get_debug_label(&self, node_id: NodeId) -> &'static str {
match unsafe { node_from_id(node_id).kind } {
NodeKind::Flexbox => "FLEX",
NodeKind::Grid => "GRID",
NodeKind::Text => "TEXT",
NodeKind::Image => "IMAGE",
}
}
fn get_final_layout(&self, node_id: NodeId) -> &Layout {
unsafe { &node_from_id(node_id).final_layout }
}
}
fn main() -> Result<(), taffy::TaffyError> {
let mut root = Node::new_column(Style::DEFAULT);
let text_node = Node::new_text(
Style::default(),
TextContext { text_content: LOREM_IPSUM.into(), writing_mode: WritingMode::Horizontal },
);
root.append_child(text_node);
let image_node = Node::new_image(Style::default(), ImageContext { width: 400.0, height: 300.0 });
root.append_child(image_node);
// Compute layout and print result
root.compute_layout(Size::MAX_CONTENT, true);
root.print_tree();
Ok(())
}

293
vendor/taffy/examples/custom_tree_vec.rs vendored Normal file
View File

@@ -0,0 +1,293 @@
mod common {
pub mod image;
pub mod text;
}
use common::image::{image_measure_function, ImageContext};
use common::text::{text_measure_function, FontMetrics, TextContext, WritingMode, LOREM_IPSUM};
use taffy::util::print_tree;
use taffy::{
compute_cached_layout, compute_flexbox_layout, compute_grid_layout, compute_leaf_layout, compute_root_layout,
prelude::*, round_layout, Cache, CacheTree,
};
#[derive(Debug, Copy, Clone)]
#[allow(dead_code)]
enum NodeKind {
Flexbox,
Grid,
Text,
Image,
}
struct Node {
kind: NodeKind,
style: Style,
text_data: Option<TextContext>,
image_data: Option<ImageContext>,
cache: Cache,
unrounded_layout: Layout,
final_layout: Layout,
children: Vec<usize>,
}
impl Default for Node {
fn default() -> Self {
Node {
kind: NodeKind::Flexbox,
style: Style::default(),
text_data: None,
image_data: None,
cache: Cache::new(),
unrounded_layout: Layout::with_order(0),
final_layout: Layout::with_order(0),
children: Vec::new(),
}
}
}
#[allow(dead_code)]
impl Node {
pub fn new_row(style: Style) -> Node {
Node {
kind: NodeKind::Flexbox,
style: Style { display: Display::Flex, flex_direction: FlexDirection::Row, ..style },
..Node::default()
}
}
pub fn new_column(style: Style) -> Node {
Node {
kind: NodeKind::Flexbox,
style: Style { display: Display::Flex, flex_direction: FlexDirection::Column, ..style },
..Node::default()
}
}
pub fn new_grid(style: Style) -> Node {
Node { kind: NodeKind::Grid, style: Style { display: Display::Grid, ..style }, ..Node::default() }
}
pub fn new_text(style: Style, text_data: TextContext) -> Node {
Node { kind: NodeKind::Text, style, text_data: Some(text_data), ..Node::default() }
}
pub fn new_image(style: Style, image_data: ImageContext) -> Node {
Node { kind: NodeKind::Image, style, image_data: Some(image_data), ..Node::default() }
}
}
struct Tree {
nodes: Vec<Node>,
}
impl Tree {
pub fn new() -> Tree {
Tree { nodes: Vec::new() }
}
pub fn add_node(&mut self, node: Node) -> usize {
self.nodes.push(node);
self.nodes.len() - 1
}
pub fn append_child(&mut self, parent: usize, child: usize) {
self.nodes[parent].children.push(child);
}
#[inline(always)]
fn node_from_id(&self, node_id: NodeId) -> &Node {
&self.nodes[usize::from(node_id)]
}
#[inline(always)]
fn node_from_id_mut(&mut self, node_id: NodeId) -> &mut Node {
&mut self.nodes[usize::from(node_id)]
}
pub fn compute_layout(&mut self, root: usize, available_space: Size<AvailableSpace>, use_rounding: bool) {
compute_root_layout(self, NodeId::from(root), available_space);
if use_rounding {
round_layout(self, NodeId::from(root))
}
}
pub fn print_tree(&mut self, root: usize) {
print_tree(self, NodeId::from(root));
}
}
struct ChildIter<'a>(std::slice::Iter<'a, usize>);
impl Iterator for ChildIter<'_> {
type Item = NodeId;
fn next(&mut self) -> Option<Self::Item> {
self.0.next().copied().map(NodeId::from)
}
}
impl taffy::TraversePartialTree for Tree {
type ChildIter<'a> = ChildIter<'a>;
fn child_ids(&self, node_id: NodeId) -> Self::ChildIter<'_> {
ChildIter(self.node_from_id(node_id).children.iter())
}
fn child_count(&self, node_id: NodeId) -> usize {
self.node_from_id(node_id).children.len()
}
fn get_child_id(&self, node_id: NodeId, index: usize) -> NodeId {
NodeId::from(self.node_from_id(node_id).children[index])
}
}
impl taffy::TraverseTree for Tree {}
impl taffy::LayoutPartialTree for Tree {
type CoreContainerStyle<'a>
= &'a Style
where
Self: 'a;
fn get_core_container_style(&self, node_id: NodeId) -> Self::CoreContainerStyle<'_> {
&self.node_from_id(node_id).style
}
fn set_unrounded_layout(&mut self, node_id: NodeId, layout: &Layout) {
self.node_from_id_mut(node_id).unrounded_layout = *layout;
}
fn compute_child_layout(&mut self, node_id: NodeId, inputs: taffy::tree::LayoutInput) -> taffy::tree::LayoutOutput {
compute_cached_layout(self, node_id, inputs, |tree, node_id, inputs| {
let node = tree.node_from_id_mut(node_id);
let font_metrics = FontMetrics { char_width: 10.0, char_height: 10.0 };
match node.kind {
NodeKind::Flexbox => compute_flexbox_layout(tree, node_id, inputs),
NodeKind::Grid => compute_grid_layout(tree, node_id, inputs),
NodeKind::Text => compute_leaf_layout(inputs, &node.style, |known_dimensions, available_space| {
text_measure_function(
known_dimensions,
available_space,
node.text_data.as_ref().unwrap(),
&font_metrics,
)
}),
NodeKind::Image => compute_leaf_layout(inputs, &node.style, |known_dimensions, _available_space| {
image_measure_function(known_dimensions, node.image_data.as_ref().unwrap())
}),
}
})
}
}
impl CacheTree for Tree {
fn cache_get(
&self,
node_id: NodeId,
known_dimensions: Size<Option<f32>>,
available_space: Size<AvailableSpace>,
run_mode: taffy::RunMode,
) -> Option<taffy::LayoutOutput> {
self.node_from_id(node_id).cache.get(known_dimensions, available_space, run_mode)
}
fn cache_store(
&mut self,
node_id: NodeId,
known_dimensions: Size<Option<f32>>,
available_space: Size<AvailableSpace>,
run_mode: taffy::RunMode,
layout_output: taffy::LayoutOutput,
) {
self.node_from_id_mut(node_id).cache.store(known_dimensions, available_space, run_mode, layout_output)
}
fn cache_clear(&mut self, node_id: NodeId) {
self.node_from_id_mut(node_id).cache.clear()
}
}
impl taffy::LayoutFlexboxContainer for Tree {
type FlexboxContainerStyle<'a>
= &'a Style
where
Self: 'a;
type FlexboxItemStyle<'a>
= &'a Style
where
Self: 'a;
fn get_flexbox_container_style(&self, node_id: NodeId) -> Self::FlexboxContainerStyle<'_> {
&self.node_from_id(node_id).style
}
fn get_flexbox_child_style(&self, child_node_id: NodeId) -> Self::FlexboxItemStyle<'_> {
&self.node_from_id(child_node_id).style
}
}
impl taffy::LayoutGridContainer for Tree {
type GridContainerStyle<'a>
= &'a Style
where
Self: 'a;
type GridItemStyle<'a>
= &'a Style
where
Self: 'a;
fn get_grid_container_style(&self, node_id: NodeId) -> Self::GridContainerStyle<'_> {
&self.node_from_id(node_id).style
}
fn get_grid_child_style(&self, child_node_id: NodeId) -> Self::GridItemStyle<'_> {
&self.node_from_id(child_node_id).style
}
}
impl taffy::RoundTree for Tree {
fn get_unrounded_layout(&self, node_id: NodeId) -> &Layout {
&self.node_from_id(node_id).unrounded_layout
}
fn set_final_layout(&mut self, node_id: NodeId, layout: &Layout) {
self.node_from_id_mut(node_id).final_layout = *layout;
}
}
impl taffy::PrintTree for Tree {
fn get_debug_label(&self, node_id: NodeId) -> &'static str {
match self.node_from_id(node_id).kind {
NodeKind::Flexbox => "FLEX",
NodeKind::Grid => "GRID",
NodeKind::Text => "TEXT",
NodeKind::Image => "IMAGE",
}
}
fn get_final_layout(&self, node_id: NodeId) -> &Layout {
&self.node_from_id(node_id).final_layout
}
}
fn main() -> Result<(), taffy::TaffyError> {
let mut tree = Tree::new();
let root = Node::new_column(Style::DEFAULT);
let root_id = tree.add_node(root);
let text_node = Node::new_text(
Style::default(),
TextContext { text_content: LOREM_IPSUM.into(), writing_mode: WritingMode::Horizontal },
);
let text_node_id = tree.add_node(text_node);
tree.append_child(root_id, text_node_id);
let image_node = Node::new_image(Style::default(), ImageContext { width: 400.0, height: 300.0 });
let image_node_id = tree.add_node(image_node);
tree.append_child(root_id, image_node_id);
// Compute layout and print result
tree.compute_layout(root_id, Size::MAX_CONTENT, true);
tree.print_tree(root_id);
Ok(())
}

24
vendor/taffy/examples/flexbox_gap.rs vendored Normal file
View File

@@ -0,0 +1,24 @@
use taffy::prelude::*;
// Creates three 20px x 20px children, evenly spaced 10px apart from each other
// Thus the container is 80px x 20px.
fn main() -> Result<(), taffy::TaffyError> {
let mut taffy: TaffyTree<()> = TaffyTree::new();
let child_style = Style { size: Size { width: length(20.0), height: length(20.0) }, ..Default::default() };
let child0 = taffy.new_leaf(child_style.clone())?;
let child1 = taffy.new_leaf(child_style.clone())?;
let child2 = taffy.new_leaf(child_style.clone())?;
let root = taffy.new_with_children(
Style { gap: Size { width: length(10.0), height: zero() }, ..Default::default() },
&[child0, child1, child2],
)?;
// Compute layout and print result
taffy.compute_layout(root, Size::MAX_CONTENT)?;
taffy.print_tree(root);
Ok(())
}

View File

@@ -0,0 +1,48 @@
// This creates a so-called "holy grail" layout using the CSS Grid layout algorithm
// See: https://en.wikipedia.org/wiki/Holy_grail_(web_design)
// NOTE: This example requires the `grid` feature flag to be enabled.
#[cfg(not(feature = "grid"))]
fn main() {
println!("Error: this example requires the 'grid' feature to be enabled");
println!("Try:");
println!(" cargo run --example grid_holy_grail --features grid")
}
#[cfg(feature = "grid")]
fn default<T: Default>() -> T {
Default::default()
}
#[cfg(feature = "grid")]
fn main() -> Result<(), taffy::TaffyError> {
use taffy::prelude::*;
let mut taffy: TaffyTree<()> = TaffyTree::new();
// Setup the grid
let root_style = Style {
display: Display::Grid,
size: Size { width: length(800.0), height: length(600.0) },
grid_template_columns: vec![length(250.0), fr(1.0), length(250.0)],
grid_template_rows: vec![length(150.0), fr(1.0), length(150.0)],
..default()
};
// Define the child nodes
let header = taffy.new_leaf(Style { grid_row: line(1), grid_column: span(3), ..default() })?;
let left_sidebar = taffy.new_leaf(Style { grid_row: line(2), grid_column: line(1), ..default() })?;
let content_area = taffy.new_leaf(Style { grid_row: line(2), grid_column: line(2), ..default() })?;
let right_sidebar = taffy.new_leaf(Style { grid_row: line(2), grid_column: line(3), ..default() })?;
let footer = taffy.new_leaf(Style { grid_row: line(3), grid_column: span(3), ..default() })?;
// Create the container with the children
let root = taffy.new_with_children(root_style, &[header, left_sidebar, content_area, right_sidebar, footer])?;
// Compute layout and print result
taffy.compute_layout(root, Size { width: length(800.0), height: length(600.0) })?;
taffy.print_tree(root);
Ok(())
}

69
vendor/taffy/examples/measure.rs vendored Normal file
View File

@@ -0,0 +1,69 @@
mod common {
pub mod image;
pub mod text;
}
use common::image::{image_measure_function, ImageContext};
use common::text::{text_measure_function, FontMetrics, TextContext, WritingMode, LOREM_IPSUM};
use taffy::prelude::*;
enum NodeContext {
Text(TextContext),
Image(ImageContext),
}
fn measure_function(
known_dimensions: taffy::geometry::Size<Option<f32>>,
available_space: taffy::geometry::Size<taffy::style::AvailableSpace>,
node_context: Option<&mut NodeContext>,
font_metrics: &FontMetrics,
) -> Size<f32> {
if let Size { width: Some(width), height: Some(height) } = known_dimensions {
return Size { width, height };
}
match node_context {
None => Size::ZERO,
Some(NodeContext::Text(text_context)) => {
text_measure_function(known_dimensions, available_space, &*text_context, font_metrics)
}
Some(NodeContext::Image(image_context)) => image_measure_function(known_dimensions, image_context),
}
}
fn main() -> Result<(), taffy::TaffyError> {
let mut taffy: TaffyTree<NodeContext> = TaffyTree::new();
let font_metrics = FontMetrics { char_width: 10.0, char_height: 10.0 };
let text_node = taffy.new_leaf_with_context(
Style::default(),
NodeContext::Text(TextContext { text_content: LOREM_IPSUM.into(), writing_mode: WritingMode::Horizontal }),
)?;
let image_node = taffy
.new_leaf_with_context(Style::default(), NodeContext::Image(ImageContext { width: 400.0, height: 300.0 }))?;
let root = taffy.new_with_children(
Style {
display: Display::Flex,
flex_direction: FlexDirection::Column,
size: Size { width: length(200.0), height: auto() },
..Default::default()
},
&[text_node, image_node],
)?;
// Compute layout and print result
taffy.compute_layout_with_measure(
root,
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)
},
)?;
taffy.print_tree(root);
Ok(())
}

55
vendor/taffy/examples/nested.rs vendored Normal file
View File

@@ -0,0 +1,55 @@
use taffy::prelude::*;
fn main() -> Result<(), taffy::TaffyError> {
let mut taffy: TaffyTree<()> = TaffyTree::new();
// left
let child_t1 = taffy.new_leaf(Style {
size: Size { width: Dimension::Length(5.0), height: Dimension::Length(5.0) },
..Default::default()
})?;
let div1 = taffy.new_with_children(
Style {
size: Size { width: Dimension::Percent(0.5), height: Dimension::Percent(1.0) },
// justify_content: JustifyContent::Center,
..Default::default()
},
&[child_t1],
)?;
// right
let child_t2 = taffy.new_leaf(Style {
size: Size { width: Dimension::Length(5.0), height: Dimension::Length(5.0) },
..Default::default()
})?;
let div2 = taffy.new_with_children(
Style {
size: Size { width: Dimension::Percent(0.5), height: Dimension::Percent(1.0) },
// justify_content: JustifyContent::Center,
..Default::default()
},
&[child_t2],
)?;
let container = taffy.new_with_children(
Style { size: Size { width: Dimension::Percent(1.0), height: Dimension::Percent(1.0) }, ..Default::default() },
&[div1, div2],
)?;
taffy.compute_layout(
container,
Size { height: AvailableSpace::Definite(100.0), width: AvailableSpace::Definite(100.0) },
)?;
println!("node: {:#?}", taffy.layout(container)?);
println!("div1: {:#?}", taffy.layout(div1)?);
println!("div2: {:#?}", taffy.layout(div2)?);
println!("child1: {:#?}", taffy.layout(child_t1)?);
println!("child2: {:#?}", taffy.layout(child_t2)?);
Ok(())
}

778
vendor/taffy/src/compute/block.rs vendored Normal file
View File

@@ -0,0 +1,778 @@
//! Computes the CSS block layout algorithm in the case that the block container being laid out contains only block-level boxes
use crate::geometry::{Line, Point, Rect, Size};
use crate::style::{AvailableSpace, CoreStyle, LengthPercentageAuto, Overflow, Position};
use crate::style_helpers::TaffyMaxContent;
use crate::tree::{CollapsibleMarginSet, Layout, LayoutInput, LayoutOutput, RunMode, SizingMode};
use crate::tree::{LayoutPartialTree, LayoutPartialTreeExt, NodeId};
use crate::util::debug::debug_log;
use crate::util::sys::f32_max;
use crate::util::sys::Vec;
use crate::util::MaybeMath;
use crate::util::{MaybeResolve, ResolveOrZero};
use crate::{BlockContainerStyle, BlockItemStyle, BoxGenerationMode, BoxSizing, LayoutBlockContainer, TextAlign};
#[cfg(feature = "content_size")]
use super::common::content_size::compute_content_size_contribution;
/// Per-child data that is accumulated and modified over the course of the layout algorithm
struct BlockItem {
/// The identifier for the associated node
node_id: NodeId,
/// The "source order" of the item. This is the index of the item within the children iterator,
/// and controls the order in which the nodes are placed
order: u32,
/// Items that are tables don't have stretch sizing applied to them
is_table: bool,
/// The base size of this item
size: Size<Option<f32>>,
/// The minimum allowable size of this item
min_size: Size<Option<f32>>,
/// The maximum allowable size of this item
max_size: Size<Option<f32>>,
/// The overflow style of the item
overflow: Point<Overflow>,
/// The width of the item's scrollbars (if it has scrollbars)
scrollbar_width: f32,
/// The position style of the item
position: Position,
/// The final offset of this item
inset: Rect<LengthPercentageAuto>,
/// The margin of this item
margin: Rect<LengthPercentageAuto>,
/// The margin of this item
padding: Rect<f32>,
/// The margin of this item
border: Rect<f32>,
/// The sum of padding and border for this item
padding_border_sum: Size<f32>,
/// The computed border box size of this item
computed_size: Size<f32>,
/// The computed "static position" of this item. The static position is the position
/// taking into account padding, border, margins, and scrollbar_gutters but not inset
static_position: Point<f32>,
/// Whether margins can be collapsed through this item
can_be_collapsed_through: bool,
}
/// Computes the layout of [`LayoutPartialTree`] according to the block layout algorithm
pub fn compute_block_layout(
tree: &mut impl LayoutBlockContainer,
node_id: NodeId,
inputs: LayoutInput,
) -> LayoutOutput {
let LayoutInput { known_dimensions, parent_size, run_mode, .. } = inputs;
let style = tree.get_block_container_style(node_id);
// Pull these out earlier to avoid borrowing issues
let aspect_ratio = style.aspect_ratio();
let padding = style.padding().resolve_or_zero(parent_size.width);
let border = style.border().resolve_or_zero(parent_size.width);
let padding_border_size = (padding + border).sum_axes();
let box_sizing_adjustment =
if style.box_sizing() == BoxSizing::ContentBox { padding_border_size } else { Size::ZERO };
let min_size = style
.min_size()
.maybe_resolve(parent_size)
.maybe_apply_aspect_ratio(aspect_ratio)
.maybe_add(box_sizing_adjustment);
let max_size = style
.max_size()
.maybe_resolve(parent_size)
.maybe_apply_aspect_ratio(aspect_ratio)
.maybe_add(box_sizing_adjustment);
let clamped_style_size = if inputs.sizing_mode == SizingMode::InherentSize {
style
.size()
.maybe_resolve(parent_size)
.maybe_apply_aspect_ratio(aspect_ratio)
.maybe_add(box_sizing_adjustment)
.maybe_clamp(min_size, max_size)
} else {
Size::NONE
};
drop(style);
// If both min and max in a given axis are set and max <= min then this determines the size in that axis
let min_max_definite_size = min_size.zip_map(max_size, |min, max| match (min, max) {
(Some(min), Some(max)) if max <= min => Some(min),
_ => None,
});
let styled_based_known_dimensions =
known_dimensions.or(min_max_definite_size).or(clamped_style_size).maybe_max(padding_border_size);
// Short-circuit layout if the container's size is fully determined by the container's size and the run mode
// is ComputeSize (and thus the container's size is all that we're interested in)
if run_mode == RunMode::ComputeSize {
if let Size { width: Some(width), height: Some(height) } = styled_based_known_dimensions {
return LayoutOutput::from_outer_size(Size { width, height });
}
}
debug_log!("BLOCK");
compute_inner(tree, node_id, LayoutInput { known_dimensions: styled_based_known_dimensions, ..inputs })
}
/// Computes the layout of [`LayoutBlockContainer`] according to the block layout algorithm
fn compute_inner(tree: &mut impl LayoutBlockContainer, node_id: NodeId, inputs: LayoutInput) -> LayoutOutput {
let LayoutInput {
known_dimensions, parent_size, available_space, run_mode, vertical_margins_are_collapsible, ..
} = inputs;
let style = tree.get_block_container_style(node_id);
let raw_padding = style.padding();
let raw_border = style.border();
let raw_margin = style.margin();
let aspect_ratio = style.aspect_ratio();
let padding = raw_padding.resolve_or_zero(parent_size.width);
let border = raw_border.resolve_or_zero(parent_size.width);
// Scrollbar gutters are reserved when the `overflow` property is set to `Overflow::Scroll`.
// However, the axis are switched (transposed) because a node that scrolls vertically needs
// *horizontal* space to be reserved for a scrollbar
let scrollbar_gutter = {
let offsets = style.overflow().transpose().map(|overflow| match overflow {
Overflow::Scroll => style.scrollbar_width(),
_ => 0.0,
});
// TODO: make side configurable based on the `direction` property
Rect { top: 0.0, left: 0.0, right: offsets.x, bottom: offsets.y }
};
let padding_border = padding + border;
let padding_border_size = padding_border.sum_axes();
let content_box_inset = padding_border + scrollbar_gutter;
let container_content_box_size = known_dimensions.maybe_sub(content_box_inset.sum_axes());
let box_sizing_adjustment =
if style.box_sizing() == BoxSizing::ContentBox { padding_border_size } else { Size::ZERO };
let size =
style.size().maybe_resolve(parent_size).maybe_apply_aspect_ratio(aspect_ratio).maybe_add(box_sizing_adjustment);
let min_size = style
.min_size()
.maybe_resolve(parent_size)
.maybe_apply_aspect_ratio(aspect_ratio)
.maybe_add(box_sizing_adjustment);
let max_size = style
.max_size()
.maybe_resolve(parent_size)
.maybe_apply_aspect_ratio(aspect_ratio)
.maybe_add(box_sizing_adjustment);
// Determine margin collapsing behaviour
let own_margins_collapse_with_children = Line {
start: vertical_margins_are_collapsible.start
&& !style.overflow().x.is_scroll_container()
&& !style.overflow().y.is_scroll_container()
&& style.position() == Position::Relative
&& padding.top == 0.0
&& border.top == 0.0,
end: vertical_margins_are_collapsible.end
&& !style.overflow().x.is_scroll_container()
&& !style.overflow().y.is_scroll_container()
&& style.position() == Position::Relative
&& padding.bottom == 0.0
&& border.bottom == 0.0
&& size.height.is_none(),
};
let has_styles_preventing_being_collapsed_through = !style.is_block()
|| style.overflow().x.is_scroll_container()
|| style.overflow().y.is_scroll_container()
|| style.position() == Position::Absolute
|| padding.top > 0.0
|| padding.bottom > 0.0
|| border.top > 0.0
|| border.bottom > 0.0
|| matches!(size.height, Some(h) if h > 0.0)
|| matches!(min_size.height, Some(h) if h > 0.0);
let text_align = style.text_align();
drop(style);
// 1. Generate items
let mut items = generate_item_list(tree, node_id, container_content_box_size);
// 2. Compute container width
let container_outer_width = known_dimensions.width.unwrap_or_else(|| {
let available_width = available_space.width.maybe_sub(content_box_inset.horizontal_axis_sum());
let intrinsic_width = determine_content_based_container_width(tree, &items, available_width)
+ content_box_inset.horizontal_axis_sum();
intrinsic_width.maybe_clamp(min_size.width, max_size.width).maybe_max(Some(padding_border_size.width))
});
// Short-circuit if computing size and both dimensions known
if let (RunMode::ComputeSize, Some(container_outer_height)) = (run_mode, known_dimensions.height) {
return LayoutOutput::from_outer_size(Size { width: container_outer_width, height: container_outer_height });
}
// 3. Perform final item layout and return content height
let resolved_padding = raw_padding.resolve_or_zero(Some(container_outer_width));
let resolved_border = raw_border.resolve_or_zero(Some(container_outer_width));
let resolved_content_box_inset = resolved_padding + resolved_border + scrollbar_gutter;
let (inflow_content_size, intrinsic_outer_height, first_child_top_margin_set, last_child_bottom_margin_set) =
perform_final_layout_on_in_flow_children(
tree,
&mut items,
container_outer_width,
content_box_inset,
resolved_content_box_inset,
text_align,
own_margins_collapse_with_children,
);
let container_outer_height = known_dimensions
.height
.unwrap_or(intrinsic_outer_height.maybe_clamp(min_size.height, max_size.height))
.maybe_max(Some(padding_border_size.height));
let final_outer_size = Size { width: container_outer_width, height: container_outer_height };
// Short-circuit if computing size
if run_mode == RunMode::ComputeSize {
return LayoutOutput::from_outer_size(final_outer_size);
}
// 4. Layout absolutely positioned children
let absolute_position_inset = resolved_border + scrollbar_gutter;
let absolute_position_area = final_outer_size - absolute_position_inset.sum_axes();
let absolute_position_offset = Point { x: absolute_position_inset.left, y: absolute_position_inset.top };
let absolute_content_size =
perform_absolute_layout_on_absolute_children(tree, &items, absolute_position_area, absolute_position_offset);
// 5. Perform hidden layout on hidden children
let len = tree.child_count(node_id);
for order in 0..len {
let child = tree.get_child_id(node_id, order);
if tree.get_block_child_style(child).box_generation_mode() == BoxGenerationMode::None {
tree.set_unrounded_layout(child, &Layout::with_order(order as u32));
tree.perform_child_layout(
child,
Size::NONE,
Size::NONE,
Size::MAX_CONTENT,
SizingMode::InherentSize,
Line::FALSE,
);
}
}
// 7. Determine whether this node can be collapsed through
let all_in_flow_children_can_be_collapsed_through =
items.iter().all(|item| item.position == Position::Absolute || item.can_be_collapsed_through);
let can_be_collapsed_through =
!has_styles_preventing_being_collapsed_through && all_in_flow_children_can_be_collapsed_through;
#[cfg_attr(not(feature = "content_size"), allow(unused_variables))]
let content_size = inflow_content_size.f32_max(absolute_content_size);
LayoutOutput {
size: final_outer_size,
#[cfg(feature = "content_size")]
content_size,
first_baselines: Point::NONE,
top_margin: if own_margins_collapse_with_children.start {
first_child_top_margin_set
} else {
let margin_top = raw_margin.top.resolve_or_zero(parent_size.width);
CollapsibleMarginSet::from_margin(margin_top)
},
bottom_margin: if own_margins_collapse_with_children.end {
last_child_bottom_margin_set
} else {
let margin_bottom = raw_margin.bottom.resolve_or_zero(parent_size.width);
CollapsibleMarginSet::from_margin(margin_bottom)
},
margins_can_collapse_through: can_be_collapsed_through,
}
}
/// Create a `Vec` of `BlockItem` structs where each item in the `Vec` represents a child of the current node
#[inline]
fn generate_item_list(
tree: &impl LayoutBlockContainer,
node: NodeId,
node_inner_size: Size<Option<f32>>,
) -> Vec<BlockItem> {
tree.child_ids(node)
.map(|child_node_id| (child_node_id, tree.get_block_child_style(child_node_id)))
.filter(|(_, style)| style.box_generation_mode() != BoxGenerationMode::None)
.enumerate()
.map(|(order, (child_node_id, child_style))| {
let aspect_ratio = child_style.aspect_ratio();
let padding = child_style.padding().resolve_or_zero(node_inner_size);
let border = child_style.border().resolve_or_zero(node_inner_size);
let pb_sum = (padding + border).sum_axes();
let box_sizing_adjustment =
if child_style.box_sizing() == BoxSizing::ContentBox { pb_sum } else { Size::ZERO };
BlockItem {
node_id: child_node_id,
order: order as u32,
is_table: child_style.is_table(),
size: child_style
.size()
.maybe_resolve(node_inner_size)
.maybe_apply_aspect_ratio(aspect_ratio)
.maybe_add(box_sizing_adjustment),
min_size: child_style
.min_size()
.maybe_resolve(node_inner_size)
.maybe_apply_aspect_ratio(aspect_ratio)
.maybe_add(box_sizing_adjustment),
max_size: child_style
.max_size()
.maybe_resolve(node_inner_size)
.maybe_apply_aspect_ratio(aspect_ratio)
.maybe_add(box_sizing_adjustment),
overflow: child_style.overflow(),
scrollbar_width: child_style.scrollbar_width(),
position: child_style.position(),
inset: child_style.inset(),
margin: child_style.margin(),
padding,
border,
padding_border_sum: pb_sum,
// Fields to be computed later (for now we initialise with dummy values)
computed_size: Size::zero(),
static_position: Point::zero(),
can_be_collapsed_through: false,
}
})
.collect()
}
/// Compute the content-based width in the case that the width of the container is not known
#[inline]
fn determine_content_based_container_width(
tree: &mut impl LayoutPartialTree,
items: &[BlockItem],
available_width: AvailableSpace,
) -> f32 {
let available_space = Size { width: available_width, height: AvailableSpace::MinContent };
let mut max_child_width = 0.0;
for item in items.iter().filter(|item| item.position != Position::Absolute) {
let known_dimensions = item.size.maybe_clamp(item.min_size, item.max_size);
let width = known_dimensions.width.unwrap_or_else(|| {
let item_x_margin_sum =
item.margin.resolve_or_zero(available_space.width.into_option()).horizontal_axis_sum();
let size_and_baselines = tree.perform_child_layout(
item.node_id,
known_dimensions,
Size::NONE,
available_space.map_width(|w| w.maybe_sub(item_x_margin_sum)),
SizingMode::InherentSize,
Line::TRUE,
);
size_and_baselines.size.width + item_x_margin_sum
});
let width = f32_max(width, item.padding_border_sum.width);
max_child_width = f32_max(max_child_width, width);
}
max_child_width
}
/// Compute each child's final size and position
#[inline]
fn perform_final_layout_on_in_flow_children(
tree: &mut impl LayoutPartialTree,
items: &mut [BlockItem],
container_outer_width: f32,
content_box_inset: Rect<f32>,
resolved_content_box_inset: Rect<f32>,
text_align: TextAlign,
own_margins_collapse_with_children: Line<bool>,
) -> (Size<f32>, f32, CollapsibleMarginSet, CollapsibleMarginSet) {
// Resolve container_inner_width for sizing child nodes using initial content_box_inset
let container_inner_width = container_outer_width - content_box_inset.horizontal_axis_sum();
let parent_size = Size { width: Some(container_outer_width), height: None };
let available_space =
Size { width: AvailableSpace::Definite(container_inner_width), height: AvailableSpace::MinContent };
#[cfg_attr(not(feature = "content_size"), allow(unused_mut))]
let mut inflow_content_size = Size::ZERO;
let mut committed_y_offset = resolved_content_box_inset.top;
let mut y_offset_for_absolute = resolved_content_box_inset.top;
let mut first_child_top_margin_set = CollapsibleMarginSet::ZERO;
let mut active_collapsible_margin_set = CollapsibleMarginSet::ZERO;
let mut is_collapsing_with_first_margin_set = true;
for item in items.iter_mut() {
if item.position == Position::Absolute {
item.static_position = Point { x: resolved_content_box_inset.left, y: y_offset_for_absolute }
} else {
let item_margin = item.margin.map(|margin| margin.resolve_to_option(container_outer_width));
let item_non_auto_margin = item_margin.map(|m| m.unwrap_or(0.0));
let item_non_auto_x_margin_sum = item_non_auto_margin.horizontal_axis_sum();
let known_dimensions = if item.is_table {
Size::NONE
} else {
item.size
.map_width(|width| {
// TODO: Allow stretch-sizing to be conditional, as there are exceptions.
// e.g. Table children of blocks do not stretch fit
Some(
width
.unwrap_or(container_inner_width - item_non_auto_x_margin_sum)
.maybe_clamp(item.min_size.width, item.max_size.width),
)
})
.maybe_clamp(item.min_size, item.max_size)
};
let item_layout = tree.perform_child_layout(
item.node_id,
known_dimensions,
parent_size,
available_space.map_width(|w| w.maybe_sub(item_non_auto_x_margin_sum)),
SizingMode::InherentSize,
Line::TRUE,
);
let final_size = item_layout.size;
let top_margin_set = item_layout.top_margin.collapse_with_margin(item_margin.top.unwrap_or(0.0));
let bottom_margin_set = item_layout.bottom_margin.collapse_with_margin(item_margin.bottom.unwrap_or(0.0));
// Expand auto margins to fill available space
// Note: Vertical auto-margins for relatively positioned block items simply resolve to 0.
// See: https://www.w3.org/TR/CSS21/visudet.html#abs-non-replaced-width
let free_x_space = f32_max(0.0, container_inner_width - final_size.width - item_non_auto_x_margin_sum);
let x_axis_auto_margin_size = {
let auto_margin_count = item_margin.left.is_none() as u8 + item_margin.right.is_none() as u8;
if auto_margin_count > 0 {
free_x_space / auto_margin_count as f32
} else {
0.0
}
};
let resolved_margin = Rect {
left: item_margin.left.unwrap_or(x_axis_auto_margin_size),
right: item_margin.right.unwrap_or(x_axis_auto_margin_size),
top: top_margin_set.resolve(),
bottom: bottom_margin_set.resolve(),
};
// Resolve item inset
let inset =
item.inset.zip_size(Size { width: container_inner_width, height: 0.0 }, |p, s| p.maybe_resolve(s));
let inset_offset = Point {
x: inset.left.or(inset.right.map(|x| -x)).unwrap_or(0.0),
y: inset.top.or(inset.bottom.map(|x| -x)).unwrap_or(0.0),
};
let y_margin_offset = if is_collapsing_with_first_margin_set && own_margins_collapse_with_children.start {
0.0
} else {
active_collapsible_margin_set.collapse_with_margin(resolved_margin.top).resolve()
};
item.computed_size = item_layout.size;
item.can_be_collapsed_through = item_layout.margins_can_collapse_through;
item.static_position = Point {
x: resolved_content_box_inset.left,
y: committed_y_offset + active_collapsible_margin_set.resolve(),
};
let mut location = Point {
x: resolved_content_box_inset.left + inset_offset.x + resolved_margin.left,
y: committed_y_offset + inset_offset.y + y_margin_offset,
};
// Apply alignment
let item_outer_width = item_layout.size.width + resolved_margin.horizontal_axis_sum();
if item_outer_width < container_inner_width {
match text_align {
TextAlign::Auto => {
// Do nothing
}
TextAlign::LegacyLeft => {
// Do nothing. Left aligned by default.
}
TextAlign::LegacyRight => location.x += container_inner_width - item_outer_width,
TextAlign::LegacyCenter => location.x += (container_inner_width - item_outer_width) / 2.0,
}
}
let scrollbar_size = Size {
width: if item.overflow.y == Overflow::Scroll { item.scrollbar_width } else { 0.0 },
height: if item.overflow.x == Overflow::Scroll { item.scrollbar_width } else { 0.0 },
};
tree.set_unrounded_layout(
item.node_id,
&Layout {
order: item.order,
size: item_layout.size,
#[cfg(feature = "content_size")]
content_size: item_layout.content_size,
scrollbar_size,
location,
padding: item.padding,
border: item.border,
margin: resolved_margin,
},
);
#[cfg(feature = "content_size")]
{
inflow_content_size = inflow_content_size.f32_max(compute_content_size_contribution(
location,
final_size,
item_layout.content_size,
item.overflow,
));
}
// Update first_child_top_margin_set
if is_collapsing_with_first_margin_set {
if item.can_be_collapsed_through {
first_child_top_margin_set = first_child_top_margin_set
.collapse_with_set(top_margin_set)
.collapse_with_set(bottom_margin_set);
} else {
first_child_top_margin_set = first_child_top_margin_set.collapse_with_set(top_margin_set);
is_collapsing_with_first_margin_set = false;
}
}
// Update active_collapsible_margin_set
if item.can_be_collapsed_through {
active_collapsible_margin_set = active_collapsible_margin_set
.collapse_with_set(top_margin_set)
.collapse_with_set(bottom_margin_set);
y_offset_for_absolute = committed_y_offset + item_layout.size.height + y_margin_offset;
} else {
committed_y_offset += item_layout.size.height + y_margin_offset;
active_collapsible_margin_set = bottom_margin_set;
y_offset_for_absolute = committed_y_offset + active_collapsible_margin_set.resolve();
}
}
}
let last_child_bottom_margin_set = active_collapsible_margin_set;
let bottom_y_margin_offset =
if own_margins_collapse_with_children.end { 0.0 } else { last_child_bottom_margin_set.resolve() };
committed_y_offset += resolved_content_box_inset.bottom + bottom_y_margin_offset;
let content_height = f32_max(0.0, committed_y_offset);
(inflow_content_size, content_height, first_child_top_margin_set, last_child_bottom_margin_set)
}
/// Perform absolute layout on all absolutely positioned children.
#[inline]
fn perform_absolute_layout_on_absolute_children(
tree: &mut impl LayoutBlockContainer,
items: &[BlockItem],
area_size: Size<f32>,
area_offset: Point<f32>,
) -> Size<f32> {
let area_width = area_size.width;
let area_height = area_size.height;
#[cfg_attr(not(feature = "content_size"), allow(unused_mut))]
let mut absolute_content_size = Size::ZERO;
for item in items.iter().filter(|item| item.position == Position::Absolute) {
let child_style = tree.get_block_child_style(item.node_id);
// Skip items that are display:none or are not position:absolute
if child_style.box_generation_mode() == BoxGenerationMode::None || child_style.position() != Position::Absolute
{
continue;
}
let aspect_ratio = child_style.aspect_ratio();
let margin = child_style.margin().map(|margin| margin.resolve_to_option(area_width));
let padding = child_style.padding().resolve_or_zero(Some(area_width));
let border = child_style.border().resolve_or_zero(Some(area_width));
let padding_border_sum = (padding + border).sum_axes();
let box_sizing_adjustment =
if child_style.box_sizing() == BoxSizing::ContentBox { padding_border_sum } else { Size::ZERO };
// Resolve inset
let left = child_style.inset().left.maybe_resolve(area_width);
let right = child_style.inset().right.maybe_resolve(area_width);
let top = child_style.inset().top.maybe_resolve(area_height);
let bottom = child_style.inset().bottom.maybe_resolve(area_height);
// Compute known dimensions from min/max/inherent size styles
let style_size = child_style
.size()
.maybe_resolve(area_size)
.maybe_apply_aspect_ratio(aspect_ratio)
.maybe_add(box_sizing_adjustment);
let min_size = child_style
.min_size()
.maybe_resolve(area_size)
.maybe_apply_aspect_ratio(aspect_ratio)
.maybe_add(box_sizing_adjustment)
.or(padding_border_sum.map(Some))
.maybe_max(padding_border_sum);
let max_size = child_style
.max_size()
.maybe_resolve(area_size)
.maybe_apply_aspect_ratio(aspect_ratio)
.maybe_add(box_sizing_adjustment);
let mut known_dimensions = style_size.maybe_clamp(min_size, max_size);
drop(child_style);
// Fill in width from left/right and reapply aspect ratio if:
// - Width is not already known
// - Item has both left and right inset properties set
if let (None, Some(left), Some(right)) = (known_dimensions.width, left, right) {
let new_width_raw = area_width.maybe_sub(margin.left).maybe_sub(margin.right) - left - right;
known_dimensions.width = Some(f32_max(new_width_raw, 0.0));
known_dimensions = known_dimensions.maybe_apply_aspect_ratio(aspect_ratio).maybe_clamp(min_size, max_size);
}
// Fill in height from top/bottom and reapply aspect ratio if:
// - Height is not already known
// - Item has both top and bottom inset properties set
if let (None, Some(top), Some(bottom)) = (known_dimensions.height, top, bottom) {
let new_height_raw = area_height.maybe_sub(margin.top).maybe_sub(margin.bottom) - top - bottom;
known_dimensions.height = Some(f32_max(new_height_raw, 0.0));
known_dimensions = known_dimensions.maybe_apply_aspect_ratio(aspect_ratio).maybe_clamp(min_size, max_size);
}
let layout_output = tree.perform_child_layout(
item.node_id,
known_dimensions,
area_size.map(Some),
Size {
width: AvailableSpace::Definite(area_width.maybe_clamp(min_size.width, max_size.width)),
height: AvailableSpace::Definite(area_height.maybe_clamp(min_size.height, max_size.height)),
},
SizingMode::ContentSize,
Line::FALSE,
);
let measured_size = layout_output.size;
let final_size = known_dimensions.unwrap_or(measured_size).maybe_clamp(min_size, max_size);
let non_auto_margin = Rect {
left: if left.is_some() { margin.left.unwrap_or(0.0) } else { 0.0 },
right: if right.is_some() { margin.right.unwrap_or(0.0) } else { 0.0 },
top: if top.is_some() { margin.top.unwrap_or(0.0) } else { 0.0 },
bottom: if bottom.is_some() { margin.left.unwrap_or(0.0) } else { 0.0 },
};
// Expand auto margins to fill available space
// https://www.w3.org/TR/CSS21/visudet.html#abs-non-replaced-width
let auto_margin = {
// Auto margins for absolutely positioned elements in block containers only resolve
// if inset is set. Otherwise they resolve to 0.
let absolute_auto_margin_space = Point {
x: right.map(|right| area_size.width - right - left.unwrap_or(0.0)).unwrap_or(final_size.width),
y: bottom.map(|bottom| area_size.height - bottom - top.unwrap_or(0.0)).unwrap_or(final_size.height),
};
let free_space = Size {
width: absolute_auto_margin_space.x - final_size.width - non_auto_margin.horizontal_axis_sum(),
height: absolute_auto_margin_space.y - final_size.height - non_auto_margin.vertical_axis_sum(),
};
let auto_margin_size = Size {
// If all three of 'left', 'width', and 'right' are 'auto': First set any 'auto' values for 'margin-left' and 'margin-right' to 0.
// Then, if the 'direction' property of the element establishing the static-position containing block is 'ltr' set 'left' to the
// static position and apply rule number three below; otherwise, set 'right' to the static position and apply rule number one below.
//
// If none of the three is 'auto': If both 'margin-left' and 'margin-right' are 'auto', solve the equation under the extra constraint
// that the two margins get equal values, unless this would make them negative, in which case when direction of the containing block is
// 'ltr' ('rtl'), set 'margin-left' ('margin-right') to zero and solve for 'margin-right' ('margin-left'). If one of 'margin-left' or
// 'margin-right' is 'auto', solve the equation for that value. If the values are over-constrained, ignore the value for 'left' (in case
// the 'direction' property of the containing block is 'rtl') or 'right' (in case 'direction' is 'ltr') and solve for that value.
width: {
let auto_margin_count = margin.left.is_none() as u8 + margin.right.is_none() as u8;
if auto_margin_count == 2
&& (style_size.width.is_none() || style_size.width.unwrap() >= free_space.width)
{
0.0
} else if auto_margin_count > 0 {
free_space.width / auto_margin_count as f32
} else {
0.0
}
},
height: {
let auto_margin_count = margin.top.is_none() as u8 + margin.bottom.is_none() as u8;
if auto_margin_count == 2
&& (style_size.height.is_none() || style_size.height.unwrap() >= free_space.height)
{
0.0
} else if auto_margin_count > 0 {
free_space.height / auto_margin_count as f32
} else {
0.0
}
},
};
Rect {
left: margin.left.map(|_| 0.0).unwrap_or(auto_margin_size.width),
right: margin.right.map(|_| 0.0).unwrap_or(auto_margin_size.width),
top: margin.top.map(|_| 0.0).unwrap_or(auto_margin_size.height),
bottom: margin.bottom.map(|_| 0.0).unwrap_or(auto_margin_size.height),
}
};
let resolved_margin = Rect {
left: margin.left.unwrap_or(auto_margin.left),
right: margin.right.unwrap_or(auto_margin.right),
top: margin.top.unwrap_or(auto_margin.top),
bottom: margin.bottom.unwrap_or(auto_margin.bottom),
};
let location = Point {
x: left
.map(|left| left + resolved_margin.left)
.or(right.map(|right| area_size.width - final_size.width - right - resolved_margin.right))
.maybe_add(area_offset.x)
.unwrap_or(item.static_position.x + resolved_margin.left),
y: top
.map(|top| top + resolved_margin.top)
.or(bottom.map(|bottom| area_size.height - final_size.height - bottom - resolved_margin.bottom))
.maybe_add(area_offset.y)
.unwrap_or(item.static_position.y + resolved_margin.top),
};
// Note: axis intentionally switched here as scrollbars take up space in the opposite axis
// to the axis in which scrolling is enabled.
let scrollbar_size = Size {
width: if item.overflow.y == Overflow::Scroll { item.scrollbar_width } else { 0.0 },
height: if item.overflow.x == Overflow::Scroll { item.scrollbar_width } else { 0.0 },
};
tree.set_unrounded_layout(
item.node_id,
&Layout {
order: item.order,
size: final_size,
#[cfg(feature = "content_size")]
content_size: layout_output.content_size,
scrollbar_size,
location,
padding,
border,
margin: resolved_margin,
},
);
#[cfg(feature = "content_size")]
{
absolute_content_size = absolute_content_size.f32_max(compute_content_size_contribution(
location,
final_size,
layout_output.content_size,
item.overflow,
));
}
}
absolute_content_size
}

View File

@@ -0,0 +1,100 @@
//! Generic CSS alignment code that is shared between both the Flexbox and CSS Grid algorithms.
use crate::style::AlignContent;
/// Implement fallback alignment.
///
/// In addition to the spec at https://www.w3.org/TR/css-align-3/ this implementation follows
/// the resolution of https://github.com/w3c/csswg-drafts/issues/10154
pub(crate) fn apply_alignment_fallback(
free_space: f32,
num_items: usize,
mut alignment_mode: AlignContent,
mut is_safe: bool,
) -> AlignContent {
// Fallback occurs in two cases:
// 1. If there is only a single item being aligned and alignment is a distributed alignment keyword
// https://www.w3.org/TR/css-align-3/#distribution-values
if num_items <= 1 || free_space <= 0.0 {
(alignment_mode, is_safe) = match alignment_mode {
AlignContent::Stretch => (AlignContent::FlexStart, true),
AlignContent::SpaceBetween => (AlignContent::FlexStart, true),
AlignContent::SpaceAround => (AlignContent::Center, true),
AlignContent::SpaceEvenly => (AlignContent::Center, true),
_ => (alignment_mode, is_safe),
}
};
// 2. If free space is negative the "safe" alignment variants all fallback to Start alignment
if free_space <= 0.0 && is_safe {
alignment_mode = AlignContent::Start;
}
alignment_mode
}
/// Generic alignment function that is used:
/// - For both align-content and justify-content alignment
/// - For both the Flexbox and CSS Grid algorithms
///
/// CSS Grid does not apply gaps as part of alignment, so the gap parameter should
/// always be set to zero for CSS Grid.
pub(crate) fn compute_alignment_offset(
free_space: f32,
num_items: usize,
gap: f32,
alignment_mode: AlignContent,
layout_is_flex_reversed: bool,
is_first: bool,
) -> f32 {
if is_first {
match alignment_mode {
AlignContent::Start => 0.0,
AlignContent::FlexStart => {
if layout_is_flex_reversed {
free_space
} else {
0.0
}
}
AlignContent::End => free_space,
AlignContent::FlexEnd => {
if layout_is_flex_reversed {
0.0
} else {
free_space
}
}
AlignContent::Center => free_space / 2.0,
AlignContent::Stretch => 0.0,
AlignContent::SpaceBetween => 0.0,
AlignContent::SpaceAround => {
if free_space >= 0.0 {
(free_space / num_items as f32) / 2.0
} else {
free_space / 2.0
}
}
AlignContent::SpaceEvenly => {
if free_space >= 0.0 {
free_space / (num_items + 1) as f32
} else {
free_space / 2.0
}
}
}
} else {
let free_space = free_space.max(0.0);
gap + match alignment_mode {
AlignContent::Start => 0.0,
AlignContent::FlexStart => 0.0,
AlignContent::End => 0.0,
AlignContent::FlexEnd => 0.0,
AlignContent::Center => 0.0,
AlignContent::Stretch => 0.0,
AlignContent::SpaceBetween => free_space / (num_items - 1) as f32,
AlignContent::SpaceAround => free_space / num_items as f32,
AlignContent::SpaceEvenly => free_space / (num_items + 1) as f32,
}
}
}

View File

@@ -0,0 +1,32 @@
//! Generic CSS content size code that is shared between all CSS algorithms.
use crate::geometry::{Point, Size};
use crate::style::Overflow;
use crate::util::sys::f32_max;
#[inline(always)]
/// Determine how much width/height a given node contributes to it's parent's content size
pub(crate) fn compute_content_size_contribution(
location: Point<f32>,
size: Size<f32>,
content_size: Size<f32>,
overflow: Point<Overflow>,
) -> Size<f32> {
let size_content_size_contribution = Size {
width: match overflow.x {
Overflow::Visible => f32_max(size.width, content_size.width),
_ => size.width,
},
height: match overflow.y {
Overflow::Visible => f32_max(size.height, content_size.height),
_ => size.height,
},
};
if size_content_size_contribution.width > 0.0 && size_content_size_contribution.height > 0.0 {
Size {
width: location.x + size_content_size_contribution.width,
height: location.y + size_content_size_contribution.height,
}
} else {
Size::ZERO
}
}

View File

@@ -0,0 +1,5 @@
//! Generic code that is shared between multiple layout algorithms
pub(crate) mod alignment;
#[cfg(feature = "content_size")]
pub(crate) mod content_size;

2313
vendor/taffy/src/compute/flexbox.rs vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,307 @@
//! Alignment of tracks and final positioning of items
use super::types::GridTrack;
use crate::compute::common::alignment::{apply_alignment_fallback, compute_alignment_offset};
use crate::geometry::{InBothAbsAxis, Line, Point, Rect, Size};
use crate::style::{AlignContent, AlignItems, AlignSelf, AvailableSpace, CoreStyle, GridItemStyle, Overflow, Position};
use crate::tree::{Layout, LayoutPartialTreeExt, NodeId, SizingMode};
use crate::util::sys::f32_max;
use crate::util::{MaybeMath, MaybeResolve, ResolveOrZero};
#[cfg(feature = "content_size")]
use crate::compute::common::content_size::compute_content_size_contribution;
use crate::{BoxSizing, LayoutGridContainer};
/// Align the grid tracks within the grid according to the align-content (rows) or
/// justify-content (columns) property. This only does anything if the size of the
/// grid is not equal to the size of the grid container in the axis being aligned.
pub(super) fn align_tracks(
grid_container_content_box_size: f32,
padding: Line<f32>,
border: Line<f32>,
tracks: &mut [GridTrack],
track_alignment_style: AlignContent,
) {
let used_size: f32 = tracks.iter().map(|track| track.base_size).sum();
let free_space = grid_container_content_box_size - used_size;
let origin = padding.start + border.start;
// Count the number of non-collapsed tracks (not counting gutters)
let num_tracks = tracks.iter().skip(1).step_by(2).filter(|track| !track.is_collapsed).count();
// Grid layout treats gaps as full tracks rather than applying them at alignment so we
// simply pass zero here. Grid layout is never reversed.
let gap = 0.0;
let layout_is_reversed = false;
let is_safe = false; // TODO: Implement safe alignment
let track_alignment = apply_alignment_fallback(free_space, num_tracks, track_alignment_style, is_safe);
// Compute offsets
let mut total_offset = origin;
tracks.iter_mut().enumerate().for_each(|(i, track)| {
// Odd tracks are gutters (but slices are zero-indexed, so odd tracks have even indices)
let is_gutter = i % 2 == 0;
// The first non-gutter track is index 1
let is_first = i == 1;
let offset = if is_gutter {
0.0
} else {
compute_alignment_offset(free_space, num_tracks, gap, track_alignment, layout_is_reversed, is_first)
};
track.offset = total_offset + offset;
total_offset = total_offset + offset + track.base_size;
});
}
/// Align and size a grid item into it's final position
pub(super) fn align_and_position_item(
tree: &mut impl LayoutGridContainer,
node: NodeId,
order: u32,
grid_area: Rect<f32>,
container_alignment_styles: InBothAbsAxis<Option<AlignItems>>,
baseline_shim: f32,
) -> (Size<f32>, f32, f32) {
let grid_area_size = Size { width: grid_area.right - grid_area.left, height: grid_area.bottom - grid_area.top };
let style = tree.get_grid_child_style(node);
let overflow = style.overflow();
let scrollbar_width = style.scrollbar_width();
let aspect_ratio = style.aspect_ratio();
let justify_self = style.justify_self();
let align_self = style.align_self();
let position = style.position();
let inset_horizontal =
style.inset().horizontal_components().map(|size| size.resolve_to_option(grid_area_size.width));
let inset_vertical = style.inset().vertical_components().map(|size| size.resolve_to_option(grid_area_size.height));
let padding = style.padding().map(|p| p.resolve_or_zero(Some(grid_area_size.width)));
let border = style.border().map(|p| p.resolve_or_zero(Some(grid_area_size.width)));
let padding_border_size = (padding + border).sum_axes();
let box_sizing_adjustment =
if style.box_sizing() == BoxSizing::ContentBox { padding_border_size } else { Size::ZERO };
let inherent_size = style
.size()
.maybe_resolve(grid_area_size)
.maybe_apply_aspect_ratio(aspect_ratio)
.maybe_add(box_sizing_adjustment);
let min_size = style
.min_size()
.maybe_resolve(grid_area_size)
.maybe_add(box_sizing_adjustment)
.or(padding_border_size.map(Some))
.maybe_max(padding_border_size)
.maybe_apply_aspect_ratio(aspect_ratio);
let max_size = style
.max_size()
.maybe_resolve(grid_area_size)
.maybe_apply_aspect_ratio(aspect_ratio)
.maybe_add(box_sizing_adjustment);
// Resolve default alignment styles if they are set on neither the parent or the node itself
// Note: if the child has a preferred aspect ratio but neither width or height are set, then the width is stretched
// and the then height is calculated from the width according the aspect ratio
// See: https://www.w3.org/TR/css-grid-1/#grid-item-sizing
let alignment_styles = InBothAbsAxis {
horizontal: justify_self.or(container_alignment_styles.horizontal).unwrap_or_else(|| {
if inherent_size.width.is_some() {
AlignSelf::Start
} else {
AlignSelf::Stretch
}
}),
vertical: align_self.or(container_alignment_styles.vertical).unwrap_or_else(|| {
if inherent_size.height.is_some() || aspect_ratio.is_some() {
AlignSelf::Start
} else {
AlignSelf::Stretch
}
}),
};
// Note: This is not a bug. It is part of the CSS spec that both horizontal and vertical margins
// resolve against the WIDTH of the grid area.
let margin = style.margin().map(|margin| margin.resolve_to_option(grid_area_size.width));
let grid_area_minus_item_margins_size = Size {
width: grid_area_size.width.maybe_sub(margin.left).maybe_sub(margin.right),
height: grid_area_size.height.maybe_sub(margin.top).maybe_sub(margin.bottom) - baseline_shim,
};
// If node is absolutely positioned and width is not set explicitly, then deduce it
// from left, right and container_content_box if both are set.
let width = inherent_size.width.or_else(|| {
// Apply width derived from both the left and right properties of an absolutely
// positioned element being set
if position == Position::Absolute {
if let (Some(left), Some(right)) = (inset_horizontal.start, inset_horizontal.end) {
return Some(f32_max(grid_area_minus_item_margins_size.width - left - right, 0.0));
}
}
// Apply width based on stretch alignment if:
// - Alignment style is "stretch"
// - The node is not absolutely positioned
// - The node does not have auto margins in this axis.
if margin.left.is_some()
&& margin.right.is_some()
&& alignment_styles.horizontal == AlignSelf::Stretch
&& position != Position::Absolute
{
return Some(grid_area_minus_item_margins_size.width);
}
None
});
// Reapply aspect ratio after stretch and absolute position width adjustments
let Size { width, height } = Size { width, height: inherent_size.height }.maybe_apply_aspect_ratio(aspect_ratio);
let height = height.or_else(|| {
if position == Position::Absolute {
if let (Some(top), Some(bottom)) = (inset_vertical.start, inset_vertical.end) {
return Some(f32_max(grid_area_minus_item_margins_size.height - top - bottom, 0.0));
}
}
// Apply height based on stretch alignment if:
// - Alignment style is "stretch"
// - The node is not absolutely positioned
// - The node does not have auto margins in this axis.
if margin.top.is_some()
&& margin.bottom.is_some()
&& alignment_styles.vertical == AlignSelf::Stretch
&& position != Position::Absolute
{
return Some(grid_area_minus_item_margins_size.height);
}
None
});
// Reapply aspect ratio after stretch and absolute position height adjustments
let Size { width, height } = Size { width, height }.maybe_apply_aspect_ratio(aspect_ratio);
// Clamp size by min and max width/height
let Size { width, height } = Size { width, height }.maybe_clamp(min_size, max_size);
// Layout node
drop(style);
let layout_output = tree.perform_child_layout(
node,
Size { width, height },
grid_area_size.map(Option::Some),
grid_area_minus_item_margins_size.map(AvailableSpace::Definite),
SizingMode::InherentSize,
Line::FALSE,
);
// Resolve final size
let Size { width, height } = Size { width, height }.unwrap_or(layout_output.size).maybe_clamp(min_size, max_size);
let (x, x_margin) = align_item_within_area(
Line { start: grid_area.left, end: grid_area.right },
justify_self.unwrap_or(alignment_styles.horizontal),
width,
position,
inset_horizontal,
margin.horizontal_components(),
0.0,
);
let (y, y_margin) = align_item_within_area(
Line { start: grid_area.top, end: grid_area.bottom },
align_self.unwrap_or(alignment_styles.vertical),
height,
position,
inset_vertical,
margin.vertical_components(),
baseline_shim,
);
let scrollbar_size = Size {
width: if overflow.y == Overflow::Scroll { scrollbar_width } else { 0.0 },
height: if overflow.x == Overflow::Scroll { scrollbar_width } else { 0.0 },
};
let resolved_margin = Rect { left: x_margin.start, right: x_margin.end, top: y_margin.start, bottom: y_margin.end };
tree.set_unrounded_layout(
node,
&Layout {
order,
location: Point { x, y },
size: Size { width, height },
#[cfg(feature = "content_size")]
content_size: layout_output.content_size,
scrollbar_size,
padding,
border,
margin: resolved_margin,
},
);
#[cfg(feature = "content_size")]
let contribution =
compute_content_size_contribution(Point { x, y }, Size { width, height }, layout_output.content_size, overflow);
#[cfg(not(feature = "content_size"))]
let contribution = Size::ZERO;
(contribution, y, height)
}
/// Align and size a grid item along a single axis
pub(super) fn align_item_within_area(
grid_area: Line<f32>,
alignment_style: AlignSelf,
resolved_size: f32,
position: Position,
inset: Line<Option<f32>>,
margin: Line<Option<f32>>,
baseline_shim: f32,
) -> (f32, Line<f32>) {
// Calculate grid area dimension in the axis
let non_auto_margin = Line { start: margin.start.unwrap_or(0.0) + baseline_shim, end: margin.end.unwrap_or(0.0) };
let grid_area_size = f32_max(grid_area.end - grid_area.start, 0.0);
let free_space = f32_max(grid_area_size - resolved_size - non_auto_margin.sum(), 0.0);
// Expand auto margins to fill available space
let auto_margin_count = margin.start.is_none() as u8 + margin.end.is_none() as u8;
let auto_margin_size = if auto_margin_count > 0 { free_space / auto_margin_count as f32 } else { 0.0 };
let resolved_margin = Line {
start: margin.start.unwrap_or(auto_margin_size) + baseline_shim,
end: margin.end.unwrap_or(auto_margin_size),
};
// Compute offset in the axis
let alignment_based_offset = match alignment_style {
AlignSelf::Start | AlignSelf::FlexStart => resolved_margin.start,
AlignSelf::End | AlignSelf::FlexEnd => grid_area_size - resolved_size - resolved_margin.end,
AlignSelf::Center => (grid_area_size - resolved_size + resolved_margin.start - resolved_margin.end) / 2.0,
// TODO: Add support for baseline alignment. For now we treat it as "start".
AlignSelf::Baseline => resolved_margin.start,
AlignSelf::Stretch => resolved_margin.start,
};
let offset_within_area = if position == Position::Absolute {
if let Some(start) = inset.start {
start + non_auto_margin.start
} else if let Some(end) = inset.end {
grid_area_size - end - resolved_size - non_auto_margin.end
} else {
alignment_based_offset
}
} else {
alignment_based_offset
};
let mut start = grid_area.start + offset_within_area;
if position == Position::Relative {
start += inset.start.or(inset.end.map(|pos| -pos)).unwrap_or(0.0);
}
(start, resolved_margin)
}

View File

@@ -0,0 +1,608 @@
//! Helper functions for initialising GridTrack's from styles
//! This mainly consists of evaluating GridAutoTracks
use super::types::{GridTrack, TrackCounts};
use crate::geometry::{AbsoluteAxis, Size};
use crate::style::{GridTrackRepetition, LengthPercentage, NonRepeatedTrackSizingFunction, TrackSizingFunction};
use crate::style_helpers::TaffyAuto;
use crate::util::sys::{ceil, floor, Vec};
use crate::util::MaybeMath;
use crate::util::ResolveOrZero;
use crate::{GridContainerStyle, MaybeResolve};
/// Compute the number of rows and columns in the explicit grid
pub(crate) fn compute_explicit_grid_size_in_axis(
style: &impl GridContainerStyle,
template: &[TrackSizingFunction],
inner_container_size: Size<Option<f32>>,
axis: AbsoluteAxis,
) -> u16 {
// If template contains no tracks, then there are trivially zero explicit tracks
if template.is_empty() {
return 0;
}
// If there are any repetitions that contains no tracks, then the whole definition should be considered invalid
// and we default to no explicit tracks
let template_has_repetitions_with_zero_tracks = template.iter().any(|track_def| match track_def {
TrackSizingFunction::Single(_) => false,
TrackSizingFunction::Repeat(_, tracks) => tracks.is_empty(),
});
if template_has_repetitions_with_zero_tracks {
return 0;
}
// Compute that number of track generated by single track definition and repetitions with a fixed repetition count
let non_auto_repeating_track_count = template
.iter()
.map(|track_def| {
use GridTrackRepetition::{AutoFill, AutoFit, Count};
match track_def {
TrackSizingFunction::Single(_) => 1,
TrackSizingFunction::Repeat(Count(count), tracks) => count * tracks.len() as u16,
TrackSizingFunction::Repeat(AutoFit | AutoFill, _) => 0,
}
})
.sum::<u16>();
let auto_repetition_count = template.iter().filter(|track_def| track_def.is_auto_repetition()).count() as u16;
let all_track_defs_have_fixed_component = template.iter().all(|track_def| match track_def {
TrackSizingFunction::Single(sizing_function) => sizing_function.has_fixed_component(),
TrackSizingFunction::Repeat(_, tracks) => {
tracks.iter().all(|sizing_function| sizing_function.has_fixed_component())
}
});
let template_is_valid =
auto_repetition_count == 0 || (auto_repetition_count == 1 && all_track_defs_have_fixed_component);
// If the template is invalid because it contains multiple auto-repetition definitions or it combines an auto-repetition
// definition with non-fixed-size track sizing functions, then disregard it entirely and default to zero explicit tracks
if !template_is_valid {
return 0;
}
// If there are no repetitions, then the number of explicit tracks is simply equal to the lengths of the track definition
// vector (as each item in the Vec represents one track).
if auto_repetition_count == 0 {
return non_auto_repeating_track_count;
}
let repetition_definition = template
.iter()
.find_map(|def| {
use GridTrackRepetition::{AutoFill, AutoFit, Count};
match def {
TrackSizingFunction::Single(_) => None,
TrackSizingFunction::Repeat(Count(_), _) => None,
TrackSizingFunction::Repeat(AutoFit | AutoFill, tracks) => Some(tracks),
}
})
.unwrap();
let repetition_track_count = repetition_definition.len() as u16;
// Otherwise, run logic to resolve the auto-repeated track count:
//
// If the grid container has a definite size or max size in the relevant axis:
// - then the number of repetitions is the largest possible positive integer that does not cause the grid to overflow the content
// box of its grid container.
// Otherwise, if the grid container has a definite min size in the relevant axis:
// - then the number of repetitions is the smallest possible positive integer that fulfills that minimum requirement
// Otherwise, the specified track list repeats only once.
let style_size_is_definite = style.size().get_abs(axis).maybe_resolve(inner_container_size.get_abs(axis)).is_some();
let style_max_size_is_definite =
style.max_size().get_abs(axis).maybe_resolve(inner_container_size.get_abs(axis)).is_some();
let size_is_maximum = style_size_is_definite | style_max_size_is_definite;
// Determine the number of repetitions
let num_repetitions: u16 = match inner_container_size.get_abs(axis) {
None => 1,
Some(inner_container_size) => {
let parent_size = Some(inner_container_size);
/// ...treating each track as its max track sizing function if that is definite or as its minimum track sizing function
/// otherwise, flooring the max track sizing function by the min track sizing function if both are definite
fn track_definite_value(sizing_function: &NonRepeatedTrackSizingFunction, parent_size: Option<f32>) -> f32 {
let max_size = sizing_function.max.definite_value(parent_size);
let min_size = sizing_function.min.definite_value(parent_size);
max_size.map(|max| max.maybe_min(min_size)).or(min_size).unwrap()
}
let non_repeating_track_used_space: f32 = template
.iter()
.map(|track_def| {
use GridTrackRepetition::{AutoFill, AutoFit, Count};
match track_def {
TrackSizingFunction::Single(sizing_function) => {
track_definite_value(sizing_function, parent_size)
}
TrackSizingFunction::Repeat(Count(count), repeated_tracks) => {
let sum = repeated_tracks
.iter()
.map(|sizing_function| track_definite_value(sizing_function, parent_size))
.sum::<f32>();
sum * (*count as f32)
}
TrackSizingFunction::Repeat(AutoFit | AutoFill, _) => 0.0,
}
})
.sum();
let gap_size = style.gap().get_abs(axis).resolve_or_zero(Some(inner_container_size));
// Compute the amount of space that a single repetition of the repeated track list takes
let per_repetition_track_used_space: f32 = repetition_definition
.iter()
.map(|sizing_function| track_definite_value(sizing_function, parent_size))
.sum::<f32>();
// We special case the first repetition here because the number of gaps in the first repetition
// depends on the number of non-repeating tracks in the template
let first_repetition_and_non_repeating_tracks_used_space = non_repeating_track_used_space
+ per_repetition_track_used_space
+ ((non_auto_repeating_track_count + repetition_track_count).saturating_sub(1) as f32 * gap_size);
// If a single repetition already overflows the container then we return 1 as the repetition count
// (the number of repetitions is floored at 1)
if first_repetition_and_non_repeating_tracks_used_space > inner_container_size {
1u16
} else {
let per_repetition_gap_used_space = (repetition_definition.len() as f32) * gap_size;
let per_repetition_used_space = per_repetition_track_used_space + per_repetition_gap_used_space;
let num_repetition_that_fit = (inner_container_size
- first_repetition_and_non_repeating_tracks_used_space)
/ per_repetition_used_space;
// If the container size is a preferred or maximum size:
// Then we return the maximum number of repetitions that fit into the container without overflowing.
// If the container size is a minimum size:
// - Then we return the minimum number of repetitions required to overflow the size.
//
// In all cases we add the additional repetition that was already accounted for in the special-case computation above
if size_is_maximum {
(floor(num_repetition_that_fit) as u16) + 1
} else {
(ceil(num_repetition_that_fit) as u16) + 1
}
}
}
};
non_auto_repeating_track_count + (repetition_track_count * num_repetitions)
}
/// Resolve the track sizing functions of explicit tracks, automatically created tracks, and gutters
/// given a set of track counts and all of the relevant styles
pub(super) fn initialize_grid_tracks(
tracks: &mut Vec<GridTrack>,
counts: TrackCounts,
track_template: &[TrackSizingFunction],
auto_tracks: &[NonRepeatedTrackSizingFunction],
gap: LengthPercentage,
track_has_items: impl Fn(usize) -> bool,
) {
// Clear vector (in case this is a re-layout), reserve space for all tracks ahead of time to reduce allocations,
// and push the initial gutter
tracks.clear();
tracks.reserve((counts.len() * 2) + 1);
tracks.push(GridTrack::gutter(gap));
// Create negative implicit tracks
if counts.negative_implicit > 0 {
if auto_tracks.is_empty() {
let iter = core::iter::repeat(NonRepeatedTrackSizingFunction::AUTO);
create_implicit_tracks(tracks, counts.negative_implicit, iter, gap)
} else {
let offset = auto_tracks.len() - (counts.negative_implicit as usize % auto_tracks.len());
let iter = auto_tracks.iter().copied().cycle().skip(offset);
create_implicit_tracks(tracks, counts.negative_implicit, iter, gap)
}
}
let mut current_track_index = (counts.negative_implicit) as usize;
// Create explicit tracks
// An explicit check against the count (rather than just relying on track_template being empty) is required here
// because a count of zero can result from the track_template being invalid, in which case it should be ignored.
if counts.explicit > 0 {
track_template.iter().for_each(|track_sizing_function| {
use GridTrackRepetition::{AutoFill, AutoFit, Count};
match track_sizing_function {
TrackSizingFunction::Single(sizing_function) => {
tracks.push(GridTrack::new(
sizing_function.min_sizing_function(),
sizing_function.max_sizing_function(),
));
tracks.push(GridTrack::gutter(gap));
current_track_index += 1;
}
TrackSizingFunction::Repeat(Count(count), repeated_tracks) => {
let track_iter = repeated_tracks.iter().cycle().take(repeated_tracks.len() * *count as usize);
track_iter.for_each(|sizing_function| {
tracks.push(GridTrack::new(
sizing_function.min_sizing_function(),
sizing_function.max_sizing_function(),
));
tracks.push(GridTrack::gutter(gap));
current_track_index += 1;
});
}
TrackSizingFunction::Repeat(repetition_kind @ (AutoFit | AutoFill), repeated_tracks) => {
let auto_repeated_track_count = (counts.explicit - (track_template.len() as u16 - 1)) as usize;
let iter = repeated_tracks.iter().copied().cycle();
for track_def in iter.take(auto_repeated_track_count) {
let mut track =
GridTrack::new(track_def.min_sizing_function(), track_def.max_sizing_function());
let mut gutter = GridTrack::gutter(gap);
// Auto-fit tracks that don't contain should be collapsed.
if *repetition_kind == AutoFit && !track_has_items(current_track_index) {
track.collapse();
gutter.collapse();
}
tracks.push(track);
tracks.push(gutter);
current_track_index += 1;
}
}
}
});
}
// Create positive implicit tracks
if auto_tracks.is_empty() {
let iter = core::iter::repeat(NonRepeatedTrackSizingFunction::AUTO);
create_implicit_tracks(tracks, counts.positive_implicit, iter, gap)
} else {
let iter = auto_tracks.iter().copied().cycle();
create_implicit_tracks(tracks, counts.positive_implicit, iter, gap)
}
// Mark first and last grid lines as collapsed
tracks.first_mut().unwrap().collapse();
tracks.last_mut().unwrap().collapse();
}
/// Utility function for repeating logic of creating implicit tracks
fn create_implicit_tracks(
tracks: &mut Vec<GridTrack>,
count: u16,
mut auto_tracks_iter: impl Iterator<Item = NonRepeatedTrackSizingFunction>,
gap: LengthPercentage,
) {
for _ in 0..count {
let track_def = auto_tracks_iter.next().unwrap();
tracks.push(GridTrack::new(track_def.min_sizing_function(), track_def.max_sizing_function()));
tracks.push(GridTrack::gutter(gap));
}
}
#[cfg(test)]
mod test {
use super::compute_explicit_grid_size_in_axis;
use super::initialize_grid_tracks;
use crate::compute::grid::types::GridTrackKind;
use crate::compute::grid::types::TrackCounts;
use crate::compute::grid::util::*;
use crate::geometry::AbsoluteAxis;
use crate::prelude::*;
#[test]
fn explicit_grid_sizing_no_repeats() {
let grid_style = (600.0, 600.0, 2, 4).into_grid();
let preferred_size = grid_style.size.map(|s| s.into_option());
let width = compute_explicit_grid_size_in_axis(
&grid_style,
&grid_style.grid_template_columns,
preferred_size,
AbsoluteAxis::Horizontal,
);
let height = compute_explicit_grid_size_in_axis(
&grid_style,
&grid_style.grid_template_rows,
preferred_size,
AbsoluteAxis::Vertical,
);
assert_eq!(width, 2);
assert_eq!(height, 4);
}
#[test]
fn explicit_grid_sizing_auto_fill_exact_fit() {
use GridTrackRepetition::AutoFill;
let grid_style = Style {
display: Display::Grid,
size: Size { width: length(120.0), height: length(80.0) },
grid_template_columns: vec![repeat(AutoFill, vec![length(40.0)])],
grid_template_rows: vec![repeat(AutoFill, vec![length(20.0)])],
..Default::default()
};
let preferred_size = grid_style.size.map(|s| s.into_option());
let width = compute_explicit_grid_size_in_axis(
&grid_style,
&grid_style.grid_template_columns,
preferred_size,
AbsoluteAxis::Horizontal,
);
let height = compute_explicit_grid_size_in_axis(
&grid_style,
&grid_style.grid_template_rows,
preferred_size,
AbsoluteAxis::Vertical,
);
assert_eq!(width, 3);
assert_eq!(height, 4);
}
#[test]
fn explicit_grid_sizing_auto_fill_non_exact_fit() {
use GridTrackRepetition::AutoFill;
let grid_style = Style {
display: Display::Grid,
size: Size { width: length(140.0), height: length(90.0) },
grid_template_columns: vec![repeat(AutoFill, vec![length(40.0)])],
grid_template_rows: vec![repeat(AutoFill, vec![length(20.0)])],
..Default::default()
};
let preferred_size = grid_style.size.map(|s| s.into_option());
let width = compute_explicit_grid_size_in_axis(
&grid_style,
&grid_style.grid_template_columns,
preferred_size,
AbsoluteAxis::Horizontal,
);
let height = compute_explicit_grid_size_in_axis(
&grid_style,
&grid_style.grid_template_rows,
preferred_size,
AbsoluteAxis::Vertical,
);
assert_eq!(width, 3);
assert_eq!(height, 4);
}
#[test]
fn explicit_grid_sizing_auto_fill_min_size_exact_fit() {
use GridTrackRepetition::AutoFill;
let grid_style = Style {
display: Display::Grid,
min_size: Size { width: length(120.0), height: length(80.0) },
grid_template_columns: vec![repeat(AutoFill, vec![length(40.0)])],
grid_template_rows: vec![repeat(AutoFill, vec![length(20.0)])],
..Default::default()
};
let inner_container_size = Size { width: Some(120.0), height: Some(80.0) };
let width = compute_explicit_grid_size_in_axis(
&grid_style,
&grid_style.grid_template_columns,
inner_container_size,
AbsoluteAxis::Horizontal,
);
let height = compute_explicit_grid_size_in_axis(
&grid_style,
&grid_style.grid_template_rows,
inner_container_size,
AbsoluteAxis::Vertical,
);
assert_eq!(width, 3);
assert_eq!(height, 4);
}
#[test]
fn explicit_grid_sizing_auto_fill_min_size_non_exact_fit() {
use GridTrackRepetition::AutoFill;
let grid_style = Style {
display: Display::Grid,
min_size: Size { width: length(140.0), height: length(90.0) },
grid_template_columns: vec![repeat(AutoFill, vec![length(40.0)])],
grid_template_rows: vec![repeat(AutoFill, vec![length(20.0)])],
..Default::default()
};
let inner_container_size = Size { width: Some(140.0), height: Some(90.0) };
let width = compute_explicit_grid_size_in_axis(
&grid_style,
&grid_style.grid_template_columns,
inner_container_size,
AbsoluteAxis::Horizontal,
);
let height = compute_explicit_grid_size_in_axis(
&grid_style,
&grid_style.grid_template_rows,
inner_container_size,
AbsoluteAxis::Vertical,
);
assert_eq!(width, 4);
assert_eq!(height, 5);
}
#[test]
fn explicit_grid_sizing_auto_fill_multiple_repeated_tracks() {
use GridTrackRepetition::AutoFill;
let grid_style = Style {
display: Display::Grid,
size: Size { width: length(140.0), height: length(100.0) },
grid_template_columns: vec![repeat(AutoFill, vec![length(40.0), length(20.0)])],
grid_template_rows: vec![repeat(AutoFill, vec![length(20.0), length(10.0)])],
..Default::default()
};
let preferred_size = grid_style.size.map(|s| s.into_option());
let width = compute_explicit_grid_size_in_axis(
&grid_style,
&grid_style.grid_template_columns,
preferred_size,
AbsoluteAxis::Horizontal,
);
let height = compute_explicit_grid_size_in_axis(
&grid_style,
&grid_style.grid_template_rows,
preferred_size,
AbsoluteAxis::Vertical,
);
assert_eq!(width, 4); // 2 repetitions * 2 repeated tracks = 4 tracks in total
assert_eq!(height, 6); // 3 repetitions * 2 repeated tracks = 4 tracks in total
}
#[test]
fn explicit_grid_sizing_auto_fill_gap() {
use GridTrackRepetition::AutoFill;
let grid_style = Style {
display: Display::Grid,
size: Size { width: length(140.0), height: length(100.0) },
grid_template_columns: vec![repeat(AutoFill, vec![length(40.0)])],
grid_template_rows: vec![repeat(AutoFill, vec![length(20.0)])],
gap: length(20.0),
..Default::default()
};
let preferred_size = grid_style.size.map(|s| s.into_option());
let width = compute_explicit_grid_size_in_axis(
&grid_style,
&grid_style.grid_template_columns,
preferred_size,
AbsoluteAxis::Horizontal,
);
let height = compute_explicit_grid_size_in_axis(
&grid_style,
&grid_style.grid_template_rows,
preferred_size,
AbsoluteAxis::Vertical,
);
assert_eq!(width, 2); // 2 tracks + 1 gap
assert_eq!(height, 3); // 3 tracks + 2 gaps
}
#[test]
fn explicit_grid_sizing_no_defined_size() {
use GridTrackRepetition::AutoFill;
let grid_style = Style {
display: Display::Grid,
grid_template_columns: vec![repeat(AutoFill, vec![length(40.0), percent(0.5), length(20.0)])],
grid_template_rows: vec![repeat(AutoFill, vec![length(20.0)])],
gap: length(20.0),
..Default::default()
};
let preferred_size = grid_style.size.map(|s| s.into_option());
let width = compute_explicit_grid_size_in_axis(
&grid_style,
&grid_style.grid_template_columns,
preferred_size,
AbsoluteAxis::Horizontal,
);
let height = compute_explicit_grid_size_in_axis(
&grid_style,
&grid_style.grid_template_rows,
preferred_size,
AbsoluteAxis::Vertical,
);
assert_eq!(width, 3);
assert_eq!(height, 1);
}
#[test]
fn explicit_grid_sizing_mix_repeated_and_non_repeated() {
use GridTrackRepetition::AutoFill;
let grid_style = Style {
display: Display::Grid,
size: Size { width: length(140.0), height: length(100.0) },
grid_template_columns: vec![length(20.0), repeat(AutoFill, vec![length(40.0)])],
grid_template_rows: vec![length(40.0), repeat(AutoFill, vec![length(20.0)])],
gap: length(20.0),
..Default::default()
};
let preferred_size = grid_style.size.map(|s| s.into_option());
let width = compute_explicit_grid_size_in_axis(
&grid_style,
&grid_style.grid_template_columns,
preferred_size,
AbsoluteAxis::Horizontal,
);
let height = compute_explicit_grid_size_in_axis(
&grid_style,
&grid_style.grid_template_rows,
preferred_size,
AbsoluteAxis::Vertical,
);
assert_eq!(width, 3); // 3 tracks + 2 gaps
assert_eq!(height, 2); // 2 tracks + 1 gap
}
#[test]
fn explicit_grid_sizing_mix_with_padding() {
use GridTrackRepetition::AutoFill;
let grid_style = Style {
display: Display::Grid,
size: Size { width: length(120.0), height: length(120.0) },
padding: Rect { left: length(10.0), right: length(10.0), top: length(20.0), bottom: length(20.0) },
grid_template_columns: vec![repeat(AutoFill, vec![length(20.0)])],
grid_template_rows: vec![repeat(AutoFill, vec![length(20.0)])],
..Default::default()
};
let inner_container_size = Size { width: Some(100.0), height: Some(80.0) };
let width = compute_explicit_grid_size_in_axis(
&grid_style,
&grid_style.grid_template_columns,
inner_container_size,
AbsoluteAxis::Horizontal,
);
let height = compute_explicit_grid_size_in_axis(
&grid_style,
&grid_style.grid_template_rows,
inner_container_size,
AbsoluteAxis::Vertical,
);
assert_eq!(width, 5); // 40px horizontal padding
assert_eq!(height, 4); // 20px vertical padding
}
#[test]
fn test_initialize_grid_tracks() {
let px0 = LengthPercentage::Length(0.0);
let px20 = LengthPercentage::Length(20.0);
let px100 = LengthPercentage::Length(100.0);
// Setup test
let track_template = vec![length(100.0), minmax(length(100.0), fr(2.0)), fr(1.0)];
let track_counts =
TrackCounts { negative_implicit: 3, explicit: track_template.len() as u16, positive_implicit: 3 };
let auto_tracks = vec![auto(), length(100.0)];
let gap = px20;
// Call function
let mut tracks = Vec::new();
initialize_grid_tracks(&mut tracks, track_counts, &track_template, &auto_tracks, gap, |_| false);
// Assertions
let expected = vec![
// Gutter
(GridTrackKind::Gutter, MinTrackSizingFunction::Fixed(px0), MaxTrackSizingFunction::Fixed(px0)),
// Negative implicit tracks
(GridTrackKind::Track, MinTrackSizingFunction::Fixed(px100), MaxTrackSizingFunction::Fixed(px100)),
(GridTrackKind::Gutter, MinTrackSizingFunction::Fixed(px20), MaxTrackSizingFunction::Fixed(px20)),
(GridTrackKind::Track, MinTrackSizingFunction::Auto, MaxTrackSizingFunction::Auto),
(GridTrackKind::Gutter, MinTrackSizingFunction::Fixed(px20), MaxTrackSizingFunction::Fixed(px20)),
(GridTrackKind::Track, MinTrackSizingFunction::Fixed(px100), MaxTrackSizingFunction::Fixed(px100)),
(GridTrackKind::Gutter, MinTrackSizingFunction::Fixed(px20), MaxTrackSizingFunction::Fixed(px20)),
// Explicit tracks
(GridTrackKind::Track, MinTrackSizingFunction::Fixed(px100), MaxTrackSizingFunction::Fixed(px100)),
(GridTrackKind::Gutter, MinTrackSizingFunction::Fixed(px20), MaxTrackSizingFunction::Fixed(px20)),
(GridTrackKind::Track, MinTrackSizingFunction::Fixed(px100), MaxTrackSizingFunction::Fraction(2.0)), // Note: separate min-max functions
(GridTrackKind::Gutter, MinTrackSizingFunction::Fixed(px20), MaxTrackSizingFunction::Fixed(px20)),
(GridTrackKind::Track, MinTrackSizingFunction::Auto, MaxTrackSizingFunction::Fraction(1.0)), // Note: min sizing function of flex sizing functions is auto
(GridTrackKind::Gutter, MinTrackSizingFunction::Fixed(px20), MaxTrackSizingFunction::Fixed(px20)),
// Positive implicit tracks
(GridTrackKind::Track, MinTrackSizingFunction::Auto, MaxTrackSizingFunction::Auto),
(GridTrackKind::Gutter, MinTrackSizingFunction::Fixed(px20), MaxTrackSizingFunction::Fixed(px20)),
(GridTrackKind::Track, MinTrackSizingFunction::Fixed(px100), MaxTrackSizingFunction::Fixed(px100)),
(GridTrackKind::Gutter, MinTrackSizingFunction::Fixed(px20), MaxTrackSizingFunction::Fixed(px20)),
(GridTrackKind::Track, MinTrackSizingFunction::Auto, MaxTrackSizingFunction::Auto),
(GridTrackKind::Gutter, MinTrackSizingFunction::Fixed(px0), MaxTrackSizingFunction::Fixed(px0)),
];
assert_eq!(tracks.len(), expected.len(), "Number of tracks doesn't match");
for (idx, (actual, (kind, min, max))) in tracks.into_iter().zip(expected).enumerate() {
assert_eq!(actual.kind, kind, "Track {idx} (0-based index)");
assert_eq!(actual.min_track_sizing_function, min, "Track {idx} (0-based index)");
assert_eq!(actual.max_track_sizing_function, max, "Track {idx} (0-based index)");
}
}
}

View File

@@ -0,0 +1,234 @@
//! This module is not required for spec compliance, but is used as a performance optimisation
//! to reduce the number of allocations required when creating a grid.
use crate::geometry::Line;
use crate::style::{GenericGridPlacement, GridPlacement};
use crate::GridItemStyle;
use core::cmp::{max, min};
use super::types::TrackCounts;
use super::OriginZeroLine;
/// Estimate the number of rows and columns in the grid
/// This is used as a performance optimisation to pre-size vectors and reduce allocations. It also forms a necessary step
/// in the auto-placement
/// - The estimates for the explicit and negative implicit track counts are exact.
/// - However, the estimates for the positive explicit track count is a lower bound as auto-placement can affect this
/// in ways which are impossible to predict until the auto-placement algorithm is run.
///
/// Note that this function internally mixes use of grid track numbers and grid line numbers
pub(crate) fn compute_grid_size_estimate<'a, S: GridItemStyle + 'a>(
explicit_col_count: u16,
explicit_row_count: u16,
child_styles_iter: impl Iterator<Item = S>,
) -> (TrackCounts, TrackCounts) {
// Iterate over children, producing an estimate of the min and max grid lines (in origin-zero coordinates where)
// along with the span of each item
let (col_min, col_max, col_max_span, row_min, row_max, row_max_span) =
get_known_child_positions(child_styles_iter, explicit_col_count, explicit_row_count);
// Compute *track* count estimates for each axis from:
// - The explicit track counts
// - The origin-zero coordinate min and max grid line variables
let negative_implicit_inline_tracks = col_min.implied_negative_implicit_tracks();
let explicit_inline_tracks = explicit_col_count;
let mut positive_implicit_inline_tracks = col_max.implied_positive_implicit_tracks(explicit_col_count);
let negative_implicit_block_tracks = row_min.implied_negative_implicit_tracks();
let explicit_block_tracks = explicit_row_count;
let mut positive_implicit_block_tracks = row_max.implied_positive_implicit_tracks(explicit_row_count);
// In each axis, adjust positive track estimate if any items have a span that does not fit within
// the total number of tracks in the estimate
let tot_inline_tracks = negative_implicit_inline_tracks + explicit_inline_tracks + positive_implicit_inline_tracks;
if tot_inline_tracks < col_max_span {
positive_implicit_inline_tracks = col_max_span - explicit_inline_tracks - negative_implicit_inline_tracks;
}
let tot_block_tracks = negative_implicit_block_tracks + explicit_block_tracks + positive_implicit_block_tracks;
if tot_block_tracks < row_max_span {
positive_implicit_block_tracks = row_max_span - explicit_block_tracks - negative_implicit_block_tracks;
}
let column_counts =
TrackCounts::from_raw(negative_implicit_inline_tracks, explicit_inline_tracks, positive_implicit_inline_tracks);
let row_counts =
TrackCounts::from_raw(negative_implicit_block_tracks, explicit_block_tracks, positive_implicit_block_tracks);
(column_counts, row_counts)
}
/// Iterate over children, producing an estimate of the min and max grid *lines* along with the span of each item
///
/// Min and max grid lines are returned in origin-zero coordinates)
/// The span is measured in tracks spanned
fn get_known_child_positions<'a, S: GridItemStyle + 'a>(
children_iter: impl Iterator<Item = S>,
explicit_col_count: u16,
explicit_row_count: u16,
) -> (OriginZeroLine, OriginZeroLine, u16, OriginZeroLine, OriginZeroLine, u16) {
let (mut col_min, mut col_max, mut col_max_span) = (OriginZeroLine(0), OriginZeroLine(0), 0);
let (mut row_min, mut row_max, mut row_max_span) = (OriginZeroLine(0), OriginZeroLine(0), 0);
children_iter.for_each(|child_style| {
// Note: that the children reference the lines in between (and around) the tracks not tracks themselves,
// and thus we must subtract 1 to get an accurate estimate of the number of tracks
let (child_col_min, child_col_max, child_col_span) =
child_min_line_max_line_span(child_style.grid_column(), explicit_col_count);
let (child_row_min, child_row_max, child_row_span) =
child_min_line_max_line_span(child_style.grid_row(), explicit_row_count);
col_min = min(col_min, child_col_min);
col_max = max(col_max, child_col_max);
col_max_span = max(col_max_span, child_col_span);
row_min = min(row_min, child_row_min);
row_max = max(row_max, child_row_max);
row_max_span = max(row_max_span, child_row_span);
});
(col_min, col_max, col_max_span, row_min, row_max, row_max_span)
}
/// Helper function for `compute_grid_size_estimate`
/// Produces a conservative estimate of the greatest and smallest grid lines used by a single grid item
///
/// Values are returned in origin-zero coordinates
#[inline]
fn child_min_line_max_line_span(
line: Line<GridPlacement>,
explicit_track_count: u16,
) -> (OriginZeroLine, OriginZeroLine, u16) {
use GenericGridPlacement::*;
// 8.3.1. Grid Placement Conflict Handling
// A. If the placement for a grid item contains two lines, and the start line is further end-ward than the end line, swap the two lines.
// B. If the start line is equal to the end line, remove the end line.
// C. If the placement contains two spans, remove the one contributed by the end grid-placement property.
// D. If the placement contains only a span for a named line, replace it with a span of 1.
// Convert line into origin-zero coordinates before attempting to analyze
let oz_line = line.into_origin_zero(explicit_track_count);
let min = match (oz_line.start, oz_line.end) {
// Both tracks specified
(Line(track1), Line(track2)) => {
// See rules A and B above
if track1 == track2 {
track1
} else {
min(track1, track2)
}
}
// Start track specified
(Line(track), Auto) => track,
(Line(track), Span(_)) => track,
// End track specified
(Auto, Line(track)) => track,
(Span(span), Line(track)) => track - span,
// Only spans or autos
// We ignore spans here by returning 0 which never effect the estimate as these are accounted for separately
(Auto | Span(_), Auto | Span(_)) => OriginZeroLine(0),
};
let max = match (oz_line.start, oz_line.end) {
// Both tracks specified
(Line(track1), Line(track2)) => {
// See rules A and B above
if track1 == track2 {
track1 + 1
} else {
max(track1, track2)
}
}
// Start track specified
(Line(track), Auto) => track + 1,
(Line(track), Span(span)) => track + span,
// End track specified
(Auto, Line(track)) => track,
(Span(_), Line(track)) => track,
// Only spans or autos
// We ignore spans here by returning 0 which never effect the estimate as these are accounted for separately
(Auto | Span(_), Auto | Span(_)) => OriginZeroLine(0),
};
// Calculate span only for indefinitely placed items as we don't need for other items (whose required space will
// be taken into account by min and max)
let span = match (line.start, line.end) {
(Auto | Span(_), Auto | Span(_)) => line.indefinite_span(),
_ => 1,
};
(min, max, span)
}
#[allow(clippy::bool_assert_comparison)]
#[cfg(test)]
mod tests {
mod test_child_min_max_line {
use super::super::child_min_line_max_line_span;
use super::super::OriginZeroLine;
use crate::geometry::Line;
use crate::style_helpers::*;
#[test]
fn child_min_max_line_auto() {
let (min_col, max_col, span) = child_min_line_max_line_span(Line { start: line(5), end: span(6) }, 6);
assert_eq!(min_col, OriginZeroLine(4));
assert_eq!(max_col, OriginZeroLine(10));
assert_eq!(span, 1);
}
#[test]
fn child_min_max_line_negative_track() {
let (min_col, max_col, span) = child_min_line_max_line_span(Line { start: line(-5), end: span(3) }, 6);
assert_eq!(min_col, OriginZeroLine(2));
assert_eq!(max_col, OriginZeroLine(5));
assert_eq!(span, 1);
}
}
mod test_initial_grid_sizing {
use super::super::compute_grid_size_estimate;
use crate::compute::grid::util::test_helpers::*;
use crate::style_helpers::*;
#[test]
fn explicit_grid_sizing_with_children() {
let explicit_col_count = 6;
let explicit_row_count = 8;
let child_styles = vec![
(line(1), span(2), line(2), auto()).into_grid_child(),
(line(-4), auto(), line(-2), auto()).into_grid_child(),
];
let (inline, block) =
compute_grid_size_estimate(explicit_col_count, explicit_row_count, child_styles.iter());
assert_eq!(inline.negative_implicit, 0);
assert_eq!(inline.explicit, explicit_col_count);
assert_eq!(inline.positive_implicit, 0);
assert_eq!(block.negative_implicit, 0);
assert_eq!(block.explicit, explicit_row_count);
assert_eq!(block.positive_implicit, 0);
}
#[test]
fn negative_implicit_grid_sizing() {
let explicit_col_count = 4;
let explicit_row_count = 4;
let child_styles = vec![
(line(-6), span(2), line(-8), auto()).into_grid_child(),
(line(4), auto(), line(3), auto()).into_grid_child(),
];
let (inline, block) =
compute_grid_size_estimate(explicit_col_count, explicit_row_count, child_styles.iter());
assert_eq!(inline.negative_implicit, 1);
assert_eq!(inline.explicit, explicit_col_count);
assert_eq!(inline.positive_implicit, 0);
assert_eq!(block.negative_implicit, 3);
assert_eq!(block.explicit, explicit_row_count);
assert_eq!(block.positive_implicit, 0);
}
}
}

721
vendor/taffy/src/compute/grid/mod.rs vendored Normal file
View File

@@ -0,0 +1,721 @@
//! This module is a partial implementation of the CSS Grid Level 1 specification
//! <https://www.w3.org/TR/css-grid-1>
use core::borrow::Borrow;
use crate::geometry::{AbsoluteAxis, AbstractAxis, InBothAbsAxis};
use crate::geometry::{Line, Point, Rect, Size};
use crate::style::{AlignItems, AlignSelf, AvailableSpace, Overflow, Position};
use crate::tree::{Layout, LayoutInput, LayoutOutput, LayoutPartialTreeExt, NodeId, RunMode, SizingMode};
use crate::util::debug::debug_log;
use crate::util::sys::{f32_max, GridTrackVec, Vec};
use crate::util::MaybeMath;
use crate::util::{MaybeResolve, ResolveOrZero};
use crate::{
style_helpers::*, AlignContent, BoxGenerationMode, BoxSizing, CoreStyle, GridContainerStyle, GridItemStyle,
JustifyContent, LayoutGridContainer,
};
use alignment::{align_and_position_item, align_tracks};
use explicit_grid::{compute_explicit_grid_size_in_axis, initialize_grid_tracks};
use implicit_grid::compute_grid_size_estimate;
use placement::place_grid_items;
use track_sizing::{
determine_if_item_crosses_flexible_or_intrinsic_tracks, resolve_item_track_indexes, track_sizing_algorithm,
};
use types::{CellOccupancyMatrix, GridTrack};
#[cfg(feature = "detailed_layout_info")]
use types::{GridItem, GridTrackKind, TrackCounts};
pub(crate) use types::{GridCoordinate, GridLine, OriginZeroLine};
mod alignment;
mod explicit_grid;
mod implicit_grid;
mod placement;
mod track_sizing;
mod types;
mod util;
/// Grid layout algorithm
/// This consists of a few phases:
/// - Resolving the explicit grid
/// - Placing items (which also resolves the implicit grid)
/// - Track (row/column) sizing
/// - Alignment & Final item placement
pub fn compute_grid_layout(tree: &mut impl LayoutGridContainer, node: NodeId, inputs: LayoutInput) -> LayoutOutput {
let LayoutInput { known_dimensions, parent_size, available_space, run_mode, .. } = inputs;
let style = tree.get_grid_container_style(node);
// 1. Compute "available grid space"
// https://www.w3.org/TR/css-grid-1/#available-grid-space
let aspect_ratio = style.aspect_ratio();
let padding = style.padding().resolve_or_zero(parent_size.width);
let border = style.border().resolve_or_zero(parent_size.width);
let padding_border = padding + border;
let padding_border_size = padding_border.sum_axes();
let box_sizing_adjustment =
if style.box_sizing() == BoxSizing::ContentBox { padding_border_size } else { Size::ZERO };
let min_size = style
.min_size()
.maybe_resolve(parent_size)
.maybe_apply_aspect_ratio(aspect_ratio)
.maybe_add(box_sizing_adjustment);
let max_size = style
.max_size()
.maybe_resolve(parent_size)
.maybe_apply_aspect_ratio(aspect_ratio)
.maybe_add(box_sizing_adjustment);
let preferred_size = if inputs.sizing_mode == SizingMode::InherentSize {
style
.size()
.maybe_resolve(parent_size)
.maybe_apply_aspect_ratio(style.aspect_ratio())
.maybe_add(box_sizing_adjustment)
} else {
Size::NONE
};
// Scrollbar gutters are reserved when the `overflow` property is set to `Overflow::Scroll`.
// However, the axis are switched (transposed) because a node that scrolls vertically needs
// *horizontal* space to be reserved for a scrollbar
let scrollbar_gutter = style.overflow().transpose().map(|overflow| match overflow {
Overflow::Scroll => style.scrollbar_width(),
_ => 0.0,
});
// TODO: make side configurable based on the `direction` property
let mut content_box_inset = padding_border;
content_box_inset.right += scrollbar_gutter.x;
content_box_inset.bottom += scrollbar_gutter.y;
let align_content = style.align_content().unwrap_or(AlignContent::Stretch);
let justify_content = style.justify_content().unwrap_or(JustifyContent::Stretch);
let align_items = style.align_items();
let justify_items = style.justify_items();
// Note: we avoid accessing the grid rows/columns methods more than once as this can
// cause an expensive-ish computation
let grid_template_columms = style.grid_template_columns();
let grid_template_rows = style.grid_template_rows();
let grid_auto_columms = style.grid_auto_columns();
let grid_auto_rows = style.grid_auto_rows();
let constrained_available_space = known_dimensions
.or(preferred_size)
.map(|size| size.map(AvailableSpace::Definite))
.unwrap_or(available_space)
.maybe_clamp(min_size, max_size)
.maybe_max(padding_border_size);
let available_grid_space = Size {
width: constrained_available_space
.width
.map_definite_value(|space| space - content_box_inset.horizontal_axis_sum()),
height: constrained_available_space
.height
.map_definite_value(|space| space - content_box_inset.vertical_axis_sum()),
};
let outer_node_size =
known_dimensions.or(preferred_size).maybe_clamp(min_size, max_size).maybe_max(padding_border_size);
let mut inner_node_size = Size {
width: outer_node_size.width.map(|space| space - content_box_inset.horizontal_axis_sum()),
height: outer_node_size.height.map(|space| space - content_box_inset.vertical_axis_sum()),
};
debug_log!("parent_size", dbg:parent_size);
debug_log!("outer_node_size", dbg:outer_node_size);
debug_log!("inner_node_size", dbg:inner_node_size);
if let (RunMode::ComputeSize, Some(width), Some(height)) = (run_mode, outer_node_size.width, outer_node_size.height)
{
return LayoutOutput::from_outer_size(Size { width, height });
}
let get_child_styles_iter =
|node| tree.child_ids(node).map(|child_node: NodeId| tree.get_grid_child_style(child_node));
let child_styles_iter = get_child_styles_iter(node);
// 2. Resolve the explicit grid
// This is very similar to the inner_node_size except if the inner_node_size is not definite but the node
// has a min- or max- size style then that will be used in it's place.
let auto_fit_container_size = outer_node_size
.or(max_size)
.or(min_size)
.maybe_clamp(min_size, max_size)
.maybe_max(padding_border_size)
.maybe_sub(content_box_inset.sum_axes());
// Exactly compute the number of rows and columns in the explicit grid.
let explicit_col_count = compute_explicit_grid_size_in_axis(
&style,
grid_template_columms.borrow(),
auto_fit_container_size,
AbsoluteAxis::Horizontal,
);
let explicit_row_count = compute_explicit_grid_size_in_axis(
&style,
grid_template_rows.borrow(),
auto_fit_container_size,
AbsoluteAxis::Vertical,
);
// 3. Implicit Grid: Estimate Track Counts
// Estimate the number of rows and columns in the implicit grid (= the entire grid)
// This is necessary as part of placement. Doing it early here is a perf optimisation to reduce allocations.
let (est_col_counts, est_row_counts) =
compute_grid_size_estimate(explicit_col_count, explicit_row_count, child_styles_iter);
// 4. Grid Item Placement
// Match items (children) to a definite grid position (row start/end and column start/end position)
let mut items = Vec::with_capacity(tree.child_count(node));
let mut cell_occupancy_matrix = CellOccupancyMatrix::with_track_counts(est_col_counts, est_row_counts);
let in_flow_children_iter = || {
tree.child_ids(node)
.enumerate()
.map(|(index, child_node)| (index, child_node, tree.get_grid_child_style(child_node)))
.filter(|(_, _, style)| {
style.box_generation_mode() != BoxGenerationMode::None && style.position() != Position::Absolute
})
};
place_grid_items(
&mut cell_occupancy_matrix,
&mut items,
in_flow_children_iter,
style.grid_auto_flow(),
align_items.unwrap_or(AlignItems::Stretch),
justify_items.unwrap_or(AlignItems::Stretch),
);
// Extract track counts from previous step (auto-placement can expand the number of tracks)
let final_col_counts = *cell_occupancy_matrix.track_counts(AbsoluteAxis::Horizontal);
let final_row_counts = *cell_occupancy_matrix.track_counts(AbsoluteAxis::Vertical);
// 5. Initialize Tracks
// Initialize (explicit and implicit) grid tracks (and gutters)
// This resolves the min and max track sizing functions for all tracks and gutters
let mut columns = GridTrackVec::new();
let mut rows = GridTrackVec::new();
initialize_grid_tracks(
&mut columns,
final_col_counts,
grid_template_columms.borrow(),
grid_auto_columms.borrow(),
style.gap().width,
|column_index| cell_occupancy_matrix.column_is_occupied(column_index),
);
initialize_grid_tracks(
&mut rows,
final_row_counts,
grid_template_rows.borrow(),
grid_auto_rows.borrow(),
style.gap().height,
|row_index| cell_occupancy_matrix.row_is_occupied(row_index),
);
drop(grid_template_rows);
drop(grid_template_columms);
drop(grid_auto_rows);
drop(grid_auto_columms);
drop(style);
// 6. Track Sizing
// Convert grid placements in origin-zero coordinates to indexes into the GridTrack (rows and columns) vectors
// This computation is relatively trivial, but it requires the final number of negative (implicit) tracks in
// each axis, and doing it up-front here means we don't have to keep repeating that calculation
resolve_item_track_indexes(&mut items, final_col_counts, final_row_counts);
// For each item, and in each axis, determine whether the item crosses any flexible (fr) tracks
// Record this as a boolean (per-axis) on each item for later use in the track-sizing algorithm
determine_if_item_crosses_flexible_or_intrinsic_tracks(&mut items, &columns, &rows);
// Determine if the grid has any baseline aligned items
let has_baseline_aligned_item = items.iter().any(|item| item.align_self == AlignSelf::Baseline);
// Run track sizing algorithm for Inline axis
track_sizing_algorithm(
tree,
AbstractAxis::Inline,
min_size.get(AbstractAxis::Inline),
max_size.get(AbstractAxis::Inline),
justify_content,
align_content,
available_grid_space,
inner_node_size,
&mut columns,
&mut rows,
&mut items,
|track: &GridTrack, parent_size: Option<f32>| track.max_track_sizing_function.definite_value(parent_size),
has_baseline_aligned_item,
);
let initial_column_sum = columns.iter().map(|track| track.base_size).sum::<f32>();
inner_node_size.width = inner_node_size.width.or_else(|| initial_column_sum.into());
items.iter_mut().for_each(|item| item.available_space_cache = None);
// Run track sizing algorithm for Block axis
track_sizing_algorithm(
tree,
AbstractAxis::Block,
min_size.get(AbstractAxis::Block),
max_size.get(AbstractAxis::Block),
align_content,
justify_content,
available_grid_space,
inner_node_size,
&mut rows,
&mut columns,
&mut items,
|track: &GridTrack, _| Some(track.base_size),
false, // TODO: Support baseline alignment in the vertical axis
);
let initial_row_sum = rows.iter().map(|track| track.base_size).sum::<f32>();
inner_node_size.height = inner_node_size.height.or_else(|| initial_row_sum.into());
debug_log!("initial_column_sum", dbg:initial_column_sum);
debug_log!(dbg: columns.iter().map(|track| track.base_size).collect::<Vec<_>>());
debug_log!("initial_row_sum", dbg:initial_row_sum);
debug_log!(dbg: rows.iter().map(|track| track.base_size).collect::<Vec<_>>());
// 6. Compute container size
let resolved_style_size = known_dimensions.or(preferred_size);
let container_border_box = Size {
width: resolved_style_size
.get(AbstractAxis::Inline)
.unwrap_or_else(|| initial_column_sum + content_box_inset.horizontal_axis_sum())
.maybe_clamp(min_size.width, max_size.width)
.max(padding_border_size.width),
height: resolved_style_size
.get(AbstractAxis::Block)
.unwrap_or_else(|| initial_row_sum + content_box_inset.vertical_axis_sum())
.maybe_clamp(min_size.height, max_size.height)
.max(padding_border_size.height),
};
let container_content_box = Size {
width: f32_max(0.0, container_border_box.width - content_box_inset.horizontal_axis_sum()),
height: f32_max(0.0, container_border_box.height - content_box_inset.vertical_axis_sum()),
};
// If only the container's size has been requested
if run_mode == RunMode::ComputeSize {
return LayoutOutput::from_outer_size(container_border_box);
}
// 7. Resolve percentage track base sizes
// In the case of an indefinitely sized container these resolve to zero during the "Initialise Tracks" step
// and therefore need to be re-resolved here based on the content-sized content box of the container
if !available_grid_space.width.is_definite() {
for column in &mut columns {
let min: Option<f32> =
column.min_track_sizing_function.resolved_percentage_size(container_content_box.width);
let max: Option<f32> =
column.max_track_sizing_function.resolved_percentage_size(container_content_box.width);
column.base_size = column.base_size.maybe_clamp(min, max);
}
}
if !available_grid_space.height.is_definite() {
for row in &mut rows {
let min: Option<f32> = row.min_track_sizing_function.resolved_percentage_size(container_content_box.height);
let max: Option<f32> = row.max_track_sizing_function.resolved_percentage_size(container_content_box.height);
row.base_size = row.base_size.maybe_clamp(min, max);
}
}
// Column sizing must be re-run (once) if:
// - The grid container's width was initially indefinite and there are any columns with percentage track sizing functions
// - Any grid item crossing an intrinsically sized track's min content contribution width has changed
// TODO: Only rerun sizing for tracks that actually require it rather than for all tracks if any need it.
let mut rerun_column_sizing;
let has_percentage_column = columns.iter().any(|track| track.uses_percentage());
let parent_width_indefinite = !available_space.width.is_definite();
rerun_column_sizing = parent_width_indefinite && has_percentage_column;
if !rerun_column_sizing {
let min_content_contribution_changed =
items.iter_mut().filter(|item| item.crosses_intrinsic_column).any(|item| {
let available_space = item.available_space(
AbstractAxis::Inline,
&rows,
inner_node_size.height,
|track: &GridTrack, _| Some(track.base_size),
);
let new_min_content_contribution =
item.min_content_contribution(AbstractAxis::Inline, tree, available_space, inner_node_size);
let has_changed = Some(new_min_content_contribution) != item.min_content_contribution_cache.width;
item.available_space_cache = Some(available_space);
item.min_content_contribution_cache.width = Some(new_min_content_contribution);
item.max_content_contribution_cache.width = None;
item.minimum_contribution_cache.width = None;
has_changed
});
rerun_column_sizing = min_content_contribution_changed;
} else {
// Clear intrisic width caches
items.iter_mut().for_each(|item| {
item.available_space_cache = None;
item.min_content_contribution_cache.width = None;
item.max_content_contribution_cache.width = None;
item.minimum_contribution_cache.width = None;
});
}
if rerun_column_sizing {
// Re-run track sizing algorithm for Inline axis
track_sizing_algorithm(
tree,
AbstractAxis::Inline,
min_size.get(AbstractAxis::Inline),
max_size.get(AbstractAxis::Inline),
justify_content,
align_content,
available_grid_space,
inner_node_size,
&mut columns,
&mut rows,
&mut items,
|track: &GridTrack, _| Some(track.base_size),
has_baseline_aligned_item,
);
// Row sizing must be re-run (once) if:
// - The grid container's height was initially indefinite and there are any rows with percentage track sizing functions
// - Any grid item crossing an intrinsically sized track's min content contribution height has changed
// TODO: Only rerun sizing for tracks that actually require it rather than for all tracks if any need it.
let mut rerun_row_sizing;
let has_percentage_row = rows.iter().any(|track| track.uses_percentage());
let parent_height_indefinite = !available_space.height.is_definite();
rerun_row_sizing = parent_height_indefinite && has_percentage_row;
if !rerun_row_sizing {
let min_content_contribution_changed =
items.iter_mut().filter(|item| item.crosses_intrinsic_column).any(|item| {
let available_space = item.available_space(
AbstractAxis::Block,
&columns,
inner_node_size.width,
|track: &GridTrack, _| Some(track.base_size),
);
let new_min_content_contribution =
item.min_content_contribution(AbstractAxis::Block, tree, available_space, inner_node_size);
let has_changed = Some(new_min_content_contribution) != item.min_content_contribution_cache.height;
item.available_space_cache = Some(available_space);
item.min_content_contribution_cache.height = Some(new_min_content_contribution);
item.max_content_contribution_cache.height = None;
item.minimum_contribution_cache.height = None;
has_changed
});
rerun_row_sizing = min_content_contribution_changed;
} else {
items.iter_mut().for_each(|item| {
// Clear intrisic height caches
item.available_space_cache = None;
item.min_content_contribution_cache.height = None;
item.max_content_contribution_cache.height = None;
item.minimum_contribution_cache.height = None;
});
}
if rerun_row_sizing {
// Re-run track sizing algorithm for Block axis
track_sizing_algorithm(
tree,
AbstractAxis::Block,
min_size.get(AbstractAxis::Block),
max_size.get(AbstractAxis::Block),
align_content,
justify_content,
available_grid_space,
inner_node_size,
&mut rows,
&mut columns,
&mut items,
|track: &GridTrack, _| Some(track.base_size),
false, // TODO: Support baseline alignment in the vertical axis
);
}
}
// 8. Track Alignment
// Align columns
align_tracks(
container_content_box.get(AbstractAxis::Inline),
Line { start: padding.left, end: padding.right },
Line { start: border.left, end: border.right },
&mut columns,
justify_content,
);
// Align rows
align_tracks(
container_content_box.get(AbstractAxis::Block),
Line { start: padding.top, end: padding.bottom },
Line { start: border.top, end: border.bottom },
&mut rows,
align_content,
);
// 9. Size, Align, and Position Grid Items
#[cfg_attr(not(feature = "content_size"), allow(unused_mut))]
let mut item_content_size_contribution = Size::ZERO;
// Sort items back into original order to allow them to be matched up with styles
items.sort_by_key(|item| item.source_order);
let container_alignment_styles = InBothAbsAxis { horizontal: justify_items, vertical: align_items };
// Position in-flow children (stored in items vector)
for (index, item) in items.iter_mut().enumerate() {
let grid_area = Rect {
top: rows[item.row_indexes.start as usize + 1].offset,
bottom: rows[item.row_indexes.end as usize].offset,
left: columns[item.column_indexes.start as usize + 1].offset,
right: columns[item.column_indexes.end as usize].offset,
};
#[cfg_attr(not(feature = "content_size"), allow(unused_variables))]
let (content_size_contribution, y_position, height) = align_and_position_item(
tree,
item.node,
index as u32,
grid_area,
container_alignment_styles,
item.baseline_shim,
);
item.y_position = y_position;
item.height = height;
#[cfg(feature = "content_size")]
{
item_content_size_contribution = item_content_size_contribution.f32_max(content_size_contribution);
}
}
// Position hidden and absolutely positioned children
let mut order = items.len() as u32;
(0..tree.child_count(node)).for_each(|index| {
let child = tree.get_child_id(node, index);
let child_style = tree.get_grid_child_style(child);
// Position hidden child
if child_style.box_generation_mode() == BoxGenerationMode::None {
drop(child_style);
tree.set_unrounded_layout(child, &Layout::with_order(order));
tree.perform_child_layout(
child,
Size::NONE,
Size::NONE,
Size::MAX_CONTENT,
SizingMode::InherentSize,
Line::FALSE,
);
order += 1;
return;
}
// Position absolutely positioned child
if child_style.position() == Position::Absolute {
// Convert grid-col-{start/end} into Option's of indexes into the columns vector
// The Option is None if the style property is Auto and an unresolvable Span
let maybe_col_indexes = child_style
.grid_column()
.into_origin_zero(final_col_counts.explicit)
.resolve_absolutely_positioned_grid_tracks()
.map(|maybe_grid_line| {
maybe_grid_line.map(|line: OriginZeroLine| line.into_track_vec_index(final_col_counts))
});
// Convert grid-row-{start/end} into Option's of indexes into the row vector
// The Option is None if the style property is Auto and an unresolvable Span
let maybe_row_indexes = child_style
.grid_row()
.into_origin_zero(final_row_counts.explicit)
.resolve_absolutely_positioned_grid_tracks()
.map(|maybe_grid_line| {
maybe_grid_line.map(|line: OriginZeroLine| line.into_track_vec_index(final_row_counts))
});
let grid_area = Rect {
top: maybe_row_indexes.start.map(|index| rows[index].offset).unwrap_or(border.top),
bottom: maybe_row_indexes
.end
.map(|index| rows[index].offset)
.unwrap_or(container_border_box.height - border.bottom - scrollbar_gutter.y),
left: maybe_col_indexes.start.map(|index| columns[index].offset).unwrap_or(border.left),
right: maybe_col_indexes
.end
.map(|index| columns[index].offset)
.unwrap_or(container_border_box.width - border.right - scrollbar_gutter.x),
};
drop(child_style);
// TODO: Baseline alignment support for absolutely positioned items (should check if is actuallty specified)
#[cfg_attr(not(feature = "content_size"), allow(unused_variables))]
let (content_size_contribution, _, _) =
align_and_position_item(tree, child, order, grid_area, container_alignment_styles, 0.0);
#[cfg(feature = "content_size")]
{
item_content_size_contribution = item_content_size_contribution.f32_max(content_size_contribution);
}
order += 1;
}
});
// Set detailed grid information
#[cfg(feature = "detailed_layout_info")]
tree.set_detailed_grid_info(
node,
DetailedGridInfo {
rows: DetailedGridTracksInfo::from_grid_tracks_and_track_count(final_row_counts, rows),
columns: DetailedGridTracksInfo::from_grid_tracks_and_track_count(final_col_counts, columns),
items: items.iter().map(DetailedGridItemsInfo::from_grid_item).collect(),
},
);
// If there are not items then return just the container size (no baseline)
if items.is_empty() {
return LayoutOutput::from_outer_size(container_border_box);
}
// Determine the grid container baseline(s) (currently we only compute the first baseline)
let grid_container_baseline: f32 = {
// Sort items by row start position so that we can iterate items in groups which are in the same row
items.sort_by_key(|item| item.row_indexes.start);
// Get the row index of the first row containing items
let first_row = items[0].row_indexes.start;
// Create a slice of all of the items start in this row (taking advantage of the fact that we have just sorted the array)
let first_row_items = &items[0..].split(|item| item.row_indexes.start != first_row).next().unwrap();
// Check if any items in *this row* are baseline aligned
let row_has_baseline_item = first_row_items.iter().any(|item| item.align_self == AlignSelf::Baseline);
let item = if row_has_baseline_item {
first_row_items.iter().find(|item| item.align_self == AlignSelf::Baseline).unwrap()
} else {
&first_row_items[0]
};
item.y_position + item.baseline.unwrap_or(item.height)
};
LayoutOutput::from_sizes_and_baselines(
container_border_box,
item_content_size_contribution,
Point { x: None, y: Some(grid_container_baseline) },
)
}
/// Information from the computation of grid
#[derive(Debug, Clone, PartialEq)]
#[cfg(feature = "detailed_layout_info")]
pub struct DetailedGridInfo {
/// <https://drafts.csswg.org/css-grid-1/#grid-row>
pub rows: DetailedGridTracksInfo,
/// <https://drafts.csswg.org/css-grid-1/#grid-column>
pub columns: DetailedGridTracksInfo,
/// <https://drafts.csswg.org/css-grid-1/#grid-items>
pub items: Vec<DetailedGridItemsInfo>,
}
/// Information from the computation of grids tracks
#[derive(Debug, Clone, PartialEq)]
#[cfg(feature = "detailed_layout_info")]
pub struct DetailedGridTracksInfo {
/// Number of leading implicit grid tracks
pub negative_implicit_tracks: u16,
/// Number of explicit grid tracks
pub explicit_tracks: u16,
/// Number of trailing implicit grid tracks
pub positive_implicit_tracks: u16,
/// Gutters between tracks
pub gutters: Vec<f32>,
/// The used size of the tracks
pub sizes: Vec<f32>,
}
#[cfg(feature = "detailed_layout_info")]
impl DetailedGridTracksInfo {
/// Get the base_size of [`GridTrack`] with a kind [`types::GridTrackKind`]
#[inline(always)]
fn grid_track_base_size_of_kind(grid_tracks: &[GridTrack], kind: GridTrackKind) -> Vec<f32> {
grid_tracks
.iter()
.filter_map(|track| match track.kind == kind {
true => Some(track.base_size),
false => None,
})
.collect()
}
/// Get the sizes of the gutters
fn gutters_from_grid_track_layout(grid_tracks: &[GridTrack]) -> Vec<f32> {
DetailedGridTracksInfo::grid_track_base_size_of_kind(grid_tracks, GridTrackKind::Gutter)
}
/// Get the sizes of the tracks
fn sizes_from_grid_track_layout(grid_tracks: &[GridTrack]) -> Vec<f32> {
DetailedGridTracksInfo::grid_track_base_size_of_kind(grid_tracks, GridTrackKind::Track)
}
/// Construct DetailedGridTracksInfo from TrackCounts and GridTracks
fn from_grid_tracks_and_track_count(track_count: TrackCounts, grid_tracks: Vec<GridTrack>) -> Self {
DetailedGridTracksInfo {
negative_implicit_tracks: track_count.negative_implicit,
explicit_tracks: track_count.explicit,
positive_implicit_tracks: track_count.positive_implicit,
gutters: DetailedGridTracksInfo::gutters_from_grid_track_layout(&grid_tracks),
sizes: DetailedGridTracksInfo::sizes_from_grid_track_layout(&grid_tracks),
}
}
}
/// Grid area information from the placement algorithm
///
/// The values is 1-indexed grid line numbers bounding the area.
/// This matches the Chrome and Firefox's format as of 2nd Jan 2024.
#[derive(Debug, Clone, PartialEq)]
#[cfg(feature = "detailed_layout_info")]
pub struct DetailedGridItemsInfo {
/// row-start with 1-indexed grid line numbers
pub row_start: u16,
/// row-end with 1-indexed grid line numbers
pub row_end: u16,
/// column-start with 1-indexed grid line numbers
pub column_start: u16,
/// column-end with 1-indexed grid line numbers
pub column_end: u16,
}
/// Grid area information from the placement algorithm
#[cfg(feature = "detailed_layout_info")]
impl DetailedGridItemsInfo {
/// Construct from GridItems
#[inline(always)]
fn from_grid_item(grid_item: &GridItem) -> Self {
/// Conversion from the indexes of Vec<GridTrack> into 1-indexed grid line numbers. See [`GridItem::row_indexes`] or [`GridItem::column_indexes`]
#[inline(always)]
fn to_one_indexed_grid_line(grid_track_index: u16) -> u16 {
grid_track_index / 2 + 1
}
DetailedGridItemsInfo {
row_start: to_one_indexed_grid_line(grid_item.row_indexes.start),
row_end: to_one_indexed_grid_line(grid_item.row_indexes.end),
column_start: to_one_indexed_grid_line(grid_item.column_indexes.start),
column_end: to_one_indexed_grid_line(grid_item.column_indexes.end),
}
}
}

View File

@@ -0,0 +1,599 @@
//! Implements placing items in the grid and resolving the implicit grid.
//! <https://www.w3.org/TR/css-grid-1/#placement>
use super::types::{CellOccupancyMatrix, CellOccupancyState, GridItem};
use super::OriginZeroLine;
use crate::geometry::Line;
use crate::geometry::{AbsoluteAxis, InBothAbsAxis};
use crate::style::{AlignItems, GridAutoFlow, OriginZeroGridPlacement};
use crate::tree::NodeId;
use crate::util::sys::Vec;
use crate::GridItemStyle;
/// 8.5. Grid Item Placement Algorithm
/// Place items into the grid, generating new rows/column into the implicit grid as required
///
/// [Specification](https://www.w3.org/TR/css-grid-2/#auto-placement-algo)
pub(super) fn place_grid_items<'a, S, ChildIter>(
cell_occupancy_matrix: &mut CellOccupancyMatrix,
items: &mut Vec<GridItem>,
children_iter: impl Fn() -> ChildIter,
grid_auto_flow: GridAutoFlow,
align_items: AlignItems,
justify_items: AlignItems,
) where
S: GridItemStyle + 'a,
ChildIter: Iterator<Item = (usize, NodeId, S)>,
{
let primary_axis = grid_auto_flow.primary_axis();
let secondary_axis = primary_axis.other_axis();
let map_child_style_to_origin_zero_placement = {
let explicit_col_count = cell_occupancy_matrix.track_counts(AbsoluteAxis::Horizontal).explicit;
let explicit_row_count = cell_occupancy_matrix.track_counts(AbsoluteAxis::Vertical).explicit;
move |(index, node, style): (usize, NodeId, S)| -> (_, _, _, S) {
let origin_zero_placement = InBothAbsAxis {
horizontal: style
.grid_column()
.map(|placement| placement.into_origin_zero_placement(explicit_col_count)),
vertical: style.grid_row().map(|placement| placement.into_origin_zero_placement(explicit_row_count)),
};
(index, node, origin_zero_placement, style)
}
};
// 1. Place children with definite positions
let mut idx = 0;
children_iter()
.filter(|(_, _, child_style)| child_style.grid_row().is_definite() && child_style.grid_column().is_definite())
.map(map_child_style_to_origin_zero_placement)
.for_each(|(index, child_node, child_placement, style)| {
idx += 1;
#[cfg(test)]
println!("Definite Item {idx}\n==============");
let (row_span, col_span) = place_definite_grid_item(child_placement, primary_axis);
record_grid_placement(
cell_occupancy_matrix,
items,
child_node,
index,
style,
align_items,
justify_items,
primary_axis,
row_span,
col_span,
CellOccupancyState::DefinitelyPlaced,
);
});
// 2. Place remaining children with definite secondary axis positions
let mut idx = 0;
children_iter()
.filter(|(_, _, child_style)| {
child_style.grid_placement(secondary_axis).is_definite()
&& !child_style.grid_placement(primary_axis).is_definite()
})
.map(map_child_style_to_origin_zero_placement)
.for_each(|(index, child_node, child_placement, style)| {
idx += 1;
#[cfg(test)]
println!("Definite Secondary Item {idx}\n==============");
let (primary_span, secondary_span) =
place_definite_secondary_axis_item(&*cell_occupancy_matrix, child_placement, grid_auto_flow);
record_grid_placement(
cell_occupancy_matrix,
items,
child_node,
index,
style,
align_items,
justify_items,
primary_axis,
primary_span,
secondary_span,
CellOccupancyState::AutoPlaced,
);
});
// 3. Determine the number of columns in the implicit grid
// By the time we get to this point in the execution, this is actually already accounted for:
//
// 3.1 Start with the columns from the explicit grid
// => Handled by grid size estimate which is used to pre-size the GridOccupancyMatrix
//
// 3.2 Among all the items with a definite column position (explicitly positioned items, items positioned in the previous step,
// and items not yet positioned but with a definite column) add columns to the beginning and end of the implicit grid as necessary
// to accommodate those items.
// => Handled by expand_to_fit_range which expands the GridOccupancyMatrix as necessary
// -> Called by mark_area_as
// -> Called by record_grid_placement
//
// 3.3 If the largest column span among all the items without a definite column position is larger than the width of
// the implicit grid, add columns to the end of the implicit grid to accommodate that column span.
// => Handled by grid size estimate which is used to pre-size the GridOccupancyMatrix
// 4. Position the remaining grid items
// (which either have definite position only in the secondary axis or indefinite positions in both axis)
let primary_axis = grid_auto_flow.primary_axis();
let secondary_axis = primary_axis.other_axis();
let primary_neg_tracks = cell_occupancy_matrix.track_counts(primary_axis).negative_implicit as i16;
let secondary_neg_tracks = cell_occupancy_matrix.track_counts(secondary_axis).negative_implicit as i16;
let grid_start_position = (OriginZeroLine(-primary_neg_tracks), OriginZeroLine(-secondary_neg_tracks));
let mut grid_position = grid_start_position;
let mut idx = 0;
children_iter()
.filter(|(_, _, child_style)| !child_style.grid_placement(secondary_axis).is_definite())
.map(map_child_style_to_origin_zero_placement)
.for_each(|(index, child_node, child_placement, style)| {
idx += 1;
#[cfg(test)]
println!("\nAuto Item {idx}\n==============");
// Compute placement
let (primary_span, secondary_span) = place_indefinitely_positioned_item(
&*cell_occupancy_matrix,
child_placement,
grid_auto_flow,
grid_position,
);
// Record item
record_grid_placement(
cell_occupancy_matrix,
items,
child_node,
index,
style,
align_items,
justify_items,
primary_axis,
primary_span,
secondary_span,
CellOccupancyState::AutoPlaced,
);
// If using the "dense" placement algorithm then reset the grid position back to grid_start_position ready for the next item
// Otherwise set it to the position of the current item so that the next item it placed after it.
grid_position = match grid_auto_flow.is_dense() {
true => grid_start_position,
false => (primary_span.end, secondary_span.start),
}
});
}
/// 8.5. Grid Item Placement Algorithm
/// Place a single definitely placed item into the grid
fn place_definite_grid_item(
placement: InBothAbsAxis<Line<OriginZeroGridPlacement>>,
primary_axis: AbsoluteAxis,
) -> (Line<OriginZeroLine>, Line<OriginZeroLine>) {
// Resolve spans to tracks
let primary_span = placement.get(primary_axis).resolve_definite_grid_lines();
let secondary_span = placement.get(primary_axis.other_axis()).resolve_definite_grid_lines();
(primary_span, secondary_span)
}
/// 8.5. Grid Item Placement Algorithm
/// Step 2. Place remaining children with definite secondary axis positions
fn place_definite_secondary_axis_item(
cell_occupancy_matrix: &CellOccupancyMatrix,
placement: InBothAbsAxis<Line<OriginZeroGridPlacement>>,
auto_flow: GridAutoFlow,
) -> (Line<OriginZeroLine>, Line<OriginZeroLine>) {
let primary_axis = auto_flow.primary_axis();
let secondary_axis = primary_axis.other_axis();
let secondary_axis_placement = placement.get(secondary_axis).resolve_definite_grid_lines();
let primary_axis_grid_start_line = cell_occupancy_matrix.track_counts(primary_axis).implicit_start_line();
let starting_position = match auto_flow.is_dense() {
true => primary_axis_grid_start_line,
false => cell_occupancy_matrix
.last_of_type(primary_axis, secondary_axis_placement.start, CellOccupancyState::AutoPlaced)
.unwrap_or(primary_axis_grid_start_line),
};
let mut position: OriginZeroLine = starting_position;
loop {
let primary_axis_placement = placement.get(primary_axis).resolve_indefinite_grid_tracks(position);
let does_fit = cell_occupancy_matrix.line_area_is_unoccupied(
primary_axis,
primary_axis_placement,
secondary_axis_placement,
);
if does_fit {
return (primary_axis_placement, secondary_axis_placement);
} else {
position += 1;
}
}
}
/// 8.5. Grid Item Placement Algorithm
/// Step 4. Position the remaining grid items.
fn place_indefinitely_positioned_item(
cell_occupancy_matrix: &CellOccupancyMatrix,
placement: InBothAbsAxis<Line<OriginZeroGridPlacement>>,
auto_flow: GridAutoFlow,
grid_position: (OriginZeroLine, OriginZeroLine),
) -> (Line<OriginZeroLine>, Line<OriginZeroLine>) {
let primary_axis = auto_flow.primary_axis();
let primary_placement_style = placement.get(primary_axis);
let secondary_placement_style = placement.get(primary_axis.other_axis());
let secondary_span = secondary_placement_style.indefinite_span();
let has_definite_primary_axis_position = primary_placement_style.is_definite();
let primary_axis_grid_start_line = cell_occupancy_matrix.track_counts(primary_axis).implicit_start_line();
let primary_axis_grid_end_line = cell_occupancy_matrix.track_counts(primary_axis).implicit_end_line();
let secondary_axis_grid_start_line =
cell_occupancy_matrix.track_counts(primary_axis.other_axis()).implicit_start_line();
let line_area_is_occupied = |primary_span, secondary_span| {
!cell_occupancy_matrix.line_area_is_unoccupied(primary_axis, primary_span, secondary_span)
};
let (mut primary_idx, mut secondary_idx) = grid_position;
if has_definite_primary_axis_position {
let definite_primary_placement = primary_placement_style.resolve_definite_grid_lines();
let defined_primary_idx = definite_primary_placement.start;
// Compute starting position for search
if defined_primary_idx < primary_idx && secondary_idx != secondary_axis_grid_start_line {
secondary_idx = secondary_axis_grid_start_line;
primary_idx = defined_primary_idx + 1;
} else {
primary_idx = defined_primary_idx;
}
// Item has fixed primary axis position: so we simply increment the secondary axis position
// until we find a space that the item fits in
loop {
let primary_span = Line { start: primary_idx, end: primary_idx + definite_primary_placement.span() };
let secondary_span = Line { start: secondary_idx, end: secondary_idx + secondary_span };
// If area is occupied, increment the index and try again
if line_area_is_occupied(primary_span, secondary_span) {
secondary_idx += 1;
continue;
}
// Once we find a free space, return that position
return (primary_span, secondary_span);
}
} else {
let primary_span = primary_placement_style.indefinite_span();
// Item does not have any fixed axis, so we search along the primary axis until we hit the end of the already
// existent tracks, and then we reset the primary axis back to zero and increment the secondary axis index.
// We continue in this vein until we find a space that the item fits in.
loop {
let primary_span = Line { start: primary_idx, end: primary_idx + primary_span };
let secondary_span = Line { start: secondary_idx, end: secondary_idx + secondary_span };
// If the primary index is out of bounds, then increment the secondary index and reset the primary
// index back to the start of the grid
let primary_out_of_bounds = primary_span.end > primary_axis_grid_end_line;
if primary_out_of_bounds {
secondary_idx += 1;
primary_idx = primary_axis_grid_start_line;
continue;
}
// If area is occupied, increment the primary index and try again
if line_area_is_occupied(primary_span, secondary_span) {
primary_idx += 1;
continue;
}
// Once we find a free space that's in bounds, return that position
return (primary_span, secondary_span);
}
}
}
/// Record the grid item in both CellOccupancyMatric and the GridItems list
/// once a definite placement has been determined
#[allow(clippy::too_many_arguments)]
fn record_grid_placement<S: GridItemStyle>(
cell_occupancy_matrix: &mut CellOccupancyMatrix,
items: &mut Vec<GridItem>,
node: NodeId,
index: usize,
style: S,
parent_align_items: AlignItems,
parent_justify_items: AlignItems,
primary_axis: AbsoluteAxis,
primary_span: Line<OriginZeroLine>,
secondary_span: Line<OriginZeroLine>,
placement_type: CellOccupancyState,
) {
#[cfg(test)]
println!("BEFORE placement:");
#[cfg(test)]
println!("{cell_occupancy_matrix:?}");
// Mark area of grid as occupied
cell_occupancy_matrix.mark_area_as(primary_axis, primary_span, secondary_span, placement_type);
// Create grid item
let (col_span, row_span) = match primary_axis {
AbsoluteAxis::Horizontal => (primary_span, secondary_span),
AbsoluteAxis::Vertical => (secondary_span, primary_span),
};
items.push(GridItem::new_with_placement_style_and_order(
node,
col_span,
row_span,
style,
parent_align_items,
parent_justify_items,
index as u16,
));
#[cfg(test)]
println!("AFTER placement:");
#[cfg(test)]
println!("{cell_occupancy_matrix:?}");
#[cfg(test)]
println!("\n");
}
#[cfg(test)]
mod tests {
mod test_placement_algorithm {
use crate::compute::grid::implicit_grid::compute_grid_size_estimate;
use crate::compute::grid::types::TrackCounts;
use crate::compute::grid::util::*;
use crate::compute::grid::CellOccupancyMatrix;
use crate::prelude::*;
use crate::style::GridAutoFlow;
use super::super::place_grid_items;
type ExpectedPlacement = (i16, i16, i16, i16);
fn placement_test_runner(
explicit_col_count: u16,
explicit_row_count: u16,
children: Vec<(usize, Style, ExpectedPlacement)>,
expected_col_counts: TrackCounts,
expected_row_counts: TrackCounts,
flow: GridAutoFlow,
) {
// Setup test
let children_iter = || children.iter().map(|(index, style, _)| (*index, NodeId::from(*index), style));
let child_styles_iter = children.iter().map(|(_, style, _)| style);
let estimated_sizes = compute_grid_size_estimate(explicit_col_count, explicit_row_count, child_styles_iter);
let mut items = Vec::new();
let mut cell_occupancy_matrix =
CellOccupancyMatrix::with_track_counts(estimated_sizes.0, estimated_sizes.1);
// Run placement algorithm
place_grid_items(
&mut cell_occupancy_matrix,
&mut items,
children_iter,
flow,
AlignSelf::Start,
AlignSelf::Start,
);
// Assert that each item has been placed in the right location
let mut sorted_children = children.clone();
sorted_children.sort_by_key(|child| child.0);
for (idx, ((id, _style, expected_placement), item)) in sorted_children.iter().zip(items.iter()).enumerate()
{
assert_eq!(item.node, NodeId::from(*id));
let actual_placement = (item.column.start, item.column.end, item.row.start, item.row.end);
assert_eq!(actual_placement, (*expected_placement).into_oz(), "Item {idx} (0-indexed)");
}
// Assert that the correct number of implicit rows have been generated
let actual_row_counts = *cell_occupancy_matrix.track_counts(crate::compute::grid::AbsoluteAxis::Vertical);
assert_eq!(actual_row_counts, expected_row_counts, "row track counts");
let actual_col_counts = *cell_occupancy_matrix.track_counts(crate::compute::grid::AbsoluteAxis::Horizontal);
assert_eq!(actual_col_counts, expected_col_counts, "column track counts");
}
#[test]
fn test_only_fixed_placement() {
let flow = GridAutoFlow::Row;
let explicit_col_count = 2;
let explicit_row_count = 2;
let children = {
vec![
// node, style (grid coords), expected_placement (oz coords)
(1, (line(1), auto(), line(1), auto()).into_grid_child(), (0, 1, 0, 1)),
(2, (line(-4), auto(), line(-3), auto()).into_grid_child(), (-1, 0, 0, 1)),
(3, (line(-3), auto(), line(-4), auto()).into_grid_child(), (0, 1, -1, 0)),
(4, (line(3), span(2), line(5), auto()).into_grid_child(), (2, 4, 4, 5)),
]
};
let expected_cols = TrackCounts { negative_implicit: 1, explicit: 2, positive_implicit: 2 };
let expected_rows = TrackCounts { negative_implicit: 1, explicit: 2, positive_implicit: 3 };
placement_test_runner(explicit_col_count, explicit_row_count, children, expected_cols, expected_rows, flow);
}
#[test]
fn test_placement_spanning_origin() {
let flow = GridAutoFlow::Row;
let explicit_col_count = 2;
let explicit_row_count = 2;
let children = {
vec![
// node, style (grid coords), expected_placement (oz coords)
(1, (line(-1), line(-1), line(-1), line(-1)).into_grid_child(), (2, 3, 2, 3)),
(2, (line(-1), span(2), line(-1), span(2)).into_grid_child(), (2, 4, 2, 4)),
(3, (line(-4), line(-4), line(-4), line(-4)).into_grid_child(), (-1, 0, -1, 0)),
(4, (line(-4), span(2), line(-4), span(2)).into_grid_child(), (-1, 1, -1, 1)),
]
};
let expected_cols = TrackCounts { negative_implicit: 1, explicit: 2, positive_implicit: 2 };
let expected_rows = TrackCounts { negative_implicit: 1, explicit: 2, positive_implicit: 2 };
placement_test_runner(explicit_col_count, explicit_row_count, children, expected_cols, expected_rows, flow);
}
#[test]
fn test_only_auto_placement_row_flow() {
let flow = GridAutoFlow::Row;
let explicit_col_count = 2;
let explicit_row_count = 2;
let children = {
let auto_child = (auto(), auto(), auto(), auto()).into_grid_child();
vec![
// output order, node, style (grid coords), expected_placement (oz coords)
(1, auto_child.clone(), (0, 1, 0, 1)),
(2, auto_child.clone(), (1, 2, 0, 1)),
(3, auto_child.clone(), (0, 1, 1, 2)),
(4, auto_child.clone(), (1, 2, 1, 2)),
(5, auto_child.clone(), (0, 1, 2, 3)),
(6, auto_child.clone(), (1, 2, 2, 3)),
(7, auto_child.clone(), (0, 1, 3, 4)),
(8, auto_child.clone(), (1, 2, 3, 4)),
]
};
let expected_cols = TrackCounts { negative_implicit: 0, explicit: 2, positive_implicit: 0 };
let expected_rows = TrackCounts { negative_implicit: 0, explicit: 2, positive_implicit: 2 };
placement_test_runner(explicit_col_count, explicit_row_count, children, expected_cols, expected_rows, flow);
}
#[test]
fn test_only_auto_placement_column_flow() {
let flow = GridAutoFlow::Column;
let explicit_col_count = 2;
let explicit_row_count = 2;
let children = {
let auto_child = (auto(), auto(), auto(), auto()).into_grid_child();
vec![
// output order, node, style (grid coords), expected_placement (oz coords)
(1, auto_child.clone(), (0, 1, 0, 1)),
(2, auto_child.clone(), (0, 1, 1, 2)),
(3, auto_child.clone(), (1, 2, 0, 1)),
(4, auto_child.clone(), (1, 2, 1, 2)),
(5, auto_child.clone(), (2, 3, 0, 1)),
(6, auto_child.clone(), (2, 3, 1, 2)),
(7, auto_child.clone(), (3, 4, 0, 1)),
(8, auto_child.clone(), (3, 4, 1, 2)),
]
};
let expected_cols = TrackCounts { negative_implicit: 0, explicit: 2, positive_implicit: 2 };
let expected_rows = TrackCounts { negative_implicit: 0, explicit: 2, positive_implicit: 0 };
placement_test_runner(explicit_col_count, explicit_row_count, children, expected_cols, expected_rows, flow);
}
#[test]
fn test_oversized_item() {
let flow = GridAutoFlow::Row;
let explicit_col_count = 2;
let explicit_row_count = 2;
let children = {
vec![
// output order, node, style (grid coords), expected_placement (oz coords)
(1, (span(5), auto(), auto(), auto()).into_grid_child(), (0, 5, 0, 1)),
]
};
let expected_cols = TrackCounts { negative_implicit: 0, explicit: 2, positive_implicit: 3 };
let expected_rows = TrackCounts { negative_implicit: 0, explicit: 2, positive_implicit: 0 };
placement_test_runner(explicit_col_count, explicit_row_count, children, expected_cols, expected_rows, flow);
}
#[test]
fn test_fixed_in_secondary_axis() {
let flow = GridAutoFlow::Row;
let explicit_col_count = 2;
let explicit_row_count = 2;
let children = {
vec![
// output order, node, style (grid coords), expected_placement (oz coords)
(1, (span(2), auto(), line(1), auto()).into_grid_child(), (0, 2, 0, 1)),
(2, (auto(), auto(), line(2), auto()).into_grid_child(), (0, 1, 1, 2)),
(3, (auto(), auto(), line(1), auto()).into_grid_child(), (2, 3, 0, 1)),
(4, (auto(), auto(), line(4), auto()).into_grid_child(), (0, 1, 3, 4)),
]
};
let expected_cols = TrackCounts { negative_implicit: 0, explicit: 2, positive_implicit: 1 };
let expected_rows = TrackCounts { negative_implicit: 0, explicit: 2, positive_implicit: 2 };
placement_test_runner(explicit_col_count, explicit_row_count, children, expected_cols, expected_rows, flow);
}
#[test]
fn test_definite_in_secondary_axis_with_fully_definite_negative() {
let flow = GridAutoFlow::Row;
let explicit_col_count = 2;
let explicit_row_count = 2;
let children = {
vec![
// output order, node, style (grid coords), expected_placement (oz coords)
(2, (auto(), auto(), line(2), auto()).into_grid_child(), (0, 1, 1, 2)),
(1, (line(-4), auto(), line(2), auto()).into_grid_child(), (-1, 0, 1, 2)),
(3, (auto(), auto(), line(1), auto()).into_grid_child(), (-1, 0, 0, 1)),
]
};
let expected_cols = TrackCounts { negative_implicit: 1, explicit: 2, positive_implicit: 0 };
let expected_rows = TrackCounts { negative_implicit: 0, explicit: 2, positive_implicit: 0 };
placement_test_runner(explicit_col_count, explicit_row_count, children, expected_cols, expected_rows, flow);
}
#[test]
fn test_dense_packing_algorithm() {
let flow = GridAutoFlow::RowDense;
let explicit_col_count = 4;
let explicit_row_count = 4;
let children = {
vec![
// output order, node, style (grid coords), expected_placement (oz coords)
(1, (line(2), auto(), line(1), auto()).into_grid_child(), (1, 2, 0, 1)), // Definitely positioned in column 2
(2, (span(2), auto(), auto(), auto()).into_grid_child(), (2, 4, 0, 1)), // Spans 2 columns, so positioned after item 1
(3, (auto(), auto(), auto(), auto()).into_grid_child(), (0, 1, 0, 1)), // Spans 1 column, so should be positioned before item 1
]
};
let expected_cols = TrackCounts { negative_implicit: 0, explicit: 4, positive_implicit: 0 };
let expected_rows = TrackCounts { negative_implicit: 0, explicit: 4, positive_implicit: 0 };
placement_test_runner(explicit_col_count, explicit_row_count, children, expected_cols, expected_rows, flow);
}
#[test]
fn test_sparse_packing_algorithm() {
let flow = GridAutoFlow::Row;
let explicit_col_count = 4;
let explicit_row_count = 4;
let children = {
vec![
// output order, node, style (grid coords), expected_placement (oz coords)
(1, (auto(), span(3), auto(), auto()).into_grid_child(), (0, 3, 0, 1)), // Width 3
(2, (auto(), span(3), auto(), auto()).into_grid_child(), (0, 3, 1, 2)), // Width 3 (wraps to next row)
(3, (auto(), span(1), auto(), auto()).into_grid_child(), (3, 4, 1, 2)), // Width 1 (uses second row as we're already on it)
]
};
let expected_cols = TrackCounts { negative_implicit: 0, explicit: 4, positive_implicit: 0 };
let expected_rows = TrackCounts { negative_implicit: 0, explicit: 4, positive_implicit: 0 };
placement_test_runner(explicit_col_count, explicit_row_count, children, expected_cols, expected_rows, flow);
}
#[test]
fn test_auto_placement_in_negative_tracks() {
let flow = GridAutoFlow::RowDense;
let explicit_col_count = 2;
let explicit_row_count = 2;
let children = {
vec![
// output order, node, style (grid coords), expected_placement (oz coords)
(1, (line(-5), auto(), line(1), auto()).into_grid_child(), (-2, -1, 0, 1)), // Row 1. Definitely positioned in column -2
(2, (auto(), auto(), line(2), auto()).into_grid_child(), (-2, -1, 1, 2)), // Row 2. Auto positioned in column -2
(3, (auto(), auto(), auto(), auto()).into_grid_child(), (-1, 0, 0, 1)), // Row 1. Auto positioned in column -1
]
};
let expected_cols = TrackCounts { negative_implicit: 2, explicit: 2, positive_implicit: 0 };
let expected_rows = TrackCounts { negative_implicit: 0, explicit: 2, positive_implicit: 0 };
placement_test_runner(explicit_col_count, explicit_row_count, children, expected_cols, expected_rows, flow);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,251 @@
//! Contains CellOccupancyMatrix used to track occupied cells during grid placement
use super::TrackCounts;
use crate::compute::grid::OriginZeroLine;
use crate::geometry::AbsoluteAxis;
use crate::geometry::Line;
use crate::util::sys::Vec;
use core::cmp::{max, min};
use core::fmt::Debug;
use core::ops::Range;
use grid::Grid;
/// The occupancy state of a single grid cell
#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
pub(crate) enum CellOccupancyState {
#[default]
/// Indicates that a grid cell is unoccupied
Unoccupied,
/// Indicates that a grid cell is occupied by a definitely placed item
DefinitelyPlaced,
/// Indicates that a grid cell is occupied by an item that was placed by the auto placement algorithm
AutoPlaced,
}
/// A dynamically sized matrix (2d grid) which tracks the occupancy of each grid cell during auto-placement
/// It also keeps tabs on how many tracks there are and which tracks are implicit and which are explicit.
pub(crate) struct CellOccupancyMatrix {
/// The grid of occupancy states
inner: Grid<CellOccupancyState>,
/// The counts of implicit and explicit columns
columns: TrackCounts,
/// The counts of implicit and explicit rows
rows: TrackCounts,
}
/// Debug impl that represents the matrix in a compact 2d text format
impl Debug for CellOccupancyMatrix {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
writeln!(
f,
"Rows: neg_implicit={} explicit={} pos_implicit={}",
self.rows.negative_implicit, self.rows.explicit, self.rows.positive_implicit
)?;
writeln!(
f,
"Cols: neg_implicit={} explicit={} pos_implicit={}",
self.columns.negative_implicit, self.columns.explicit, self.columns.positive_implicit
)?;
writeln!(f, "State:")?;
for row_idx in 0..self.inner.rows() {
for cell in self.inner.iter_row(row_idx) {
let letter = match *cell {
CellOccupancyState::Unoccupied => '_',
CellOccupancyState::DefinitelyPlaced => 'D',
CellOccupancyState::AutoPlaced => 'A',
};
write!(f, "{letter}")?;
}
writeln!(f)?;
}
Ok(())
}
}
impl CellOccupancyMatrix {
/// Create a CellOccupancyMatrix given a set of provisional track counts. The grid can expand as needed to fit more tracks,
/// the provisional track counts represent a best effort attempt to avoid the extra allocations this requires.
pub fn with_track_counts(columns: TrackCounts, rows: TrackCounts) -> Self {
Self { inner: Grid::new(rows.len(), columns.len()), rows, columns }
}
/// Determines whether the specified area fits within the tracks currently represented by the matrix
pub fn is_area_in_range(
&self,
primary_axis: AbsoluteAxis,
primary_range: Range<i16>,
secondary_range: Range<i16>,
) -> bool {
if primary_range.start < 0 || primary_range.end > self.track_counts(primary_axis).len() as i16 {
return false;
}
if secondary_range.start < 0 || secondary_range.end > self.track_counts(primary_axis.other_axis()).len() as i16
{
return false;
}
true
}
/// Expands the grid (potentially in all 4 directions) in order to ensure that the specified range fits within the allocated space
fn expand_to_fit_range(&mut self, row_range: Range<i16>, col_range: Range<i16>) {
// Calculate number of rows and columns missing to accommodate ranges (if any)
let req_negative_rows = min(row_range.start, 0);
let req_positive_rows = max(row_range.end - self.rows.len() as i16, 0);
let req_negative_cols = min(col_range.start, 0);
let req_positive_cols = max(col_range.end - self.columns.len() as i16, 0);
let old_row_count = self.rows.len();
let old_col_count = self.columns.len();
let new_row_count = old_row_count + (req_negative_rows + req_positive_rows) as usize;
let new_col_count = old_col_count + (req_negative_cols + req_positive_cols) as usize;
let mut data = Vec::with_capacity(new_row_count * new_col_count);
// Push new negative rows
for _ in 0..(req_negative_rows as usize * new_col_count) {
data.push(CellOccupancyState::Unoccupied);
}
// Push existing rows
for row in 0..old_row_count {
// Push new negative columns
for _ in 0..req_negative_cols {
data.push(CellOccupancyState::Unoccupied);
}
// Push existing columns
for col in 0..old_col_count {
data.push(*self.inner.get(row, col).unwrap());
}
// Push new positive columns
for _ in 0..req_positive_cols {
data.push(CellOccupancyState::Unoccupied);
}
}
// Push new negative rows
for _ in 0..(req_positive_rows as usize * new_col_count) {
data.push(CellOccupancyState::Unoccupied);
}
// Update self with new data
self.inner = Grid::from_vec(data, new_col_count);
self.rows.negative_implicit += req_negative_rows as u16;
self.rows.positive_implicit += req_positive_rows as u16;
self.columns.negative_implicit += req_negative_cols as u16;
self.columns.positive_implicit += req_positive_cols as u16;
}
/// Mark an area of the matrix as occupied, expanding the allocated space as necessary to accommodate the passed area.
pub fn mark_area_as(
&mut self,
primary_axis: AbsoluteAxis,
primary_span: Line<OriginZeroLine>,
secondary_span: Line<OriginZeroLine>,
value: CellOccupancyState,
) {
let (row_span, column_span) = match primary_axis {
AbsoluteAxis::Horizontal => (secondary_span, primary_span),
AbsoluteAxis::Vertical => (primary_span, secondary_span),
};
let mut col_range = self.columns.oz_line_range_to_track_range(column_span);
let mut row_range = self.rows.oz_line_range_to_track_range(row_span);
// Check that if the resolved ranges fit within the allocated grid. And if they don't then expand the grid to fit
// and then re-resolve the ranges once the grid has been expanded as the resolved indexes may have changed
let is_in_range = self.is_area_in_range(AbsoluteAxis::Horizontal, col_range.clone(), row_range.clone());
if !is_in_range {
self.expand_to_fit_range(row_range.clone(), col_range.clone());
col_range = self.columns.oz_line_range_to_track_range(column_span);
row_range = self.rows.oz_line_range_to_track_range(row_span);
}
for x in row_range {
for y in col_range.clone() {
*self.inner.get_mut(x as usize, y as usize).unwrap() = value;
}
}
}
/// Determines whether a grid area specified by the bounding grid lines in OriginZero coordinates
/// is entirely unnocupied. Returns true if all grid cells within the grid area are unnocupied, else false.
pub fn line_area_is_unoccupied(
&self,
primary_axis: AbsoluteAxis,
primary_span: Line<OriginZeroLine>,
secondary_span: Line<OriginZeroLine>,
) -> bool {
let primary_range = self.track_counts(primary_axis).oz_line_range_to_track_range(primary_span);
let secondary_range = self.track_counts(primary_axis.other_axis()).oz_line_range_to_track_range(secondary_span);
self.track_area_is_unoccupied(primary_axis, primary_range, secondary_range)
}
/// Determines whether a grid area specified by a range of indexes into this CellOccupancyMatrix
/// is entirely unnocupied. Returns true if all grid cells within the grid area are unnocupied, else false.
pub fn track_area_is_unoccupied(
&self,
primary_axis: AbsoluteAxis,
primary_range: Range<i16>,
secondary_range: Range<i16>,
) -> bool {
let (row_range, col_range) = match primary_axis {
AbsoluteAxis::Horizontal => (secondary_range, primary_range),
AbsoluteAxis::Vertical => (primary_range, secondary_range),
};
// Search for occupied cells in the specified area. Out of bounds cells are considered unoccupied.
for x in row_range {
for y in col_range.clone() {
match self.inner.get(x as usize, y as usize) {
None | Some(CellOccupancyState::Unoccupied) => continue,
_ => return false,
}
}
}
true
}
/// Determines whether the specified row contains any items
pub fn row_is_occupied(&self, row_index: usize) -> bool {
self.inner.iter_row(row_index).any(|cell| !matches!(cell, CellOccupancyState::Unoccupied))
}
/// Determines whether the specified column contains any items
pub fn column_is_occupied(&self, column_index: usize) -> bool {
self.inner.iter_col(column_index).any(|cell| !matches!(cell, CellOccupancyState::Unoccupied))
}
/// Returns the track counts of this CellOccunpancyMatrix in the relevant axis
pub fn track_counts(&self, track_type: AbsoluteAxis) -> &TrackCounts {
match track_type {
AbsoluteAxis::Horizontal => &self.columns,
AbsoluteAxis::Vertical => &self.rows,
}
}
/// Given an axis and a track index
/// Search backwards from the end of the track and find the last grid cell matching the specified state (if any)
/// Return the index of that cell or None.
pub fn last_of_type(
&self,
track_type: AbsoluteAxis,
start_at: OriginZeroLine,
kind: CellOccupancyState,
) -> Option<OriginZeroLine> {
let track_counts = self.track_counts(track_type.other_axis());
let track_computed_index = track_counts.oz_line_to_next_track(start_at);
let maybe_index = match track_type {
AbsoluteAxis::Horizontal => {
self.inner.iter_row(track_computed_index as usize).rposition(|item| *item == kind)
}
AbsoluteAxis::Vertical => {
self.inner.iter_col(track_computed_index as usize).rposition(|item| *item == kind)
}
};
maybe_index.map(|idx| track_counts.track_to_prev_oz_line(idx as u16))
}
}

View File

@@ -0,0 +1,130 @@
//! Taffy uses two coordinate systems to refer to grid lines (the gaps/gutters between rows/columns):
use super::super::types::TrackCounts;
use crate::geometry::Line;
use core::cmp::{max, Ordering};
use core::ops::{Add, AddAssign, Sub};
/// Represents a grid line position in "CSS Grid Line" coordinates
///
/// "CSS Grid Line" coordinates are those used in grid-row/grid-column in the CSS grid spec:
/// - The line at left hand (or top) edge of the explicit grid is line 1
/// (and counts up from there)
/// - The line at the right hand (or bottom) edge of the explicit grid is -1
/// (and counts down from there)
/// - 0 is not a valid index
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[repr(transparent)]
pub struct GridLine(i16);
impl From<i16> for GridLine {
fn from(value: i16) -> Self {
Self(value)
}
}
impl GridLine {
/// Returns the underlying i16
pub fn as_i16(self) -> i16 {
self.0
}
/// Convert into OriginZero coordinates using the specified explicit track count
pub(crate) fn into_origin_zero_line(self, explicit_track_count: u16) -> OriginZeroLine {
let explicit_line_count = explicit_track_count + 1;
let oz_line = match self.0.cmp(&0) {
Ordering::Greater => self.0 - 1,
Ordering::Less => self.0 + explicit_line_count as i16,
Ordering::Equal => panic!("Grid line of zero is invalid"),
};
OriginZeroLine(oz_line)
}
}
/// Represents a grid line position in "OriginZero" coordinates
///
/// "OriginZero" coordinates are a normalized form:
/// - The line at left hand (or top) edge of the explicit grid is line 0
/// - The next line to the right (or down) is 1, and so on
/// - The next line to the left (or up) is -1, and so on
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[repr(transparent)]
pub struct OriginZeroLine(pub i16);
// Add and Sub with Self
impl Add<OriginZeroLine> for OriginZeroLine {
type Output = Self;
fn add(self, rhs: OriginZeroLine) -> Self::Output {
OriginZeroLine(self.0 + rhs.0)
}
}
impl Sub<OriginZeroLine> for OriginZeroLine {
type Output = Self;
fn sub(self, rhs: OriginZeroLine) -> Self::Output {
OriginZeroLine(self.0 - rhs.0)
}
}
// Add and Sub with u16
impl Add<u16> for OriginZeroLine {
type Output = Self;
fn add(self, rhs: u16) -> Self::Output {
OriginZeroLine(self.0 + rhs as i16)
}
}
impl AddAssign<u16> for OriginZeroLine {
fn add_assign(&mut self, rhs: u16) {
self.0 += rhs as i16;
}
}
impl Sub<u16> for OriginZeroLine {
type Output = Self;
fn sub(self, rhs: u16) -> Self::Output {
OriginZeroLine(self.0 - rhs as i16)
}
}
impl OriginZeroLine {
/// Converts a grid line in OriginZero coordinates into the index of that same grid line in the GridTrackVec.
pub(crate) fn into_track_vec_index(self, track_counts: TrackCounts) -> usize {
assert!(
self.0 >= -(track_counts.negative_implicit as i16),
"OriginZero grid line cannot be less than the number of negative grid lines"
);
assert!(
self.0 <= (track_counts.explicit + track_counts.positive_implicit) as i16,
"OriginZero grid line cannot be more than the number of positive grid lines"
);
2 * ((self.0 + track_counts.negative_implicit as i16) as usize)
}
/// The minimum number of negative implicit track there must be if a grid item starts at this line.
pub(crate) fn implied_negative_implicit_tracks(self) -> u16 {
if self.0 < 0 {
self.0.unsigned_abs()
} else {
0
}
}
/// The minimum number of positive implicit track there must be if a grid item end at this line.
pub(crate) fn implied_positive_implicit_tracks(self, explicit_track_count: u16) -> u16 {
if self.0 > explicit_track_count as i16 {
self.0 as u16 - explicit_track_count
} else {
0
}
}
}
impl Line<OriginZeroLine> {
/// The number of tracks between the start and end lines
pub(crate) fn span(self) -> u16 {
max(self.end.0 - self.start.0, 0) as u16
}
}
/// A trait for the different coordinates used to define grid lines.
pub trait GridCoordinate: Copy {}
impl GridCoordinate for GridLine {}
impl GridCoordinate for OriginZeroLine {}

View File

@@ -0,0 +1,535 @@
//! Contains GridItem used to represent a single grid item during layout
use super::GridTrack;
use crate::compute::grid::OriginZeroLine;
use crate::geometry::AbstractAxis;
use crate::geometry::{Line, Point, Rect, Size};
use crate::style::{
AlignItems, AlignSelf, AvailableSpace, Dimension, LengthPercentageAuto, MaxTrackSizingFunction,
MinTrackSizingFunction, Overflow,
};
use crate::tree::{LayoutPartialTree, LayoutPartialTreeExt, NodeId, SizingMode};
use crate::util::{MaybeMath, MaybeResolve, ResolveOrZero};
use crate::{BoxSizing, GridItemStyle, LengthPercentage};
use core::ops::Range;
/// Represents a single grid item
#[derive(Debug)]
pub(in super::super) struct GridItem {
/// The id of the node that this item represents
pub node: NodeId,
/// The order of the item in the children array
///
/// We sort the list of grid items during track sizing. This field allows us to sort back the original order
/// for final positioning
pub source_order: u16,
/// The item's definite row-start and row-end, as resolved by the placement algorithm
/// (in origin-zero coordinates)
pub row: Line<OriginZeroLine>,
/// The items definite column-start and column-end, as resolved by the placement algorithm
/// (in origin-zero coordinates)
pub column: Line<OriginZeroLine>,
/// The item's overflow style
pub overflow: Point<Overflow>,
/// The item's box_sizing style
pub box_sizing: BoxSizing,
/// The item's size style
pub size: Size<Dimension>,
/// The item's min_size style
pub min_size: Size<Dimension>,
/// The item's max_size style
pub max_size: Size<Dimension>,
/// The item's aspect_ratio style
pub aspect_ratio: Option<f32>,
/// The item's padding style
pub padding: Rect<LengthPercentage>,
/// The item's border style
pub border: Rect<LengthPercentage>,
/// The item's margin style
pub margin: Rect<LengthPercentageAuto>,
/// The item's align_self property, or the parent's align_items property is not set
pub align_self: AlignSelf,
/// The item's justify_self property, or the parent's justify_items property is not set
pub justify_self: AlignSelf,
/// The items first baseline (horizontal)
pub baseline: Option<f32>,
/// Shim for baseline alignment that acts like an extra top margin
/// TODO: Support last baseline and vertical text baselines
pub baseline_shim: f32,
/// The item's definite row-start and row-end (same as `row` field, except in a different coordinate system)
/// (as indexes into the Vec<GridTrack> stored in a grid's AbstractAxisTracks)
pub row_indexes: Line<u16>,
/// The items definite column-start and column-end (same as `column` field, except in a different coordinate system)
/// (as indexes into the Vec<GridTrack> stored in a grid's AbstractAxisTracks)
pub column_indexes: Line<u16>,
/// Whether the item crosses a flexible row
pub crosses_flexible_row: bool,
/// Whether the item crosses a flexible column
pub crosses_flexible_column: bool,
/// Whether the item crosses a intrinsic row
pub crosses_intrinsic_row: bool,
/// Whether the item crosses a intrinsic column
pub crosses_intrinsic_column: bool,
// Caches for intrinsic size computation. These caches are only valid for a single run of the track-sizing algorithm.
/// Cache for the known_dimensions input to intrinsic sizing computation
pub available_space_cache: Option<Size<Option<f32>>>,
/// Cache for the min-content size
pub min_content_contribution_cache: Size<Option<f32>>,
/// Cache for the minimum contribution
pub minimum_contribution_cache: Size<Option<f32>>,
/// Cache for the max-content size
pub max_content_contribution_cache: Size<Option<f32>>,
/// Final y position. Used to compute baseline alignment for the container.
pub y_position: f32,
/// Final height. Used to compute baseline alignment for the container.
pub height: f32,
}
impl GridItem {
/// Create a new item given a concrete placement in both axes
pub fn new_with_placement_style_and_order<S: GridItemStyle>(
node: NodeId,
col_span: Line<OriginZeroLine>,
row_span: Line<OriginZeroLine>,
style: S,
parent_align_items: AlignItems,
parent_justify_items: AlignItems,
source_order: u16,
) -> Self {
GridItem {
node,
source_order,
row: row_span,
column: col_span,
overflow: style.overflow(),
box_sizing: style.box_sizing(),
size: style.size(),
min_size: style.min_size(),
max_size: style.max_size(),
aspect_ratio: style.aspect_ratio(),
padding: style.padding(),
border: style.border(),
margin: style.margin(),
align_self: style.align_self().unwrap_or(parent_align_items),
justify_self: style.justify_self().unwrap_or(parent_justify_items),
baseline: None,
baseline_shim: 0.0,
row_indexes: Line { start: 0, end: 0 }, // Properly initialised later
column_indexes: Line { start: 0, end: 0 }, // Properly initialised later
crosses_flexible_row: false, // Properly initialised later
crosses_flexible_column: false, // Properly initialised later
crosses_intrinsic_row: false, // Properly initialised later
crosses_intrinsic_column: false, // Properly initialised later
available_space_cache: None,
min_content_contribution_cache: Size::NONE,
max_content_contribution_cache: Size::NONE,
minimum_contribution_cache: Size::NONE,
y_position: 0.0,
height: 0.0,
}
}
/// This item's placement in the specified axis in OriginZero coordinates
pub fn placement(&self, axis: AbstractAxis) -> Line<OriginZeroLine> {
match axis {
AbstractAxis::Block => self.row,
AbstractAxis::Inline => self.column,
}
}
/// This item's placement in the specified axis as GridTrackVec indices
pub fn placement_indexes(&self, axis: AbstractAxis) -> Line<u16> {
match axis {
AbstractAxis::Block => self.row_indexes,
AbstractAxis::Inline => self.column_indexes,
}
}
/// Returns a range which can be used as an index into the GridTrackVec in the specified axis
/// which will produce a sub-slice of covering all the tracks and lines that this item spans
/// excluding the lines that bound it.
pub fn track_range_excluding_lines(&self, axis: AbstractAxis) -> Range<usize> {
let indexes = self.placement_indexes(axis);
(indexes.start as usize + 1)..(indexes.end as usize)
}
/// Returns the number of tracks that this item spans in the specified axis
pub fn span(&self, axis: AbstractAxis) -> u16 {
match axis {
AbstractAxis::Block => self.row.span(),
AbstractAxis::Inline => self.column.span(),
}
}
/// Returns the pre-computed value indicating whether the grid item crosses a flexible track in
/// the specified axis
pub fn crosses_flexible_track(&self, axis: AbstractAxis) -> bool {
match axis {
AbstractAxis::Inline => self.crosses_flexible_column,
AbstractAxis::Block => self.crosses_flexible_row,
}
}
/// Returns the pre-computed value indicating whether the grid item crosses an intrinsic track in
/// the specified axis
pub fn crosses_intrinsic_track(&self, axis: AbstractAxis) -> bool {
match axis {
AbstractAxis::Inline => self.crosses_intrinsic_column,
AbstractAxis::Block => self.crosses_intrinsic_row,
}
}
/// For an item spanning multiple tracks, the upper limit used to calculate its limited min-/max-content contribution is the
/// sum of the fixed max track sizing functions of any tracks it spans, and is applied if it only spans such tracks.
pub fn spanned_track_limit(
&mut self,
axis: AbstractAxis,
axis_tracks: &[GridTrack],
axis_parent_size: Option<f32>,
) -> Option<f32> {
let spanned_tracks = &axis_tracks[self.track_range_excluding_lines(axis)];
let tracks_all_fixed = spanned_tracks
.iter()
.all(|track| track.max_track_sizing_function.definite_limit(axis_parent_size).is_some());
if tracks_all_fixed {
let limit: f32 = spanned_tracks
.iter()
.map(|track| track.max_track_sizing_function.definite_limit(axis_parent_size).unwrap())
.sum();
Some(limit)
} else {
None
}
}
/// Similar to the spanned_track_limit, but excludes FitContent arguments from the limit.
/// Used to clamp the automatic minimum contributions of an item
pub fn spanned_fixed_track_limit(
&mut self,
axis: AbstractAxis,
axis_tracks: &[GridTrack],
axis_parent_size: Option<f32>,
) -> Option<f32> {
let spanned_tracks = &axis_tracks[self.track_range_excluding_lines(axis)];
let tracks_all_fixed = spanned_tracks
.iter()
.all(|track| track.max_track_sizing_function.definite_value(axis_parent_size).is_some());
if tracks_all_fixed {
let limit: f32 = spanned_tracks
.iter()
.map(|track| track.max_track_sizing_function.definite_value(axis_parent_size).unwrap())
.sum();
Some(limit)
} else {
None
}
}
/// Compute the known_dimensions to be passed to the child sizing functions
/// The key thing that is being done here is applying stretch alignment, which is necessary to
/// allow percentage sizes further down the tree to resolve properly in some cases
fn known_dimensions(
&self,
inner_node_size: Size<Option<f32>>,
grid_area_size: Size<Option<f32>>,
) -> Size<Option<f32>> {
let margins = self.margins_axis_sums_with_baseline_shims(inner_node_size.width);
let aspect_ratio = self.aspect_ratio;
let padding = self.padding.resolve_or_zero(grid_area_size);
let border = self.border.resolve_or_zero(grid_area_size);
let padding_border_size = (padding + border).sum_axes();
let box_sizing_adjustment =
if self.box_sizing == BoxSizing::ContentBox { padding_border_size } else { Size::ZERO };
let inherent_size = self
.size
.maybe_resolve(grid_area_size)
.maybe_apply_aspect_ratio(aspect_ratio)
.maybe_add(box_sizing_adjustment);
let min_size = self
.min_size
.maybe_resolve(grid_area_size)
.maybe_apply_aspect_ratio(aspect_ratio)
.maybe_add(box_sizing_adjustment);
let max_size = self
.max_size
.maybe_resolve(grid_area_size)
.maybe_apply_aspect_ratio(aspect_ratio)
.maybe_add(box_sizing_adjustment);
let grid_area_minus_item_margins_size = grid_area_size.maybe_sub(margins);
// If node is absolutely positioned and width is not set explicitly, then deduce it
// from left, right and container_content_box if both are set.
let width = inherent_size.width.or_else(|| {
// Apply width based on stretch alignment if:
// - Alignment style is "stretch"
// - The node is not absolutely positioned
// - The node does not have auto margins in this axis.
if self.margin.left != LengthPercentageAuto::Auto
&& self.margin.right != LengthPercentageAuto::Auto
&& self.justify_self == AlignSelf::Stretch
{
return grid_area_minus_item_margins_size.width;
}
None
});
// Reapply aspect ratio after stretch and absolute position width adjustments
let Size { width, height } =
Size { width, height: inherent_size.height }.maybe_apply_aspect_ratio(aspect_ratio);
let height = height.or_else(|| {
// Apply height based on stretch alignment if:
// - Alignment style is "stretch"
// - The node is not absolutely positioned
// - The node does not have auto margins in this axis.
if self.margin.top != LengthPercentageAuto::Auto
&& self.margin.bottom != LengthPercentageAuto::Auto
&& self.align_self == AlignSelf::Stretch
{
return grid_area_minus_item_margins_size.height;
}
None
});
// Reapply aspect ratio after stretch and absolute position height adjustments
let Size { width, height } = Size { width, height }.maybe_apply_aspect_ratio(aspect_ratio);
// Clamp size by min and max width/height
let Size { width, height } = Size { width, height }.maybe_clamp(min_size, max_size);
Size { width, height }
}
/// Compute the available_space to be passed to the child sizing functions
/// These are estimates based on either the max track sizing function or the provisional base size in the opposite
/// axis to the one currently being sized.
/// https://www.w3.org/TR/css-grid-1/#algo-overview
pub fn available_space(
&self,
axis: AbstractAxis,
other_axis_tracks: &[GridTrack],
other_axis_available_space: Option<f32>,
get_track_size_estimate: impl Fn(&GridTrack, Option<f32>) -> Option<f32>,
) -> Size<Option<f32>> {
let item_other_axis_size: Option<f32> = {
other_axis_tracks[self.track_range_excluding_lines(axis.other())]
.iter()
.map(|track| {
get_track_size_estimate(track, other_axis_available_space)
.map(|size| size + track.content_alignment_adjustment)
})
.sum::<Option<f32>>()
};
let mut size = Size::NONE;
size.set(axis.other(), item_other_axis_size);
size
}
/// Retrieve the available_space from the cache or compute them using the passed parameters
pub fn available_space_cached(
&mut self,
axis: AbstractAxis,
other_axis_tracks: &[GridTrack],
other_axis_available_space: Option<f32>,
get_track_size_estimate: impl Fn(&GridTrack, Option<f32>) -> Option<f32>,
) -> Size<Option<f32>> {
self.available_space_cache.unwrap_or_else(|| {
let available_spaces =
self.available_space(axis, other_axis_tracks, other_axis_available_space, get_track_size_estimate);
self.available_space_cache = Some(available_spaces);
available_spaces
})
}
/// Compute the item's resolved margins for size contributions. Horizontal percentage margins always resolve
/// to zero if the container size is indefinite as otherwise this would introduce a cyclic dependency.
#[inline(always)]
pub fn margins_axis_sums_with_baseline_shims(&self, inner_node_width: Option<f32>) -> Size<f32> {
Rect {
left: self.margin.left.resolve_or_zero(Some(0.0)),
right: self.margin.right.resolve_or_zero(Some(0.0)),
top: self.margin.top.resolve_or_zero(inner_node_width) + self.baseline_shim,
bottom: self.margin.bottom.resolve_or_zero(inner_node_width),
}
.sum_axes()
}
/// Compute the item's min content contribution from the provided parameters
pub fn min_content_contribution(
&self,
axis: AbstractAxis,
tree: &mut impl LayoutPartialTree,
available_space: Size<Option<f32>>,
inner_node_size: Size<Option<f32>>,
) -> f32 {
let known_dimensions = self.known_dimensions(inner_node_size, available_space);
tree.measure_child_size(
self.node,
known_dimensions,
inner_node_size,
available_space.map(|opt| match opt {
Some(size) => AvailableSpace::Definite(size),
None => AvailableSpace::MinContent,
}),
SizingMode::InherentSize,
axis.as_abs_naive(),
Line::FALSE,
)
}
/// Retrieve the item's min content contribution from the cache or compute it using the provided parameters
#[inline(always)]
pub fn min_content_contribution_cached(
&mut self,
axis: AbstractAxis,
tree: &mut impl LayoutPartialTree,
available_space: Size<Option<f32>>,
inner_node_size: Size<Option<f32>>,
) -> f32 {
self.min_content_contribution_cache.get(axis).unwrap_or_else(|| {
let size = self.min_content_contribution(axis, tree, available_space, inner_node_size);
self.min_content_contribution_cache.set(axis, Some(size));
size
})
}
/// Compute the item's max content contribution from the provided parameters
pub fn max_content_contribution(
&self,
axis: AbstractAxis,
tree: &mut impl LayoutPartialTree,
available_space: Size<Option<f32>>,
inner_node_size: Size<Option<f32>>,
) -> f32 {
let known_dimensions = self.known_dimensions(inner_node_size, available_space);
tree.measure_child_size(
self.node,
known_dimensions,
inner_node_size,
available_space.map(|opt| match opt {
Some(size) => AvailableSpace::Definite(size),
None => AvailableSpace::MaxContent,
}),
SizingMode::InherentSize,
axis.as_abs_naive(),
Line::FALSE,
)
}
/// Retrieve the item's max content contribution from the cache or compute it using the provided parameters
#[inline(always)]
pub fn max_content_contribution_cached(
&mut self,
axis: AbstractAxis,
tree: &mut impl LayoutPartialTree,
available_space: Size<Option<f32>>,
inner_node_size: Size<Option<f32>>,
) -> f32 {
self.max_content_contribution_cache.get(axis).unwrap_or_else(|| {
let size = self.max_content_contribution(axis, tree, available_space, inner_node_size);
self.max_content_contribution_cache.set(axis, Some(size));
size
})
}
/// The minimum contribution of an item is the smallest outer size it can have.
/// Specifically:
/// - If the items computed preferred size behaves as auto or depends on the size of its containing block in the relevant axis:
/// Its minimum contribution is the outer size that would result from assuming the items used minimum size as its preferred size;
/// - Else the items minimum contribution is its min-content contribution.
///
/// Because the minimum contribution often depends on the size of the items content, it is considered a type of intrinsic size contribution.
/// See: https://www.w3.org/TR/css-grid-1/#min-size-auto
pub fn minimum_contribution(
&mut self,
tree: &mut impl LayoutPartialTree,
axis: AbstractAxis,
axis_tracks: &[GridTrack],
known_dimensions: Size<Option<f32>>,
inner_node_size: Size<Option<f32>>,
) -> f32 {
let padding = self.padding.resolve_or_zero(inner_node_size);
let border = self.border.resolve_or_zero(inner_node_size);
let padding_border_size = (padding + border).sum_axes();
let box_sizing_adjustment =
if self.box_sizing == BoxSizing::ContentBox { padding_border_size } else { Size::ZERO };
let size = self
.size
.maybe_resolve(inner_node_size)
.maybe_apply_aspect_ratio(self.aspect_ratio)
.maybe_add(box_sizing_adjustment)
.get(axis)
.or_else(|| {
self.min_size
.maybe_resolve(inner_node_size)
.maybe_apply_aspect_ratio(self.aspect_ratio)
.maybe_add(box_sizing_adjustment)
.get(axis)
})
.or_else(|| self.overflow.get(axis).maybe_into_automatic_min_size())
.unwrap_or_else(|| {
// Automatic minimum size. See https://www.w3.org/TR/css-grid-1/#min-size-auto
// To provide a more reasonable default minimum size for grid items, the used value of its automatic minimum size
// in a given axis is the content-based minimum size if all of the following are true:
let item_axis_tracks = &axis_tracks[self.track_range_excluding_lines(axis)];
// it is not a scroll container
// TODO: support overflow property
// it spans at least one track in that axis whose min track sizing function is auto
let spans_auto_min_track = axis_tracks
.iter()
// TODO: should this be 'behaves as auto' rather than just literal auto?
.any(|track| track.min_track_sizing_function == MinTrackSizingFunction::Auto);
// if it spans more than one track in that axis, none of those tracks are flexible
let only_span_one_track = item_axis_tracks.len() == 1;
let spans_a_flexible_track = axis_tracks
.iter()
.any(|track| matches!(track.max_track_sizing_function, MaxTrackSizingFunction::Fraction(_)));
let use_content_based_minimum =
spans_auto_min_track && (only_span_one_track || !spans_a_flexible_track);
// Otherwise, the automatic minimum size is zero, as usual.
if use_content_based_minimum {
self.min_content_contribution_cached(axis, tree, known_dimensions, inner_node_size)
} else {
0.0
}
});
// In all cases, the size suggestion is additionally clamped by the maximum size in the affected axis, if its definite.
// Note: The argument to fit-content() does not clamp the content-based minimum size in the same way as a fixed max track
// sizing function.
let limit = self.spanned_fixed_track_limit(axis, axis_tracks, inner_node_size.get(axis));
size.maybe_min(limit)
}
/// Retrieve the item's minimum contribution from the cache or compute it using the provided parameters
#[inline(always)]
pub fn minimum_contribution_cached(
&mut self,
tree: &mut impl LayoutPartialTree,
axis: AbstractAxis,
axis_tracks: &[GridTrack],
known_dimensions: Size<Option<f32>>,
inner_node_size: Size<Option<f32>>,
) -> f32 {
self.minimum_contribution_cache.get(axis).unwrap_or_else(|| {
let size = self.minimum_contribution(tree, axis, axis_tracks, known_dimensions, inner_node_size);
self.minimum_contribution_cache.set(axis, Some(size));
size
})
}
}

View File

@@ -0,0 +1,155 @@
//! Contains GridTrack used to represent a single grid track (row/column) during layout
use crate::{
style::{LengthPercentage, MaxTrackSizingFunction, MinTrackSizingFunction},
util::sys::f32_min,
};
/// Whether a GridTrack represents an actual track or a gutter.
#[derive(Copy, Clone, Debug, PartialEq)]
pub(in super::super) enum GridTrackKind {
/// Track is an actual track
Track,
/// Track is a gutter (aka grid line) (aka gap)
Gutter, // { name: Option<u16> },
}
/// Internal sizing information for a single grid track (row/column)
/// Gutters between tracks are sized similarly to actual tracks, so they
/// are also represented by this struct
#[derive(Debug, Clone)]
pub(in super::super) struct GridTrack {
#[allow(dead_code)] // Used in tests + may be useful in future
/// Whether the track is a full track, a gutter, or a placeholder that has not yet been initialised
pub kind: GridTrackKind,
/// Whether the track is a collapsed track/gutter. Collapsed tracks are effectively treated as if
/// they don't exist for the purposes of grid sizing. Gutters between collapsed tracks are also collapsed.
pub is_collapsed: bool,
/// The minimum track sizing function of the track
pub min_track_sizing_function: MinTrackSizingFunction,
/// The maximum track sizing function of the track
pub max_track_sizing_function: MaxTrackSizingFunction,
/// The distance of the start of the track from the start of the grid container
pub offset: f32,
/// The size (width/height as applicable) of the track
pub base_size: f32,
/// A temporary scratch value when sizing tracks
/// Note: can be infinity
pub growth_limit: f32,
/// A temporary scratch value when sizing tracks. Is used as an additional amount to add to the
/// estimate for the available space in the opposite axis when content sizing items
pub content_alignment_adjustment: f32,
/// A temporary scratch value when "distributing space" to avoid clobbering planned increase variable
pub item_incurred_increase: f32,
/// A temporary scratch value when "distributing space" to avoid clobbering the main variable
pub base_size_planned_increase: f32,
/// A temporary scratch value when "distributing space" to avoid clobbering the main variable
pub growth_limit_planned_increase: f32,
/// A temporary scratch value when "distributing space"
/// See: https://www.w3.org/TR/css3-grid-layout/#infinitely-growable
pub infinitely_growable: bool,
}
impl GridTrack {
/// GridTrack constructor with all configuration parameters for the other constructors exposed
fn new_with_kind(
kind: GridTrackKind,
min_track_sizing_function: MinTrackSizingFunction,
max_track_sizing_function: MaxTrackSizingFunction,
) -> GridTrack {
GridTrack {
kind,
is_collapsed: false,
min_track_sizing_function,
max_track_sizing_function,
offset: 0.0,
base_size: 0.0,
growth_limit: 0.0,
content_alignment_adjustment: 0.0,
item_incurred_increase: 0.0,
base_size_planned_increase: 0.0,
growth_limit_planned_increase: 0.0,
infinitely_growable: false,
}
}
/// Create new GridTrack representing an actual track (not a gutter)
pub fn new(
min_track_sizing_function: MinTrackSizingFunction,
max_track_sizing_function: MaxTrackSizingFunction,
) -> GridTrack {
Self::new_with_kind(GridTrackKind::Track, min_track_sizing_function, max_track_sizing_function)
}
/// Create a new GridTrack representing a gutter
pub fn gutter(size: LengthPercentage) -> GridTrack {
Self::new_with_kind(
GridTrackKind::Gutter,
MinTrackSizingFunction::Fixed(size),
MaxTrackSizingFunction::Fixed(size),
)
}
/// Mark a GridTrack as collapsed. Also sets both of the track's sizing functions
/// to fixed zero-sized sizing functions.
pub fn collapse(&mut self) {
self.is_collapsed = true;
self.min_track_sizing_function = MinTrackSizingFunction::Fixed(LengthPercentage::Length(0.0));
self.max_track_sizing_function = MaxTrackSizingFunction::Fixed(LengthPercentage::Length(0.0));
}
#[inline(always)]
/// Returns true if the track is flexible (has a Flex MaxTrackSizingFunction), else false.
pub fn is_flexible(&self) -> bool {
matches!(self.max_track_sizing_function, MaxTrackSizingFunction::Fraction(_))
}
#[inline(always)]
/// Returns true if the track is flexible (has a Flex MaxTrackSizingFunction), else false.
pub fn uses_percentage(&self) -> bool {
self.min_track_sizing_function.uses_percentage() || self.max_track_sizing_function.uses_percentage()
}
#[inline(always)]
/// Returns true if the track has an intrinsic min and or max sizing function
pub fn has_intrinsic_sizing_function(&self) -> bool {
self.min_track_sizing_function.is_intrinsic() || self.max_track_sizing_function.is_intrinsic()
}
#[inline]
/// Returns true if the track is flexible (has a Flex MaxTrackSizingFunction), else false.
pub fn fit_content_limit(&self, axis_available_grid_space: Option<f32>) -> f32 {
match self.max_track_sizing_function {
MaxTrackSizingFunction::FitContent(LengthPercentage::Length(limit)) => limit,
MaxTrackSizingFunction::FitContent(LengthPercentage::Percent(fraction)) => {
match axis_available_grid_space {
Some(space) => space * fraction,
None => f32::INFINITY,
}
}
_ => f32::INFINITY,
}
}
#[inline]
/// Returns true if the track is flexible (has a Flex MaxTrackSizingFunction), else false.
pub fn fit_content_limited_growth_limit(&self, axis_available_grid_space: Option<f32>) -> f32 {
f32_min(self.growth_limit, self.fit_content_limit(axis_available_grid_space))
}
#[inline]
/// Returns the track's flex factor if it is a flex track, else 0.
pub fn flex_factor(&self) -> f32 {
match self.max_track_sizing_function {
MaxTrackSizingFunction::Fraction(flex_factor) => flex_factor,
_ => 0.0,
}
}
}

View File

@@ -0,0 +1,102 @@
//! Contains TrackCounts used to keep track of the number of tracks in the explicit and implicit grids.
//! Also contains coordinate conversion functions which depend on those counts
//!
//! Taffy uses two coordinate systems to refer to grid lines (the gaps/gutters between rows/columns):
//!
//! "CSS Grid Line" coordinates are those used in grid-row/grid-column in the CSS grid spec:
//! - 0 is not a valid index
//! - The line at left hand (or top) edge of the explicit grid is line 1
//! (and counts up from there)
//! - The line at the right hand (or bottom) edge of the explicit grid in -1
//! (and counts down from there)
//!
//! "OriginZero" coordinates are a normalized form:
//! - The line at left hand (or top) edge of the explicit grid is line 0
//! - The next line to the right (or down) is 1, and so on
//! - The next line to the left (or up) is -1, and so on
//!
//! Taffy also uses two coordinate systems to refer to grid tracks (rows/columns):
//!
//! Both of these systems represent the entire implicit grid, not just the explicit grid.
//!
//! "CellOccupancyMatrix track indices":
//! - These are indexes into the CellOccupancyMatrix
//! - The CellOccupancyMatrix stores only tracks
//! - 0 is the leftmost track of the implicit grid, and indexes count up there
//!
//! "GridTrackVec track indices":
//! - The GridTrackVecs store both lines and tracks, so:
//! - even indices (0, 2, 4, etc) represent lines
//! - odd indices (1, 3, 5, etc) represent tracks
//! - These is always an odd number of
//! - Index 1 is the leftmost track of the implicit grid. Index 3 is the second leftmost track, etc.
//! - Index 0 is the leftmost grid line. Index 2 is the second leftmost line, etc.
//!
use crate::{compute::grid::OriginZeroLine, geometry::Line};
use core::ops::Range;
/// Stores the number of tracks in a given dimension.
/// Stores separately the number of tracks in the implicit and explicit grids
#[derive(Clone, Copy, Debug, PartialEq, Default)]
pub(crate) struct TrackCounts {
/// The number of track in the implicit grid before the explicit grid
pub negative_implicit: u16,
/// The number of tracks in the explicit grid
pub explicit: u16,
/// The number of tracks in the implicit grid after the explicit grid
pub positive_implicit: u16,
}
impl TrackCounts {
/// Create a TrackCounts instance from raw track count numbers
pub fn from_raw(negative_implicit: u16, explicit: u16, positive_implicit: u16) -> Self {
Self { negative_implicit, explicit, positive_implicit }
}
/// Count the total number of tracks in the axis
pub fn len(&self) -> usize {
(self.negative_implicit + self.explicit + self.positive_implicit) as usize
}
/// The OriginZeroLine representing the start of the implicit grid
pub fn implicit_start_line(&self) -> OriginZeroLine {
OriginZeroLine(-(self.negative_implicit as i16))
}
/// The OriginZeroLine representing the end of the implicit grid
pub fn implicit_end_line(&self) -> OriginZeroLine {
OriginZeroLine((self.explicit + self.positive_implicit) as i16)
}
}
/// Conversion functions between OriginZero coordinates and CellOccupancyMatrix track indexes
#[allow(dead_code)]
impl TrackCounts {
/// Converts a grid line in OriginZero coordinates into the track immediately
/// following that grid line as an index into the CellOccupancyMatrix.
pub fn oz_line_to_next_track(&self, index: OriginZeroLine) -> i16 {
index.0 + (self.negative_implicit as i16)
}
/// Converts start and end grid lines in OriginZero coordinates into a range of tracks
/// as indexes into the CellOccupancyMatrix
pub fn oz_line_range_to_track_range(&self, input: Line<OriginZeroLine>) -> Range<i16> {
let start = self.oz_line_to_next_track(input.start);
let end = self.oz_line_to_next_track(input.end); // Don't subtract 1 as output range is exclusive
start..end
}
/// Converts a track as an index into the CellOccupancyMatrix into the grid line immediately
/// preceding that track in OriginZero coordinates.
pub fn track_to_prev_oz_line(&self, index: u16) -> OriginZeroLine {
OriginZeroLine((index as i16) - (self.negative_implicit as i16))
}
/// Converts a range of tracks as indexes into the CellOccupancyMatrix into
/// start and end grid lines in OriginZero coordinates
pub fn track_range_to_oz_line_range(&self, input: Range<i16>) -> Line<OriginZeroLine> {
let start = self.track_to_prev_oz_line(input.start as u16);
let end = self.track_to_prev_oz_line(input.end as u16); // Don't add 1 as input range is exclusive
Line { start, end }
}
}

View File

@@ -0,0 +1,41 @@
//! Structs and enums that are used within the grid module
mod cell_occupancy;
mod coordinates;
mod grid_item;
mod grid_track;
mod grid_track_counts;
// Publish only locally in the grid module
pub(super) use cell_occupancy::{CellOccupancyMatrix, CellOccupancyState};
pub(crate) use coordinates::{GridCoordinate, GridLine, OriginZeroLine};
pub(super) use grid_item::GridItem;
pub(super) use grid_track::GridTrack;
pub(super) use grid_track_counts::TrackCounts;
#[allow(unused_imports)]
pub(super) use grid_track::GridTrackKind;
// pub(super) enum GridPosition {
// Auto,
// LineIndex(i16),
// LineName(u16),
// // GridAreaStart(u16),
// // GridAreaEnd(u16),
// }
// pub(super) struct NamedArea {
// name: u16,
// row_start: u16,
// row_end: u16,
// column_start: u16,
// column_end: u16,
// }
// pub(super) struct CssGrid {
// pub available_space: Size<AvailableSpace>,
// pub cell_occupancy_matrix: CellOccupancyMatrix,
// pub items: Vec<GridItem>,
// pub columns: GridAxisTracks,
// pub rows: GridAxisTracks,
// pub named_areas: Vec<NamedArea>,
// }

View File

@@ -0,0 +1,6 @@
//! Utility functions used within the grid module
#[cfg(test)]
pub(super) mod test_helpers;
#[cfg(test)]
pub(super) use test_helpers::{CreateChildTestNode, CreateExpectedPlacement, CreateParentTestNode};

View File

@@ -0,0 +1,41 @@
//! Helpers for use in unit tests within the grid module
use super::super::OriginZeroLine;
use crate::prelude::*;
use crate::style::{Dimension, GridPlacement, Style};
pub(crate) trait CreateParentTestNode {
fn into_grid(self) -> Style;
}
impl CreateParentTestNode for (f32, f32, i32, i32) {
fn into_grid(self) -> Style {
Style {
display: Display::Grid,
size: Size { width: Dimension::Length(self.0), height: Dimension::Length(self.1) },
grid_template_columns: vec![fr(1f32); self.2 as usize],
grid_template_rows: vec![fr(1f32); self.3 as usize],
..Default::default()
}
}
}
pub(crate) trait CreateChildTestNode {
fn into_grid_child(self) -> Style;
}
impl CreateChildTestNode for (GridPlacement, GridPlacement, GridPlacement, GridPlacement) {
fn into_grid_child(self) -> Style {
Style {
display: Display::Grid,
grid_column: Line { start: self.0, end: self.1 },
grid_row: Line { start: self.2, end: self.3 },
..Default::default()
}
}
}
pub(crate) trait CreateExpectedPlacement {
fn into_oz(self) -> (OriginZeroLine, OriginZeroLine, OriginZeroLine, OriginZeroLine);
}
impl CreateExpectedPlacement for (i16, i16, i16, i16) {
fn into_oz(self) -> (OriginZeroLine, OriginZeroLine, OriginZeroLine, OriginZeroLine) {
(OriginZeroLine(self.0), OriginZeroLine(self.1), OriginZeroLine(self.2), OriginZeroLine(self.3))
}
}

164
vendor/taffy/src/compute/leaf.rs vendored Normal file
View File

@@ -0,0 +1,164 @@
//! Computes size using styles and measure functions
use crate::geometry::{Point, Size};
use crate::style::{AvailableSpace, Overflow, Position};
use crate::tree::{CollapsibleMarginSet, RunMode};
use crate::tree::{LayoutInput, LayoutOutput, SizingMode};
use crate::util::debug::debug_log;
use crate::util::sys::f32_max;
use crate::util::MaybeMath;
use crate::util::{MaybeResolve, ResolveOrZero};
use crate::{BoxSizing, CoreStyle};
use core::unreachable;
/// Compute the size of a leaf node (node with no children)
pub fn compute_leaf_layout<MeasureFunction>(
inputs: LayoutInput,
style: &impl CoreStyle,
measure_function: MeasureFunction,
) -> LayoutOutput
where
MeasureFunction: FnOnce(Size<Option<f32>>, Size<AvailableSpace>) -> Size<f32>,
{
let LayoutInput { known_dimensions, parent_size, available_space, sizing_mode, run_mode, .. } = inputs;
// Note: both horizontal and vertical percentage padding/borders are resolved against the container's inline size (i.e. width).
// This is not a bug, but is how CSS is specified (see: https://developer.mozilla.org/en-US/docs/Web/CSS/padding#values)
let margin = style.margin().resolve_or_zero(parent_size.width);
let padding = style.padding().resolve_or_zero(parent_size.width);
let border = style.border().resolve_or_zero(parent_size.width);
let padding_border = padding + border;
let pb_sum = padding_border.sum_axes();
let box_sizing_adjustment = if style.box_sizing() == BoxSizing::ContentBox { pb_sum } else { Size::ZERO };
// Resolve node's preferred/min/max sizes (width/heights) against the available space (percentages resolve to pixel values)
// For ContentSize mode, we pretend that the node has no size styles as these should be ignored.
let (node_size, node_min_size, node_max_size, aspect_ratio) = match sizing_mode {
SizingMode::ContentSize => {
let node_size = known_dimensions;
let node_min_size = Size::NONE;
let node_max_size = Size::NONE;
(node_size, node_min_size, node_max_size, None)
}
SizingMode::InherentSize => {
let aspect_ratio = style.aspect_ratio();
let style_size = style
.size()
.maybe_resolve(parent_size)
.maybe_apply_aspect_ratio(aspect_ratio)
.maybe_add(box_sizing_adjustment);
let style_min_size = style
.min_size()
.maybe_resolve(parent_size)
.maybe_apply_aspect_ratio(aspect_ratio)
.maybe_add(box_sizing_adjustment);
let style_max_size = style.max_size().maybe_resolve(parent_size).maybe_add(box_sizing_adjustment);
let node_size = known_dimensions.or(style_size);
(node_size, style_min_size, style_max_size, aspect_ratio)
}
};
// Scrollbar gutters are reserved when the `overflow` property is set to `Overflow::Scroll`.
// However, the axis are switched (transposed) because a node that scrolls vertically needs
// *horizontal* space to be reserved for a scrollbar
let scrollbar_gutter = style.overflow().transpose().map(|overflow| match overflow {
Overflow::Scroll => style.scrollbar_width(),
_ => 0.0,
});
// TODO: make side configurable based on the `direction` property
let mut content_box_inset = padding_border;
content_box_inset.right += scrollbar_gutter.x;
content_box_inset.bottom += scrollbar_gutter.y;
let has_styles_preventing_being_collapsed_through = !style.is_block()
|| style.overflow().x.is_scroll_container()
|| style.overflow().y.is_scroll_container()
|| style.position() == Position::Absolute
|| padding.top > 0.0
|| padding.bottom > 0.0
|| border.top > 0.0
|| border.bottom > 0.0
|| matches!(node_size.height, Some(h) if h > 0.0)
|| matches!(node_min_size.height, Some(h) if h > 0.0);
debug_log!("LEAF");
debug_log!("node_size", dbg:node_size);
debug_log!("min_size ", dbg:node_min_size);
debug_log!("max_size ", dbg:node_max_size);
// Return early if both width and height are known
if run_mode == RunMode::ComputeSize && has_styles_preventing_being_collapsed_through {
if let Size { width: Some(width), height: Some(height) } = node_size {
let size = Size { width, height }
.maybe_clamp(node_min_size, node_max_size)
.maybe_max(padding_border.sum_axes().map(Some));
return LayoutOutput {
size,
#[cfg(feature = "content_size")]
content_size: Size::ZERO,
first_baselines: Point::NONE,
top_margin: CollapsibleMarginSet::ZERO,
bottom_margin: CollapsibleMarginSet::ZERO,
margins_can_collapse_through: false,
};
};
}
// Compute available space
let available_space = Size {
width: known_dimensions
.width
.map(AvailableSpace::from)
.unwrap_or(available_space.width)
.maybe_sub(margin.horizontal_axis_sum())
.maybe_set(known_dimensions.width)
.maybe_set(node_size.width)
.maybe_set(node_max_size.width)
.map_definite_value(|size| {
size.maybe_clamp(node_min_size.width, node_max_size.width) - content_box_inset.horizontal_axis_sum()
}),
height: known_dimensions
.height
.map(AvailableSpace::from)
.unwrap_or(available_space.height)
.maybe_sub(margin.vertical_axis_sum())
.maybe_set(known_dimensions.height)
.maybe_set(node_size.height)
.maybe_set(node_max_size.height)
.map_definite_value(|size| {
size.maybe_clamp(node_min_size.height, node_max_size.height) - content_box_inset.vertical_axis_sum()
}),
};
// Measure node
let measured_size = measure_function(
match run_mode {
RunMode::ComputeSize => known_dimensions,
RunMode::PerformLayout => Size::NONE,
RunMode::PerformHiddenLayout => unreachable!(),
},
available_space,
);
let clamped_size = known_dimensions
.or(node_size)
.unwrap_or(measured_size + content_box_inset.sum_axes())
.maybe_clamp(node_min_size, node_max_size);
let size = Size {
width: clamped_size.width,
height: f32_max(clamped_size.height, aspect_ratio.map(|ratio| clamped_size.width / ratio).unwrap_or(0.0)),
};
let size = size.maybe_max(padding_border.sum_axes().map(Some));
LayoutOutput {
size,
#[cfg(feature = "content_size")]
content_size: measured_size + padding.sum_axes(),
first_baselines: Point::NONE,
top_margin: CollapsibleMarginSet::ZERO,
bottom_margin: CollapsibleMarginSet::ZERO,
margins_can_collapse_through: !has_styles_preventing_being_collapsed_through
&& size.height == 0.0
&& measured_size.height == 0.0,
}
}

320
vendor/taffy/src/compute/mod.rs vendored Normal file
View File

@@ -0,0 +1,320 @@
//! Low-level access to the layout algorithms themselves. For a higher-level API, see the [`TaffyTree`](crate::TaffyTree) struct.
//!
//! ### Layout functions
//!
//! The layout functions all take an [`&mut impl LayoutPartialTree`](crate::LayoutPartialTree) parameter, which represents a single container node and it's direct children.
//!
//! | Function | Purpose |
//! | --- | --- |
//! | [`compute_flexbox_layout`] | Layout a Flexbox container and it's direct children |
//! | [`compute_grid_layout`] | Layout a CSS Grid container and it's direct children |
//! | [`compute_block_layout`] | Layout a Block container and it's direct children |
//! | [`compute_leaf_layout`] | Applies common properties like padding/border/aspect-ratio to a node before deferring to a passed closure to determine it's size. Can be applied to nodes like text or image nodes. |
//! | [`compute_root_layout`] | Layout the root node of a tree (regardless of it's layout mode). This function is typically called once to begin a layout run. | |
//! | [`compute_hidden_layout`] | Mark a node as hidden during layout (like `Display::None`) |
//! | [`compute_cached_layout`] | Attempts to find a cached layout for the specified node and layout inputs. Uses the provided closure to compute the layout (and then stores the result in the cache) if no cached layout is found. |
//!
//! ### Other functions
//!
//! | Function | Requires | Purpose |
//! | --- | --- | --- |
//! | [`round_layout`] | [`RoundTree`] | Round a tree of float-valued layouts to integer pixels |
//! | [`print_tree`](crate::print_tree) | [`PrintTree`](crate::PrintTree) | Print a debug representation of a node tree and it's computed layout |
//!
pub(crate) mod common;
pub(crate) mod leaf;
#[cfg(feature = "block_layout")]
pub(crate) mod block;
#[cfg(feature = "flexbox")]
pub(crate) mod flexbox;
#[cfg(feature = "grid")]
pub(crate) mod grid;
pub use leaf::compute_leaf_layout;
#[cfg(feature = "block_layout")]
pub use self::block::compute_block_layout;
#[cfg(feature = "flexbox")]
pub use self::flexbox::compute_flexbox_layout;
#[cfg(feature = "grid")]
pub use self::grid::compute_grid_layout;
use crate::geometry::{Line, Point, Size};
use crate::style::{AvailableSpace, CoreStyle, Overflow};
use crate::tree::{
Layout, LayoutInput, LayoutOutput, LayoutPartialTree, LayoutPartialTreeExt, NodeId, RoundTree, SizingMode,
};
use crate::util::debug::{debug_log, debug_log_node, debug_pop_node, debug_push_node};
use crate::util::sys::round;
use crate::util::ResolveOrZero;
use crate::{BoxSizing, CacheTree, MaybeMath, MaybeResolve};
/// Compute layout for the root node in the tree
pub fn compute_root_layout(tree: &mut impl LayoutPartialTree, root: NodeId, available_space: Size<AvailableSpace>) {
let mut known_dimensions = Size::NONE;
#[cfg(feature = "block_layout")]
{
let parent_size = available_space.into_options();
let style = tree.get_core_container_style(root);
if style.is_block() {
// Pull these out earlier to avoid borrowing issues
let aspect_ratio = style.aspect_ratio();
let margin = style.margin().resolve_or_zero(parent_size.width);
let padding = style.padding().resolve_or_zero(parent_size.width);
let border = style.border().resolve_or_zero(parent_size.width);
let padding_border_size = (padding + border).sum_axes();
let box_sizing_adjustment =
if style.box_sizing() == BoxSizing::ContentBox { padding_border_size } else { Size::ZERO };
let min_size = style
.min_size()
.maybe_resolve(parent_size)
.maybe_apply_aspect_ratio(aspect_ratio)
.maybe_add(box_sizing_adjustment);
let max_size = style
.max_size()
.maybe_resolve(parent_size)
.maybe_apply_aspect_ratio(aspect_ratio)
.maybe_add(box_sizing_adjustment);
let clamped_style_size = style
.size()
.maybe_resolve(parent_size)
.maybe_apply_aspect_ratio(aspect_ratio)
.maybe_add(box_sizing_adjustment)
.maybe_clamp(min_size, max_size);
// If both min and max in a given axis are set and max <= min then this determines the size in that axis
let min_max_definite_size = min_size.zip_map(max_size, |min, max| match (min, max) {
(Some(min), Some(max)) if max <= min => Some(min),
_ => None,
});
// Block nodes automatically stretch fit their width to fit available space if available space is definite
let available_space_based_size = Size {
width: available_space.width.into_option().maybe_sub(margin.horizontal_axis_sum()),
height: None,
};
let styled_based_known_dimensions = known_dimensions
.or(min_max_definite_size)
.or(clamped_style_size)
.or(available_space_based_size)
.maybe_max(padding_border_size);
known_dimensions = styled_based_known_dimensions;
}
}
// Recursively compute node layout
let output = tree.perform_child_layout(
root,
known_dimensions,
available_space.into_options(),
available_space,
SizingMode::InherentSize,
Line::FALSE,
);
let style = tree.get_core_container_style(root);
let padding = style.padding().resolve_or_zero(available_space.width.into_option());
let border = style.border().resolve_or_zero(available_space.width.into_option());
let margin = style.margin().resolve_or_zero(available_space.width.into_option());
let scrollbar_size = Size {
width: if style.overflow().y == Overflow::Scroll { style.scrollbar_width() } else { 0.0 },
height: if style.overflow().x == Overflow::Scroll { style.scrollbar_width() } else { 0.0 },
};
drop(style);
tree.set_unrounded_layout(
root,
&Layout {
order: 0,
location: Point::ZERO,
size: output.size,
#[cfg(feature = "content_size")]
content_size: output.content_size,
scrollbar_size,
padding,
border,
// TODO: support auto margins for root node?
margin,
},
);
}
/// Attempts to find a cached layout for the specified node and layout inputs.
///
/// Uses the provided closure to compute the layout (and then stores the result in the cache) if no cached layout is found.
#[inline(always)]
pub fn compute_cached_layout<Tree: CacheTree + ?Sized, ComputeFunction>(
tree: &mut Tree,
node: NodeId,
inputs: LayoutInput,
mut compute_uncached: ComputeFunction,
) -> LayoutOutput
where
ComputeFunction: FnMut(&mut Tree, NodeId, LayoutInput) -> LayoutOutput,
{
debug_push_node!(node);
let LayoutInput { known_dimensions, available_space, run_mode, .. } = inputs;
// First we check if we have a cached result for the given input
let cache_entry = tree.cache_get(node, known_dimensions, available_space, run_mode);
if let Some(cached_size_and_baselines) = cache_entry {
debug_log_node!(known_dimensions, inputs.parent_size, available_space, run_mode, inputs.sizing_mode);
debug_log!("RESULT (CACHED)", dbg:cached_size_and_baselines.size);
debug_pop_node!();
return cached_size_and_baselines;
}
debug_log_node!(known_dimensions, inputs.parent_size, available_space, run_mode, inputs.sizing_mode);
let computed_size_and_baselines = compute_uncached(tree, node, inputs);
// Cache result
tree.cache_store(node, known_dimensions, available_space, run_mode, computed_size_and_baselines);
debug_log!("RESULT", dbg:computed_size_and_baselines.size);
debug_pop_node!();
computed_size_and_baselines
}
/// Rounds the calculated layout to exact pixel values
///
/// In order to ensure that no gaps in the layout are introduced we:
/// - Always round based on the cumulative x/y coordinates (relative to the viewport) rather than
/// parent-relative coordinates
/// - Compute width/height by first rounding the top/bottom/left/right and then computing the difference
/// rather than rounding the width/height directly
///
/// See <https://github.com/facebook/yoga/commit/aa5b296ac78f7a22e1aeaf4891243c6bb76488e2> for more context
///
/// In order to prevent innacuracies caused by rounding already-rounded values, we read from `unrounded_layout`
/// and write to `final_layout`.
pub fn round_layout(tree: &mut impl RoundTree, node_id: NodeId) {
return round_layout_inner(tree, node_id, 0.0, 0.0);
/// Recursive function to apply rounding to all descendents
fn round_layout_inner(tree: &mut impl RoundTree, node_id: NodeId, cumulative_x: f32, cumulative_y: f32) {
let unrounded_layout = *tree.get_unrounded_layout(node_id);
let mut layout = unrounded_layout;
let cumulative_x = cumulative_x + unrounded_layout.location.x;
let cumulative_y = cumulative_y + unrounded_layout.location.y;
layout.location.x = round(unrounded_layout.location.x);
layout.location.y = round(unrounded_layout.location.y);
layout.size.width = round(cumulative_x + unrounded_layout.size.width) - round(cumulative_x);
layout.size.height = round(cumulative_y + unrounded_layout.size.height) - round(cumulative_y);
layout.scrollbar_size.width = round(unrounded_layout.scrollbar_size.width);
layout.scrollbar_size.height = round(unrounded_layout.scrollbar_size.height);
layout.border.left = round(cumulative_x + unrounded_layout.border.left) - round(cumulative_x);
layout.border.right = round(cumulative_x + unrounded_layout.size.width)
- round(cumulative_x + unrounded_layout.size.width - unrounded_layout.border.right);
layout.border.top = round(cumulative_y + unrounded_layout.border.top) - round(cumulative_y);
layout.border.bottom = round(cumulative_y + unrounded_layout.size.height)
- round(cumulative_y + unrounded_layout.size.height - unrounded_layout.border.bottom);
layout.padding.left = round(cumulative_x + unrounded_layout.padding.left) - round(cumulative_x);
layout.padding.right = round(cumulative_x + unrounded_layout.size.width)
- round(cumulative_x + unrounded_layout.size.width - unrounded_layout.padding.right);
layout.padding.top = round(cumulative_y + unrounded_layout.padding.top) - round(cumulative_y);
layout.padding.bottom = round(cumulative_y + unrounded_layout.size.height)
- round(cumulative_y + unrounded_layout.size.height - unrounded_layout.padding.bottom);
#[cfg(feature = "content_size")]
round_content_size(&mut layout, unrounded_layout.content_size, cumulative_x, cumulative_y);
tree.set_final_layout(node_id, &layout);
let child_count = tree.child_count(node_id);
for index in 0..child_count {
let child = tree.get_child_id(node_id, index);
round_layout_inner(tree, child, cumulative_x, cumulative_y);
}
}
#[cfg(feature = "content_size")]
#[inline(always)]
/// Round content size variables.
/// This is split into a separate function to make it easier to feature flag.
fn round_content_size(
layout: &mut Layout,
unrounded_content_size: Size<f32>,
cumulative_x: f32,
cumulative_y: f32,
) {
layout.content_size.width = round(cumulative_x + unrounded_content_size.width) - round(cumulative_x);
layout.content_size.height = round(cumulative_y + unrounded_content_size.height) - round(cumulative_y);
}
}
/// Creates a layout for this node and its children, recursively.
/// Each hidden node has zero size and is placed at the origin
pub fn compute_hidden_layout(tree: &mut (impl LayoutPartialTree + CacheTree), node: NodeId) -> LayoutOutput {
// Clear cache and set zeroed-out layout for the node
tree.cache_clear(node);
tree.set_unrounded_layout(node, &Layout::with_order(0));
// Perform hidden layout on all children
for index in 0..tree.child_count(node) {
let child_id = tree.get_child_id(node, index);
tree.compute_child_layout(child_id, LayoutInput::HIDDEN);
}
LayoutOutput::HIDDEN
}
/// A module for unified re-exports of detailed layout info structs, used by low level API
#[cfg(feature = "detailed_layout_info")]
pub mod detailed_info {
#[cfg(feature = "grid")]
pub use super::grid::DetailedGridInfo;
}
#[cfg(test)]
mod tests {
use super::compute_hidden_layout;
use crate::geometry::{Point, Size};
use crate::style::{Display, Style};
use crate::TaffyTree;
#[test]
fn hidden_layout_should_hide_recursively() {
let mut taffy: TaffyTree<()> = TaffyTree::new();
let style: Style = Style { display: Display::Flex, size: Size::from_lengths(50.0, 50.0), ..Default::default() };
let grandchild_00 = taffy.new_leaf(style.clone()).unwrap();
let grandchild_01 = taffy.new_leaf(style.clone()).unwrap();
let child_00 = taffy.new_with_children(style.clone(), &[grandchild_00, grandchild_01]).unwrap();
let grandchild_02 = taffy.new_leaf(style.clone()).unwrap();
let child_01 = taffy.new_with_children(style.clone(), &[grandchild_02]).unwrap();
let root = taffy
.new_with_children(
Style { display: Display::None, size: Size::from_lengths(50.0, 50.0), ..Default::default() },
&[child_00, child_01],
)
.unwrap();
compute_hidden_layout(&mut taffy.as_layout_tree(), root);
// Whatever size and display-mode the nodes had previously,
// all layouts should resolve to ZERO due to the root's DISPLAY::NONE
for node in [root, child_00, child_01, grandchild_00, grandchild_01, grandchild_02] {
let layout = taffy.layout(node).unwrap();
assert_eq!(layout.size, Size::zero());
assert_eq!(layout.location, Point::zero());
}
}
}

729
vendor/taffy/src/geometry.rs vendored Normal file
View File

@@ -0,0 +1,729 @@
//! Geometric primitives useful for layout
use crate::util::sys::f32_max;
use crate::{style::Dimension, util::sys::f32_min};
use core::ops::{Add, Sub};
#[cfg(feature = "flexbox")]
use crate::style::FlexDirection;
/// The simple absolute horizontal and vertical axis
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum AbsoluteAxis {
/// The horizontal axis
Horizontal,
/// The vertical axis
Vertical,
}
impl AbsoluteAxis {
/// Returns the other variant of the enum
#[inline]
pub const fn other_axis(&self) -> Self {
match *self {
AbsoluteAxis::Horizontal => AbsoluteAxis::Vertical,
AbsoluteAxis::Vertical => AbsoluteAxis::Horizontal,
}
}
}
impl<T> Size<T> {
#[inline(always)]
/// Get either the width or height depending on the AbsoluteAxis passed in
pub fn get_abs(self, axis: AbsoluteAxis) -> T {
match axis {
AbsoluteAxis::Horizontal => self.width,
AbsoluteAxis::Vertical => self.height,
}
}
}
impl<T: Add> Rect<T> {
#[inline(always)]
/// Get either the width or height depending on the AbsoluteAxis passed in
pub fn grid_axis_sum(self, axis: AbsoluteAxis) -> <T as Add>::Output {
match axis {
AbsoluteAxis::Horizontal => self.left + self.right,
AbsoluteAxis::Vertical => self.top + self.bottom,
}
}
}
/// The CSS abstract axis
/// <https://www.w3.org/TR/css-writing-modes-3/#abstract-axes>
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum AbstractAxis {
/// The axis in the inline dimension, i.e. the horizontal axis in horizontal writing modes and the vertical axis in vertical writing modes.
Inline,
/// The axis in the block dimension, i.e. the vertical axis in horizontal writing modes and the horizontal axis in vertical writing modes.
Block,
}
impl AbstractAxis {
/// Returns the other variant of the enum
#[inline]
pub fn other(&self) -> AbstractAxis {
match *self {
AbstractAxis::Inline => AbstractAxis::Block,
AbstractAxis::Block => AbstractAxis::Inline,
}
}
/// Convert an `AbstractAxis` into an `AbsoluteAxis` naively assuming that the Inline axis is Horizontal
/// This is currently always true, but will change if Taffy ever implements the `writing_mode` property
#[inline]
pub fn as_abs_naive(&self) -> AbsoluteAxis {
match self {
AbstractAxis::Inline => AbsoluteAxis::Horizontal,
AbstractAxis::Block => AbsoluteAxis::Vertical,
}
}
}
/// Container that holds an item in each absolute axis without specifying
/// what kind of item it is.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct InBothAbsAxis<T> {
/// The item in the horizontal axis
pub horizontal: T,
/// The item in the vertical axis
pub vertical: T,
}
impl<T: Copy> InBothAbsAxis<T> {
#[cfg(feature = "grid")]
/// Get the contained item based on the AbsoluteAxis passed
pub fn get(&self, axis: AbsoluteAxis) -> T {
match axis {
AbsoluteAxis::Horizontal => self.horizontal,
AbsoluteAxis::Vertical => self.vertical,
}
}
}
/// An axis-aligned UI rectangle
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Rect<T> {
/// This can represent either the x-coordinate of the starting edge,
/// or the amount of padding on the starting side.
///
/// The starting edge is the left edge when working with LTR text,
/// and the right edge when working with RTL text.
pub left: T,
/// This can represent either the x-coordinate of the ending edge,
/// or the amount of padding on the ending side.
///
/// The ending edge is the right edge when working with LTR text,
/// and the left edge when working with RTL text.
pub right: T,
/// This can represent either the y-coordinate of the top edge,
/// or the amount of padding on the top side.
pub top: T,
/// This can represent either the y-coordinate of the bottom edge,
/// or the amount of padding on the bottom side.
pub bottom: T,
}
impl<U, T: Add<U>> Add<Rect<U>> for Rect<T> {
type Output = Rect<T::Output>;
fn add(self, rhs: Rect<U>) -> Self::Output {
Rect {
left: self.left + rhs.left,
right: self.right + rhs.right,
top: self.top + rhs.top,
bottom: self.bottom + rhs.bottom,
}
}
}
impl<T> Rect<T> {
/// Applies the function `f` to all four sides of the rect
///
/// When applied to the left and right sides, the width is used
/// as the second parameter of `f`.
/// When applied to the top or bottom sides, the height is used instead.
#[cfg(any(feature = "flexbox", feature = "block_layout"))]
pub(crate) fn zip_size<R, F, U>(self, size: Size<U>, f: F) -> Rect<R>
where
F: Fn(T, U) -> R,
U: Copy,
{
Rect {
left: f(self.left, size.width),
right: f(self.right, size.width),
top: f(self.top, size.height),
bottom: f(self.bottom, size.height),
}
}
/// Applies the function `f` to the left, right, top, and bottom properties
///
/// This is used to transform a `Rect<T>` into a `Rect<R>`.
pub fn map<R, F>(self, f: F) -> Rect<R>
where
F: Fn(T) -> R,
{
Rect { left: f(self.left), right: f(self.right), top: f(self.top), bottom: f(self.bottom) }
}
/// Returns a `Line<T>` representing the left and right properties of the Rect
pub fn horizontal_components(self) -> Line<T> {
Line { start: self.left, end: self.right }
}
/// Returns a `Line<T>` containing the top and bottom properties of the Rect
pub fn vertical_components(self) -> Line<T> {
Line { start: self.top, end: self.bottom }
}
}
impl<T, U> Rect<T>
where
T: Add<Output = U> + Copy + Clone,
{
/// The sum of [`Rect.start`](Rect) and [`Rect.end`](Rect)
///
/// This is typically used when computing total padding.
///
/// **NOTE:** this is *not* the width of the rectangle.
#[inline(always)]
pub(crate) fn horizontal_axis_sum(&self) -> U {
self.left + self.right
}
/// The sum of [`Rect.top`](Rect) and [`Rect.bottom`](Rect)
///
/// This is typically used when computing total padding.
///
/// **NOTE:** this is *not* the height of the rectangle.
#[inline(always)]
pub(crate) fn vertical_axis_sum(&self) -> U {
self.top + self.bottom
}
/// Both horizontal_axis_sum and vertical_axis_sum as a Size<T>
///
/// **NOTE:** this is *not* the width/height of the rectangle.
#[inline(always)]
#[allow(dead_code)] // Fixes spurious clippy warning: this function is used!
pub(crate) fn sum_axes(&self) -> Size<U> {
Size { width: self.horizontal_axis_sum(), height: self.vertical_axis_sum() }
}
/// The sum of the two fields of the [`Rect`] representing the main axis.
///
/// This is typically used when computing total padding.
///
/// If the [`FlexDirection`] is [`FlexDirection::Row`] or [`FlexDirection::RowReverse`], this is [`Rect::horizontal`].
/// Otherwise, this is [`Rect::vertical`].
#[cfg(feature = "flexbox")]
pub(crate) fn main_axis_sum(&self, direction: FlexDirection) -> U {
if direction.is_row() {
self.horizontal_axis_sum()
} else {
self.vertical_axis_sum()
}
}
/// The sum of the two fields of the [`Rect`] representing the cross axis.
///
/// If the [`FlexDirection`] is [`FlexDirection::Row`] or [`FlexDirection::RowReverse`], this is [`Rect::vertical`].
/// Otherwise, this is [`Rect::horizontal`].
#[cfg(feature = "flexbox")]
pub(crate) fn cross_axis_sum(&self, direction: FlexDirection) -> U {
if direction.is_row() {
self.vertical_axis_sum()
} else {
self.horizontal_axis_sum()
}
}
}
impl<T> Rect<T>
where
T: Copy + Clone,
{
/// The `start` or `top` value of the [`Rect`], from the perspective of the main layout axis
#[cfg(feature = "flexbox")]
pub(crate) fn main_start(&self, direction: FlexDirection) -> T {
if direction.is_row() {
self.left
} else {
self.top
}
}
/// The `end` or `bottom` value of the [`Rect`], from the perspective of the main layout axis
#[cfg(feature = "flexbox")]
pub(crate) fn main_end(&self, direction: FlexDirection) -> T {
if direction.is_row() {
self.right
} else {
self.bottom
}
}
/// The `start` or `top` value of the [`Rect`], from the perspective of the cross layout axis
#[cfg(feature = "flexbox")]
pub(crate) fn cross_start(&self, direction: FlexDirection) -> T {
if direction.is_row() {
self.top
} else {
self.left
}
}
/// The `end` or `bottom` value of the [`Rect`], from the perspective of the main layout axis
#[cfg(feature = "flexbox")]
pub(crate) fn cross_end(&self, direction: FlexDirection) -> T {
if direction.is_row() {
self.bottom
} else {
self.right
}
}
}
impl Rect<f32> {
/// Creates a new Rect with `0.0` as all parameters
pub const ZERO: Rect<f32> = Self { left: 0.0, right: 0.0, top: 0.0, bottom: 0.0 };
/// Creates a new Rect
#[must_use]
pub const fn new(start: f32, end: f32, top: f32, bottom: f32) -> Self {
Self { left: start, right: end, top, bottom }
}
}
/// An abstract "line". Represents any type that has a start and an end
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(default))]
pub struct Line<T> {
/// The start position of a line
pub start: T,
/// The end position of a line
pub end: T,
}
impl<T> Line<T> {
/// Applies the function `f` to both the width and height
///
/// This is used to transform a `Line<T>` into a `Line<R>`.
pub fn map<R, F>(self, f: F) -> Line<R>
where
F: Fn(T) -> R,
{
Line { start: f(self.start), end: f(self.end) }
}
}
impl Line<bool> {
/// A `Line<bool>` with both start and end set to `true`
pub const TRUE: Self = Line { start: true, end: true };
/// A `Line<bool>` with both start and end set to `false`
pub const FALSE: Self = Line { start: false, end: false };
}
impl<T: Add + Copy> Line<T> {
/// Adds the start and end values together and returns the result
pub fn sum(&self) -> <T as Add>::Output {
self.start + self.end
}
}
/// The width and height of a [`Rect`]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Size<T> {
/// The x extent of the rectangle
pub width: T,
/// The y extent of the rectangle
pub height: T,
}
// Generic Add impl for Size<T> + Size<U> where T + U has an Add impl
impl<U, T: Add<U>> Add<Size<U>> for Size<T> {
type Output = Size<<T as Add<U>>::Output>;
fn add(self, rhs: Size<U>) -> Self::Output {
Size { width: self.width + rhs.width, height: self.height + rhs.height }
}
}
// Generic Sub impl for Size<T> + Size<U> where T + U has an Sub impl
impl<U, T: Sub<U>> Sub<Size<U>> for Size<T> {
type Output = Size<<T as Sub<U>>::Output>;
fn sub(self, rhs: Size<U>) -> Self::Output {
Size { width: self.width - rhs.width, height: self.height - rhs.height }
}
}
// Note: we allow dead_code here as we want to provide a complete API of helpers that is symmetrical in all axes,
// but sometimes we only currently have a use for the helper in a single axis
#[allow(dead_code)]
impl<T> Size<T> {
/// Applies the function `f` to both the width and height
///
/// This is used to transform a `Size<T>` into a `Size<R>`.
pub fn map<R, F>(self, f: F) -> Size<R>
where
F: Fn(T) -> R,
{
Size { width: f(self.width), height: f(self.height) }
}
/// Applies the function `f` to the width
pub fn map_width<F>(self, f: F) -> Size<T>
where
F: Fn(T) -> T,
{
Size { width: f(self.width), height: self.height }
}
/// Applies the function `f` to the height
pub fn map_height<F>(self, f: F) -> Size<T>
where
F: Fn(T) -> T,
{
Size { width: self.width, height: f(self.height) }
}
/// Applies the function `f` to both the width and height
/// of this value and another passed value
pub fn zip_map<Other, Ret, Func>(self, other: Size<Other>, f: Func) -> Size<Ret>
where
Func: Fn(T, Other) -> Ret,
{
Size { width: f(self.width, other.width), height: f(self.height, other.height) }
}
/// Sets the extent of the main layout axis
///
/// Whether this is the width or height depends on the `direction` provided
#[cfg(feature = "flexbox")]
pub(crate) fn set_main(&mut self, direction: FlexDirection, value: T) {
if direction.is_row() {
self.width = value
} else {
self.height = value
}
}
/// Sets the extent of the cross layout axis
///
/// Whether this is the width or height depends on the `direction` provided
#[cfg(feature = "flexbox")]
pub(crate) fn set_cross(&mut self, direction: FlexDirection, value: T) {
if direction.is_row() {
self.height = value
} else {
self.width = value
}
}
/// Creates a new value of type Self with the main axis set to value provided
///
/// Whether this is the width or height depends on the `direction` provided
#[cfg(feature = "flexbox")]
pub(crate) fn with_main(self, direction: FlexDirection, value: T) -> Self {
let mut new = self;
if direction.is_row() {
new.width = value
} else {
new.height = value
}
new
}
/// Creates a new value of type Self with the cross axis set to value provided
///
/// Whether this is the width or height depends on the `direction` provided
#[cfg(feature = "flexbox")]
pub(crate) fn with_cross(self, direction: FlexDirection, value: T) -> Self {
let mut new = self;
if direction.is_row() {
new.height = value
} else {
new.width = value
}
new
}
/// Creates a new value of type Self with the main axis modified by the callback provided
///
/// Whether this is the width or height depends on the `direction` provided
#[cfg(feature = "flexbox")]
pub(crate) fn map_main(self, direction: FlexDirection, mapper: impl FnOnce(T) -> T) -> Self {
let mut new = self;
if direction.is_row() {
new.width = mapper(new.width);
} else {
new.height = mapper(new.height);
}
new
}
/// Creates a new value of type Self with the cross axis modified by the callback provided
///
/// Whether this is the width or height depends on the `direction` provided
#[cfg(feature = "flexbox")]
pub(crate) fn map_cross(self, direction: FlexDirection, mapper: impl FnOnce(T) -> T) -> Self {
let mut new = self;
if direction.is_row() {
new.height = mapper(new.height);
} else {
new.width = mapper(new.width);
}
new
}
/// Gets the extent of the main layout axis
///
/// Whether this is the width or height depends on the `direction` provided
#[cfg(feature = "flexbox")]
pub(crate) fn main(self, direction: FlexDirection) -> T {
if direction.is_row() {
self.width
} else {
self.height
}
}
/// Gets the extent of the cross layout axis
///
/// Whether this is the width or height depends on the `direction` provided
#[cfg(feature = "flexbox")]
pub(crate) fn cross(self, direction: FlexDirection) -> T {
if direction.is_row() {
self.height
} else {
self.width
}
}
/// Gets the extent of the specified layout axis
/// Whether this is the width or height depends on the `GridAxis` provided
#[cfg(feature = "grid")]
pub(crate) fn get(self, axis: AbstractAxis) -> T {
match axis {
AbstractAxis::Inline => self.width,
AbstractAxis::Block => self.height,
}
}
/// Sets the extent of the specified layout axis
/// Whether this is the width or height depends on the `GridAxis` provided
#[cfg(feature = "grid")]
pub(crate) fn set(&mut self, axis: AbstractAxis, value: T) {
match axis {
AbstractAxis::Inline => self.width = value,
AbstractAxis::Block => self.height = value,
}
}
}
impl Size<f32> {
/// A [`Size`] with zero width and height
pub const ZERO: Size<f32> = Self { width: 0.0, height: 0.0 };
/// Applies f32_max to each component separately
#[inline(always)]
pub fn f32_max(self, rhs: Size<f32>) -> Size<f32> {
Size { width: f32_max(self.width, rhs.width), height: f32_max(self.height, rhs.height) }
}
/// Applies f32_min to each component separately
#[inline(always)]
pub fn f32_min(self, rhs: Size<f32>) -> Size<f32> {
Size { width: f32_min(self.width, rhs.width), height: f32_min(self.height, rhs.height) }
}
/// Return true if both width and height are greater than 0 else false
#[inline(always)]
pub fn has_non_zero_area(self) -> bool {
self.width > 0.0 && self.height > 0.0
}
}
impl Size<Option<f32>> {
/// A [`Size`] with `None` width and height
pub const NONE: Size<Option<f32>> = Self { width: None, height: None };
/// A [`Size<Option<f32>>`] with `Some(width)` and `Some(height)` as parameters
#[must_use]
pub const fn new(width: f32, height: f32) -> Self {
Size { width: Some(width), height: Some(height) }
}
/// Creates a new [`Size<Option<f32>>`] with either the width or height set based on the provided `direction`
#[cfg(feature = "flexbox")]
pub fn from_cross(direction: FlexDirection, value: Option<f32>) -> Self {
let mut new = Self::NONE;
if direction.is_row() {
new.height = value
} else {
new.width = value
}
new
}
/// Applies aspect_ratio (if one is supplied) to the Size:
/// - If width is `Some` but height is `None`, then height is computed from width and aspect_ratio
/// - If height is `Some` but width is `None`, then width is computed from height and aspect_ratio
///
/// If aspect_ratio is `None` then this function simply returns self.
pub fn maybe_apply_aspect_ratio(self, aspect_ratio: Option<f32>) -> Size<Option<f32>> {
match aspect_ratio {
Some(ratio) => match (self.width, self.height) {
(Some(width), None) => Size { width: Some(width), height: Some(width / ratio) },
(None, Some(height)) => Size { width: Some(height * ratio), height: Some(height) },
_ => self,
},
None => self,
}
}
}
impl<T> Size<Option<T>> {
/// Performs Option::unwrap_or on each component separately
pub fn unwrap_or(self, alt: Size<T>) -> Size<T> {
Size { width: self.width.unwrap_or(alt.width), height: self.height.unwrap_or(alt.height) }
}
/// Performs Option::or on each component separately
pub fn or(self, alt: Size<Option<T>>) -> Size<Option<T>> {
Size { width: self.width.or(alt.width), height: self.height.or(alt.height) }
}
/// Return true if both components are Some, else false.
#[inline(always)]
pub fn both_axis_defined(&self) -> bool {
self.width.is_some() && self.height.is_some()
}
}
impl Size<Dimension> {
/// Generates a [`Size<Dimension>`] using [`Dimension::Length`] values
#[must_use]
pub const fn from_lengths(width: f32, height: f32) -> Self {
Size { width: Dimension::Length(width), height: Dimension::Length(height) }
}
/// Generates a [`Size<Dimension>`] using [`Dimension::Percent`] values
#[must_use]
pub const fn from_percent(width: f32, height: f32) -> Self {
Size { width: Dimension::Percent(width), height: Dimension::Percent(height) }
}
}
/// A 2-dimensional coordinate.
///
/// When used in association with a [`Rect`], represents the top-left corner.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Point<T> {
/// The x-coordinate
pub x: T,
/// The y-coordinate
pub y: T,
}
impl Point<f32> {
/// A [`Point`] with values (0,0), representing the origin
pub const ZERO: Self = Self { x: 0.0, y: 0.0 };
}
impl Point<Option<f32>> {
/// A [`Point`] with values (None, None)
pub const NONE: Self = Self { x: None, y: None };
}
// Generic Add impl for Point<T> + Point<U> where T + U has an Add impl
impl<U, T: Add<U>> Add<Point<U>> for Point<T> {
type Output = Point<<T as Add<U>>::Output>;
fn add(self, rhs: Point<U>) -> Self::Output {
Point { x: self.x + rhs.x, y: self.y + rhs.y }
}
}
impl<T> Point<T> {
/// Applies the function `f` to both the x and y
///
/// This is used to transform a `Point<T>` into a `Point<R>`.
pub fn map<R, F>(self, f: F) -> Point<R>
where
F: Fn(T) -> R,
{
Point { x: f(self.x), y: f(self.y) }
}
/// Gets the extent of the specified layout axis
/// Whether this is the width or height depends on the `GridAxis` provided
#[cfg(feature = "grid")]
pub fn get(self, axis: AbstractAxis) -> T {
match axis {
AbstractAxis::Inline => self.x,
AbstractAxis::Block => self.y,
}
}
/// Swap x and y components
pub fn transpose(self) -> Point<T> {
Point { x: self.y, y: self.x }
}
/// Sets the extent of the specified layout axis
/// Whether this is the width or height depends on the `GridAxis` provided
#[cfg(feature = "grid")]
pub fn set(&mut self, axis: AbstractAxis, value: T) {
match axis {
AbstractAxis::Inline => self.x = value,
AbstractAxis::Block => self.y = value,
}
}
/// Gets the component in the main layout axis
///
/// Whether this is the x or y depends on the `direction` provided
#[cfg(feature = "flexbox")]
pub(crate) fn main(self, direction: FlexDirection) -> T {
if direction.is_row() {
self.x
} else {
self.y
}
}
/// Gets the component in the cross layout axis
///
/// Whether this is the x or y depends on the `direction` provided
#[cfg(feature = "flexbox")]
pub(crate) fn cross(self, direction: FlexDirection) -> T {
if direction.is_row() {
self.y
} else {
self.x
}
}
}
impl<T> From<Point<T>> for Size<T> {
fn from(value: Point<T>) -> Self {
Size { width: value.x, height: value.y }
}
}
/// Generic struct which holds a "min" value and a "max" value
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct MinMax<Min, Max> {
/// The value representing the minimum
pub min: Min,
/// The value representing the maximum
pub max: Max,
}

125
vendor/taffy/src/lib.rs vendored Normal file
View File

@@ -0,0 +1,125 @@
//! # Taffy
//!
//! Taffy is a flexible, high-performance library for **UI layout**.
//! It currently implements the Flexbox, Grid and Block layout algorithms from the CSS specification. Support for other paradigms is planned.
//! For more information on this and other future development plans see the [roadmap issue](https://github.com/DioxusLabs/taffy/issues/345).
//!
//! ## Architecture
//!
//! Taffy is based on a tree of "UI nodes" similar to the tree of DOM nodes that one finds in web-based UI. Each node has:
//! - A [`Style`] struct which holds a set of CSS styles which function as the primary input to the layout computations.
//! - A [`Layout`] struct containing a position (x/y) and a size (width/height) which function as the output of the layout computations.
//! - Optionally:
//! - A `Vec` set of child nodes
//! - "Context": arbitrary user-defined data (which you can access when using a "measure function" to integrate Taffy with other kinds of layout such as text layout)
//!
//! Usage of Taffy consists of constructing a tree of UI nodes (with associated styles, children and context), then calling function(s)
//! from Taffy to translate those styles, parent-child relationships and measure functions into a size and position in 2d space for each node
//! in the tree.
//!
//! ## High-level API vs. Low-level API
//!
//! Taffy has two APIs: a high-level API that is simpler and easier to get started with, and a low-level API that is more flexible gives greater control.
//! We would generally recommend the high-level API for users using Taffy standalone and the low-level API for users wanting to embed Taffy as part of
//! a wider layout system or as part of a UI framework that already has it's own node/widget tree representation.
//!
//! ### High-level API
//!
//! The high-level API consists of the [`TaffyTree`] struct which contains a tree implementation and provides methods that allow you to construct
//! a tree of UI nodes. Once constructed, you can call the [`compute_layout_with_measure`](crate::TaffyTree::compute_layout_with_measure) method to compute the layout (passing in a "measure function" closure which is used to compute the size of leaf nodes), and then access
//! the layout of each node using the [`layout`](crate::TaffyTree::layout) method.
//!
//! When using the high-level API, Taffy will take care of node storage, caching and dispatching to the correct layout algorithm for a given node for you.
//! See the [`TaffyTree`] struct for more details on this API.
//!
//! Examples which show usage of the high-level API include:
//!
//! - [basic](https://github.com/DioxusLabs/taffy/blob/main/examples/basic.rs)
//! - [flexbox_gap](https://github.com/DioxusLabs/taffy/blob/main/examples/flexbox_gap.rs)
//! - [grid_holy_grail](https://github.com/DioxusLabs/taffy/blob/main/examples/grid_holy_grail.rs)
//! - [measure](https://github.com/DioxusLabs/taffy/blob/main/examples/measure.rs)
//! - [cosmic_text](https://github.com/DioxusLabs/taffy/blob/main/examples/cosmic_text.rs)
//!
//! In particular, the "measure" example shows how to integrate Taffy layout with other layout modalities such as text or image layout when using the high level API.
//!
//! ### Low-level API
//!
//! The low-level API consists of a [set of traits](crate::tree::traits) (notably the [`LayoutPartialTree`] trait) which define an interface behind which you must implement your own
//! tree implementation, and a [set of functions](crate::compute) such as [`compute_flexbox_layout`] and [`compute_grid_layout`] which implement the layout algorithms (for a single node at a time), and are designed to be flexible
//! and easy to integrate into a wider layout or UI system.
//!
//! When using this API, you must handle node storage, caching, and dispatching to the correct layout algorithm for a given node yourself.
//! See the [`crate::tree::traits`] module for more details on this API.
//!
//! Examples which show usage of the low-level API are:
//!
//! - [custom_tree_vec](https://github.com/DioxusLabs/taffy/blob/main/examples/custom_tree_vec.rs) which implements a custom Taffy tree using a `Vec` as an arena with NodeId's being index's into the Vec.
//! - [custom_tree_owned_partial](https://github.com/DioxusLabs/taffy/blob/main/examples/custom_tree_owned_partial.rs) which implements a custom Taffy tree using directly owned children with NodeId's being index's into vec on parent node.
//! - [custom_tree_owned_unsafe](https://github.com/DioxusLabs/taffy/blob/main/examples/custom_tree_owned_unsafe.rs) which implements a custom Taffy tree using directly owned children with NodeId's being pointers.
// document the feature flags for the crate by extracting the comments from Cargo.toml
#![cfg_attr(feature = "document-features", doc = document_features::document_features!())]
// annotate items with their required features (gated by docsrs flag as this requires the nightly toolchain)
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![cfg_attr(not(feature = "std"), no_std)]
#![deny(unsafe_code)]
#![forbid(unsafe_code)]
#![warn(missing_docs)]
#![warn(clippy::missing_docs_in_private_items)]
// We always need std for the tests
// See <https://github.com/la10736/rstest/issues/149#issuecomment-1156402989>
#[cfg(all(test, not(feature = "std")))]
#[macro_use]
extern crate std;
#[cfg(all(not(feature = "std"), feature = "alloc"))]
extern crate alloc;
#[cfg_attr(feature = "serde", macro_use)]
#[cfg(feature = "serde")]
extern crate serde;
pub mod compute;
pub mod geometry;
pub mod prelude;
pub mod style;
pub mod style_helpers;
pub mod tree;
#[macro_use]
pub mod util;
mod readme_doctest {
#![doc = include_str!("../README.md")]
}
#[cfg(feature = "block_layout")]
#[doc(inline)]
pub use crate::compute::compute_block_layout;
#[cfg(feature = "flexbox")]
#[doc(inline)]
pub use crate::compute::compute_flexbox_layout;
#[cfg(feature = "grid")]
#[doc(inline)]
pub use crate::compute::compute_grid_layout;
#[cfg(feature = "detailed_layout_info")]
pub use crate::compute::detailed_info::*;
#[doc(inline)]
pub use crate::compute::{
compute_cached_layout, compute_hidden_layout, compute_leaf_layout, compute_root_layout, round_layout,
};
#[doc(inline)]
pub use crate::style::Style;
#[doc(inline)]
pub use crate::tree::traits::*;
#[cfg(feature = "taffy_tree")]
#[doc(inline)]
pub use crate::tree::TaffyTree;
#[cfg(feature = "std")]
#[doc(inline)]
pub use crate::util::print_tree;
pub use crate::geometry::*;
pub use crate::style::*;
pub use crate::tree::*;
pub use crate::util::*;

30
vendor/taffy/src/prelude.rs vendored Normal file
View File

@@ -0,0 +1,30 @@
//! Commonly used types
pub use crate::{
geometry::{Line, Rect, Size},
style::{
AlignContent, AlignItems, AlignSelf, AvailableSpace, BoxSizing, Dimension, Display, JustifyContent,
JustifyItems, JustifySelf, LengthPercentage, LengthPercentageAuto, Position, Style,
},
style_helpers::{
auto, fit_content, length, max_content, min_content, percent, zero, FromFlex, FromLength, FromPercent,
TaffyAuto, TaffyFitContent, TaffyMaxContent, TaffyMinContent, TaffyZero,
},
tree::{Layout, LayoutPartialTree, NodeId, PrintTree, RoundTree, TraversePartialTree, TraverseTree},
};
#[cfg(feature = "flexbox")]
pub use crate::style::{FlexDirection, FlexWrap};
#[cfg(feature = "grid")]
pub use crate::style::{
GridAutoFlow, GridPlacement, GridTrackRepetition, MaxTrackSizingFunction, MinTrackSizingFunction,
NonRepeatedTrackSizingFunction, TrackSizingFunction,
};
#[cfg(feature = "grid")]
pub use crate::style_helpers::{
evenly_sized_tracks, flex, fr, line, minmax, repeat, span, TaffyGridLine, TaffyGridSpan,
};
#[cfg(feature = "taffy_tree")]
pub use crate::TaffyTree;

97
vendor/taffy/src/style/alignment.rs vendored Normal file
View File

@@ -0,0 +1,97 @@
//! Style types for controlling alignment
/// Used to control how child nodes are aligned.
/// For Flexbox it controls alignment in the cross axis
/// For Grid it controls alignment in the block axis
///
/// [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/align-items)
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum AlignItems {
/// Items are packed toward the start of the axis
Start,
/// Items are packed toward the end of the axis
End,
/// Items are packed towards the flex-relative start of the axis.
///
/// For flex containers with flex_direction RowReverse or ColumnReverse this is equivalent
/// to End. In all other cases it is equivalent to Start.
FlexStart,
/// Items are packed towards the flex-relative end of the axis.
///
/// For flex containers with flex_direction RowReverse or ColumnReverse this is equivalent
/// to Start. In all other cases it is equivalent to End.
FlexEnd,
/// Items are packed along the center of the cross axis
Center,
/// Items are aligned such as their baselines align
Baseline,
/// Stretch to fill the container
Stretch,
}
/// Used to control how child nodes are aligned.
/// Does not apply to Flexbox, and will be ignored if specified on a flex container
/// For Grid it controls alignment in the inline axis
///
/// [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/justify-items)
pub type JustifyItems = AlignItems;
/// Controls alignment of an individual node
///
/// Overrides the parent Node's `AlignItems` property.
/// For Flexbox it controls alignment in the cross axis
/// For Grid it controls alignment in the block axis
///
/// [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/align-self)
pub type AlignSelf = AlignItems;
/// Controls alignment of an individual node
///
/// Overrides the parent Node's `JustifyItems` property.
/// Does not apply to Flexbox, and will be ignored if specified on a flex child
/// For Grid it controls alignment in the inline axis
///
/// [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/justify-self)
pub type JustifySelf = AlignItems;
/// Sets the distribution of space between and around content items
/// For Flexbox it controls alignment in the cross axis
/// For Grid it controls alignment in the block axis
///
/// [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/align-content)
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum AlignContent {
/// Items are packed toward the start of the axis
Start,
/// Items are packed toward the end of the axis
End,
/// Items are packed towards the flex-relative start of the axis.
///
/// For flex containers with flex_direction RowReverse or ColumnReverse this is equivalent
/// to End. In all other cases it is equivalent to Start.
FlexStart,
/// Items are packed towards the flex-relative end of the axis.
///
/// For flex containers with flex_direction RowReverse or ColumnReverse this is equivalent
/// to Start. In all other cases it is equivalent to End.
FlexEnd,
/// Items are centered around the middle of the axis
Center,
/// Items are stretched to fill the container
Stretch,
/// The first and last items are aligned flush with the edges of the container (no gap)
/// The gap between items is distributed evenly.
SpaceBetween,
/// The gap between the first and last items is exactly THE SAME as the gap between items.
/// The gaps are distributed evenly
SpaceEvenly,
/// The gap between the first and last items is exactly HALF the gap between items.
/// The gaps are distributed evenly in proportion to these ratios.
SpaceAround,
}
/// Sets the distribution of space between and around content items
/// For Flexbox it controls alignment in the main axis
/// For Grid it controls alignment in the inline axis
///
/// [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/justify-content)
pub type JustifyContent = AlignContent;

35
vendor/taffy/src/style/block.rs vendored Normal file
View File

@@ -0,0 +1,35 @@
//! Style types for Block layout
use crate::{CoreStyle, Style};
/// The set of styles required for a Block layout container
pub trait BlockContainerStyle: CoreStyle {
/// Defines which row in the grid the item should start and end at
#[inline(always)]
fn text_align(&self) -> TextAlign {
Style::DEFAULT.text_align
}
}
/// The set of styles required for a Block layout item (child of a Block container)
pub trait BlockItemStyle: CoreStyle {
/// Whether the item is a table. Table children are handled specially in block layout.
#[inline(always)]
fn is_table(&self) -> bool {
false
}
}
/// Used by block layout to implement the legacy behaviour of `<center>` and `<div align="left | right | center">`
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum TextAlign {
/// No special legacy text align behaviour.
#[default]
Auto,
/// Corresponds to `-webkit-left` or `-moz-left` in browsers
LegacyLeft,
/// Corresponds to `-webkit-right` or `-moz-right` in browsers
LegacyRight,
/// Corresponds to `-webkit-center` or `-moz-center` in browsers
LegacyCenter,
}

322
vendor/taffy/src/style/dimension.rs vendored Normal file
View File

@@ -0,0 +1,322 @@
//! Style types for representing lengths / sizes
use crate::geometry::{Rect, Size};
use crate::style_helpers::{FromLength, FromPercent, TaffyAuto, TaffyMaxContent, TaffyMinContent, TaffyZero};
use crate::util::sys::abs;
/// A unit of linear measurement
///
/// This is commonly combined with [`Rect`], [`Point`](crate::geometry::Point) and [`Size<T>`].
#[derive(Copy, Clone, PartialEq, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum LengthPercentage {
/// An absolute length in some abstract units. Users of Taffy may define what they correspond
/// to in their application (pixels, logical pixels, mm, etc) as they see fit.
Length(f32),
/// A percentage length relative to the size of the containing block.
///
/// **NOTE: percentages are represented as a f32 value in the range [0.0, 1.0] NOT the range [0.0, 100.0]**
Percent(f32),
}
impl TaffyZero for LengthPercentage {
const ZERO: Self = Self::Length(0.0);
}
impl FromLength for LengthPercentage {
fn from_length<Input: Into<f32> + Copy>(value: Input) -> Self {
Self::Length(value.into())
}
}
impl FromPercent for LengthPercentage {
fn from_percent<Input: Into<f32> + Copy>(percent: Input) -> Self {
Self::Percent(percent.into())
}
}
/// A unit of linear measurement
///
/// This is commonly combined with [`Rect`], [`Point`](crate::geometry::Point) and [`Size<T>`].
#[derive(Copy, Clone, PartialEq, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum LengthPercentageAuto {
/// An absolute length in some abstract units. Users of Taffy may define what they correspond
/// to in their application (pixels, logical pixels, mm, etc) as they see fit.
Length(f32),
/// A percentage length relative to the size of the containing block.
///
/// **NOTE: percentages are represented as a f32 value in the range [0.0, 1.0] NOT the range [0.0, 100.0]**
Percent(f32),
/// The dimension should be automatically computed
Auto,
}
impl TaffyZero for LengthPercentageAuto {
const ZERO: Self = Self::Length(0.0);
}
impl TaffyAuto for LengthPercentageAuto {
const AUTO: Self = Self::Auto;
}
impl FromLength for LengthPercentageAuto {
fn from_length<Input: Into<f32> + Copy>(value: Input) -> Self {
Self::Length(value.into())
}
}
impl FromPercent for LengthPercentageAuto {
fn from_percent<Input: Into<f32> + Copy>(percent: Input) -> Self {
Self::Percent(percent.into())
}
}
impl From<LengthPercentage> for LengthPercentageAuto {
fn from(input: LengthPercentage) -> Self {
match input {
LengthPercentage::Length(value) => Self::Length(value),
LengthPercentage::Percent(value) => Self::Percent(value),
}
}
}
impl LengthPercentageAuto {
/// Returns:
/// - Some(length) for Length variants
/// - Some(resolved) using the provided context for Percent variants
/// - None for Auto variants
#[inline(always)]
pub fn resolve_to_option(self, context: f32) -> Option<f32> {
match self {
Self::Length(length) => Some(length),
Self::Percent(percent) => Some(context * percent),
Self::Auto => None,
}
}
/// Returns true if value is LengthPercentageAuto::Auto
#[inline(always)]
pub fn is_auto(self) -> bool {
self == Self::Auto
}
}
/// A unit of linear measurement
///
/// This is commonly combined with [`Rect`], [`Point`](crate::geometry::Point) and [`Size<T>`].
#[derive(Copy, Clone, PartialEq, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Dimension {
/// An absolute length in some abstract units. Users of Taffy may define what they correspond
/// to in their application (pixels, logical pixels, mm, etc) as they see fit.
Length(f32),
/// A percentage length relative to the size of the containing block.
///
/// **NOTE: percentages are represented as a f32 value in the range [0.0, 1.0] NOT the range [0.0, 100.0]**
Percent(f32),
/// The dimension should be automatically computed
Auto,
}
impl TaffyZero for Dimension {
const ZERO: Self = Self::Length(0.0);
}
impl TaffyAuto for Dimension {
const AUTO: Self = Self::Auto;
}
impl FromLength for Dimension {
fn from_length<Input: Into<f32> + Copy>(value: Input) -> Self {
Self::Length(value.into())
}
}
impl FromPercent for Dimension {
fn from_percent<Input: Into<f32> + Copy>(percent: Input) -> Self {
Self::Percent(percent.into())
}
}
impl From<LengthPercentage> for Dimension {
fn from(input: LengthPercentage) -> Self {
match input {
LengthPercentage::Length(value) => Self::Length(value),
LengthPercentage::Percent(value) => Self::Percent(value),
}
}
}
impl From<LengthPercentageAuto> for Dimension {
fn from(input: LengthPercentageAuto) -> Self {
match input {
LengthPercentageAuto::Length(value) => Self::Length(value),
LengthPercentageAuto::Percent(value) => Self::Percent(value),
LengthPercentageAuto::Auto => Self::Auto,
}
}
}
impl Dimension {
/// Get Length value if value is Length variant
#[cfg(feature = "grid")]
pub fn into_option(self) -> Option<f32> {
match self {
Dimension::Length(value) => Some(value),
_ => None,
}
}
}
impl Rect<Dimension> {
/// Create a new Rect with [`Dimension::Length`]
#[must_use]
pub const fn from_length(start: f32, end: f32, top: f32, bottom: f32) -> Self {
Rect {
left: Dimension::Length(start),
right: Dimension::Length(end),
top: Dimension::Length(top),
bottom: Dimension::Length(bottom),
}
}
/// Create a new Rect with [`Dimension::Percent`]
#[must_use]
pub const fn from_percent(start: f32, end: f32, top: f32, bottom: f32) -> Self {
Rect {
left: Dimension::Percent(start),
right: Dimension::Percent(end),
top: Dimension::Percent(top),
bottom: Dimension::Percent(bottom),
}
}
}
/// The amount of space available to a node in a given axis
/// <https://www.w3.org/TR/css-sizing-3/#available>
#[derive(Copy, Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub enum AvailableSpace {
/// The amount of space available is the specified number of pixels
Definite(f32),
/// The amount of space available is indefinite and the node should be laid out under a min-content constraint
MinContent,
/// The amount of space available is indefinite and the node should be laid out under a max-content constraint
MaxContent,
}
impl TaffyZero for AvailableSpace {
const ZERO: Self = Self::Definite(0.0);
}
impl TaffyMaxContent for AvailableSpace {
const MAX_CONTENT: Self = Self::MaxContent;
}
impl TaffyMinContent for AvailableSpace {
const MIN_CONTENT: Self = Self::MinContent;
}
impl FromLength for AvailableSpace {
fn from_length<Input: Into<f32> + Copy>(value: Input) -> Self {
Self::Definite(value.into())
}
}
impl AvailableSpace {
/// Returns true for definite values, else false
pub fn is_definite(self) -> bool {
matches!(self, AvailableSpace::Definite(_))
}
/// Convert to Option
/// Definite values become Some(value). Constraints become None.
pub fn into_option(self) -> Option<f32> {
match self {
AvailableSpace::Definite(value) => Some(value),
_ => None,
}
}
/// Return the definite value or a default value
pub fn unwrap_or(self, default: f32) -> f32 {
self.into_option().unwrap_or(default)
}
/// Return the definite value. Panic is the value is not definite.
#[track_caller]
pub fn unwrap(self) -> f32 {
self.into_option().unwrap()
}
/// Return self if definite or a default value
pub fn or(self, default: AvailableSpace) -> AvailableSpace {
match self {
AvailableSpace::Definite(_) => self,
_ => default,
}
}
/// Return self if definite or a the result of the default value callback
pub fn or_else(self, default_cb: impl FnOnce() -> AvailableSpace) -> AvailableSpace {
match self {
AvailableSpace::Definite(_) => self,
_ => default_cb(),
}
}
/// Return the definite value or the result of the default value callback
pub fn unwrap_or_else(self, default_cb: impl FnOnce() -> f32) -> f32 {
self.into_option().unwrap_or_else(default_cb)
}
/// If passed value is Some then return AvailableSpace::Definite containing that value, else return self
pub fn maybe_set(self, value: Option<f32>) -> AvailableSpace {
match value {
Some(value) => AvailableSpace::Definite(value),
None => self,
}
}
/// If passed value is Some then return AvailableSpace::Definite containing that value, else return self
pub fn map_definite_value(self, map_function: impl FnOnce(f32) -> f32) -> AvailableSpace {
match self {
AvailableSpace::Definite(value) => AvailableSpace::Definite(map_function(value)),
_ => self,
}
}
/// Compute free_space given the passed used_space
pub fn compute_free_space(&self, used_space: f32) -> f32 {
match self {
AvailableSpace::MaxContent => f32::INFINITY,
AvailableSpace::MinContent => 0.0,
AvailableSpace::Definite(available_space) => available_space - used_space,
}
}
/// Compare equality with another AvailableSpace, treating definite values
/// that are within f32::EPSILON of each other as equal
pub fn is_roughly_equal(self, other: AvailableSpace) -> bool {
use AvailableSpace::*;
match (self, other) {
(Definite(a), Definite(b)) => abs(a - b) < f32::EPSILON,
(MinContent, MinContent) => true,
(MaxContent, MaxContent) => true,
_ => false,
}
}
}
impl From<f32> for AvailableSpace {
fn from(value: f32) -> Self {
Self::Definite(value)
}
}
impl From<Option<f32>> for AvailableSpace {
fn from(option: Option<f32>) -> Self {
match option {
Some(value) => Self::Definite(value),
None => Self::MaxContent,
}
}
}
impl Size<AvailableSpace> {
/// Convert `Size<AvailableSpace>` into `Size<Option<f32>>`
pub fn into_options(self) -> Size<Option<f32>> {
Size { width: self.width.into_option(), height: self.height.into_option() }
}
/// If passed value is Some then return AvailableSpace::Definite containing that value, else return self
pub fn maybe_set(self, value: Size<Option<f32>>) -> Size<AvailableSpace> {
Size { width: self.width.maybe_set(value.width), height: self.height.maybe_set(value.height) }
}
}

198
vendor/taffy/src/style/flex.rs vendored Normal file
View File

@@ -0,0 +1,198 @@
//! Style types for Flexbox layout
use super::{AlignContent, AlignItems, AlignSelf, CoreStyle, Dimension, JustifyContent, LengthPercentage, Style};
use crate::geometry::Size;
/// The set of styles required for a Flexbox container
pub trait FlexboxContainerStyle: CoreStyle {
/// Which direction does the main axis flow in?
#[inline(always)]
fn flex_direction(&self) -> FlexDirection {
Style::DEFAULT.flex_direction
}
/// Should elements wrap, or stay in a single line?
#[inline(always)]
fn flex_wrap(&self) -> FlexWrap {
Style::DEFAULT.flex_wrap
}
/// How large should the gaps between items in a grid or flex container be?
#[inline(always)]
fn gap(&self) -> Size<LengthPercentage> {
Style::DEFAULT.gap
}
// Alignment properties
/// How should content contained within this item be aligned in the cross/block axis
#[inline(always)]
fn align_content(&self) -> Option<AlignContent> {
Style::DEFAULT.align_content
}
/// How this node's children aligned in the cross/block axis?
#[inline(always)]
fn align_items(&self) -> Option<AlignItems> {
Style::DEFAULT.align_items
}
/// How this node's children should be aligned in the inline axis
#[inline(always)]
fn justify_content(&self) -> Option<JustifyContent> {
Style::DEFAULT.justify_content
}
}
/// The set of styles required for a Flexbox item (child of a Flexbox container)
pub trait FlexboxItemStyle: CoreStyle {
/// Sets the initial main axis size of the item
#[inline(always)]
fn flex_basis(&self) -> Dimension {
Style::DEFAULT.flex_basis
}
/// The relative rate at which this item grows when it is expanding to fill space
#[inline(always)]
fn flex_grow(&self) -> f32 {
Style::DEFAULT.flex_grow
}
/// The relative rate at which this item shrinks when it is contracting to fit into space
#[inline(always)]
fn flex_shrink(&self) -> f32 {
Style::DEFAULT.flex_shrink
}
/// How this node should be aligned in the cross/block axis
/// Falls back to the parents [`AlignItems`] if not set
#[inline(always)]
fn align_self(&self) -> Option<AlignSelf> {
Style::DEFAULT.align_self
}
}
use crate::geometry::AbsoluteAxis;
/// Controls whether flex items are forced onto one line or can wrap onto multiple lines.
///
/// Defaults to [`FlexWrap::NoWrap`]
///
/// [Specification](https://www.w3.org/TR/css-flexbox-1/#flex-wrap-property)
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum FlexWrap {
/// Items will not wrap and stay on a single line
NoWrap,
/// Items will wrap according to this item's [`FlexDirection`]
Wrap,
/// Items will wrap in the opposite direction to this item's [`FlexDirection`]
WrapReverse,
}
impl Default for FlexWrap {
fn default() -> Self {
Self::NoWrap
}
}
/// The direction of the flexbox layout main axis.
///
/// There are always two perpendicular layout axes: main (or primary) and cross (or secondary).
/// Adding items will cause them to be positioned adjacent to each other along the main axis.
/// By varying this value throughout your tree, you can create complex axis-aligned layouts.
///
/// Items are always aligned relative to the cross axis, and justified relative to the main axis.
///
/// The default behavior is [`FlexDirection::Row`].
///
/// [Specification](https://www.w3.org/TR/css-flexbox-1/#flex-direction-property)
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum FlexDirection {
/// Defines +x as the main axis
///
/// Items will be added from left to right in a row.
Row,
/// Defines +y as the main axis
///
/// Items will be added from top to bottom in a column.
Column,
/// Defines -x as the main axis
///
/// Items will be added from right to left in a row.
RowReverse,
/// Defines -y as the main axis
///
/// Items will be added from bottom to top in a column.
ColumnReverse,
}
impl Default for FlexDirection {
fn default() -> Self {
Self::Row
}
}
impl FlexDirection {
#[inline]
/// Is the direction [`FlexDirection::Row`] or [`FlexDirection::RowReverse`]?
pub(crate) fn is_row(self) -> bool {
matches!(self, Self::Row | Self::RowReverse)
}
#[inline]
/// Is the direction [`FlexDirection::Column`] or [`FlexDirection::ColumnReverse`]?
pub(crate) fn is_column(self) -> bool {
matches!(self, Self::Column | Self::ColumnReverse)
}
#[inline]
/// Is the direction [`FlexDirection::RowReverse`] or [`FlexDirection::ColumnReverse`]?
pub(crate) fn is_reverse(self) -> bool {
matches!(self, Self::RowReverse | Self::ColumnReverse)
}
#[inline]
/// The `AbsoluteAxis` that corresponds to the main axis
pub(crate) fn main_axis(self) -> AbsoluteAxis {
match self {
Self::Row | Self::RowReverse => AbsoluteAxis::Horizontal,
Self::Column | Self::ColumnReverse => AbsoluteAxis::Vertical,
}
}
#[inline]
/// The `AbsoluteAxis` that corresponds to the cross axis
pub(crate) fn cross_axis(self) -> AbsoluteAxis {
match self {
Self::Row | Self::RowReverse => AbsoluteAxis::Vertical,
Self::Column | Self::ColumnReverse => AbsoluteAxis::Horizontal,
}
}
}
#[cfg(test)]
mod tests {
mod test_flex_direction {
use crate::style::*;
#[test]
fn flex_direction_is_row() {
assert!(FlexDirection::Row.is_row());
assert!(FlexDirection::RowReverse.is_row());
assert!(!FlexDirection::Column.is_row());
assert!(!FlexDirection::ColumnReverse.is_row());
}
#[test]
fn flex_direction_is_column() {
assert!(!FlexDirection::Row.is_column());
assert!(!FlexDirection::RowReverse.is_column());
assert!(FlexDirection::Column.is_column());
assert!(FlexDirection::ColumnReverse.is_column());
}
#[test]
fn flex_direction_is_reverse() {
assert!(!FlexDirection::Row.is_reverse());
assert!(FlexDirection::RowReverse.is_reverse());
assert!(!FlexDirection::Column.is_reverse());
assert!(FlexDirection::ColumnReverse.is_reverse());
}
}
}

716
vendor/taffy/src/style/grid.rs vendored Normal file
View File

@@ -0,0 +1,716 @@
//! Style types for CSS Grid layout
use super::{AlignContent, AlignItems, AlignSelf, CoreStyle, JustifyContent, LengthPercentage, Style};
use crate::compute::grid::{GridCoordinate, GridLine, OriginZeroLine};
use crate::geometry::{AbsoluteAxis, AbstractAxis, Line, MinMax, Size};
use crate::style_helpers::*;
use crate::util::sys::GridTrackVec;
use core::borrow::Borrow;
use core::cmp::{max, min};
use core::convert::Infallible;
/// The set of styles required for a CSS Grid container
pub trait GridContainerStyle: CoreStyle {
/// The type returned by grid_template_rows and grid_template_columns
type TemplateTrackList<'a>: Borrow<[TrackSizingFunction]>
where
Self: 'a;
/// The type returned by grid_auto_rows and grid_auto_columns
type AutoTrackList<'a>: Borrow<[NonRepeatedTrackSizingFunction]>
where
Self: 'a;
// FIXME: re-add default implemenations for grid_{template,auto}_{rows,columns} once the
// associated_type_defaults feature (https://github.com/rust-lang/rust/issues/29661) is stabilised.
/// Defines the track sizing functions (heights) of the grid rows
fn grid_template_rows(&self) -> Self::TemplateTrackList<'_>;
/// Defines the track sizing functions (widths) of the grid columns
fn grid_template_columns(&self) -> Self::TemplateTrackList<'_>;
/// Defines the size of implicitly created rows
fn grid_auto_rows(&self) -> Self::AutoTrackList<'_>;
/// Defined the size of implicitly created columns
fn grid_auto_columns(&self) -> Self::AutoTrackList<'_>;
/// Controls how items get placed into the grid for auto-placed items
#[inline(always)]
fn grid_auto_flow(&self) -> GridAutoFlow {
Style::DEFAULT.grid_auto_flow
}
/// How large should the gaps between items in a grid or flex container be?
#[inline(always)]
fn gap(&self) -> Size<LengthPercentage> {
Style::DEFAULT.gap
}
// Alignment properties
/// How should content contained within this item be aligned in the cross/block axis
#[inline(always)]
fn align_content(&self) -> Option<AlignContent> {
Style::DEFAULT.align_content
}
/// How should contained within this item be aligned in the main/inline axis
#[inline(always)]
fn justify_content(&self) -> Option<JustifyContent> {
Style::DEFAULT.justify_content
}
/// How this node's children aligned in the cross/block axis?
#[inline(always)]
fn align_items(&self) -> Option<AlignItems> {
Style::DEFAULT.align_items
}
/// How this node's children should be aligned in the inline axis
#[inline(always)]
fn justify_items(&self) -> Option<AlignItems> {
Style::DEFAULT.justify_items
}
/// Get a grid item's row or column placement depending on the axis passed
#[inline(always)]
fn grid_template_tracks(&self, axis: AbsoluteAxis) -> Self::TemplateTrackList<'_> {
match axis {
AbsoluteAxis::Horizontal => self.grid_template_columns(),
AbsoluteAxis::Vertical => self.grid_template_rows(),
}
}
/// Get a grid container's align-content or justify-content alignment depending on the axis passed
#[inline(always)]
fn grid_align_content(&self, axis: AbstractAxis) -> AlignContent {
match axis {
AbstractAxis::Inline => self.justify_content().unwrap_or(AlignContent::Stretch),
AbstractAxis::Block => self.align_content().unwrap_or(AlignContent::Stretch),
}
}
}
/// The set of styles required for a CSS Grid item (child of a CSS Grid container)
pub trait GridItemStyle: CoreStyle {
/// Defines which row in the grid the item should start and end at
#[inline(always)]
fn grid_row(&self) -> Line<GridPlacement> {
Style::DEFAULT.grid_row
}
/// Defines which column in the grid the item should start and end at
#[inline(always)]
fn grid_column(&self) -> Line<GridPlacement> {
Style::DEFAULT.grid_column
}
/// How this node should be aligned in the cross/block axis
/// Falls back to the parents [`AlignItems`] if not set
#[inline(always)]
fn align_self(&self) -> Option<AlignSelf> {
Style::DEFAULT.align_self
}
/// How this node should be aligned in the inline axis
/// Falls back to the parents [`super::JustifyItems`] if not set
#[inline(always)]
fn justify_self(&self) -> Option<AlignSelf> {
Style::DEFAULT.justify_self
}
/// Get a grid item's row or column placement depending on the axis passed
#[inline(always)]
fn grid_placement(&self, axis: AbsoluteAxis) -> Line<GridPlacement> {
match axis {
AbsoluteAxis::Horizontal => self.grid_column(),
AbsoluteAxis::Vertical => self.grid_row(),
}
}
}
/// Controls whether grid items are placed row-wise or column-wise. And whether the sparse or dense packing algorithm is used.
///
/// The "dense" packing algorithm attempts to fill in holes earlier in the grid, if smaller items come up later. This may cause items to appear out-of-order, when doing so would fill in holes left by larger items.
///
/// Defaults to [`GridAutoFlow::Row`]
///
/// [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/grid-auto-flow)
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum GridAutoFlow {
/// Items are placed by filling each row in turn, adding new rows as necessary
Row,
/// Items are placed by filling each column in turn, adding new columns as necessary.
Column,
/// Combines `Row` with the dense packing algorithm.
RowDense,
/// Combines `Column` with the dense packing algorithm.
ColumnDense,
}
impl Default for GridAutoFlow {
fn default() -> Self {
Self::Row
}
}
impl GridAutoFlow {
/// Whether grid auto placement uses the sparse placement algorithm or the dense placement algorithm
/// See: <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-auto-flow#values>
pub fn is_dense(&self) -> bool {
match self {
Self::Row | Self::Column => false,
Self::RowDense | Self::ColumnDense => true,
}
}
/// Whether grid auto placement fills areas row-wise or column-wise
/// See: <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-auto-flow#values>
pub fn primary_axis(&self) -> AbsoluteAxis {
match self {
Self::Row | Self::RowDense => AbsoluteAxis::Horizontal,
Self::Column | Self::ColumnDense => AbsoluteAxis::Vertical,
}
}
}
/// A grid line placement specification which is generic over the coordinate system that it uses to define
/// grid line positions.
///
/// GenericGridPlacement<GridLine> is aliased as GridPlacement and is exposed to users of Taffy to define styles.
/// GenericGridPlacement<OriginZeroLine> is aliased as OriginZeroGridPlacement and is used internally for placement computations.
///
/// See [`crate::compute::grid::type::coordinates`] for documentation on the different coordinate systems.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum GenericGridPlacement<LineType: GridCoordinate> {
/// Place item according to the auto-placement algorithm, and the parent's grid_auto_flow property
Auto,
/// Place item at specified line (column or row) index
Line(LineType),
/// Item should span specified number of tracks (columns or rows)
Span(u16),
}
/// A grid line placement using the normalized OriginZero coordinates to specify line positions.
pub(crate) type OriginZeroGridPlacement = GenericGridPlacement<OriginZeroLine>;
/// A grid line placement specification. Used for grid-[row/column]-[start/end]. Named tracks are not implemented.
///
/// Defaults to `GridPlacement::Auto`
///
/// [Specification](https://www.w3.org/TR/css3-grid-layout/#typedef-grid-row-start-grid-line)
pub type GridPlacement = GenericGridPlacement<GridLine>;
impl TaffyAuto for GridPlacement {
const AUTO: Self = Self::Auto;
}
impl TaffyGridLine for GridPlacement {
fn from_line_index(index: i16) -> Self {
GridPlacement::Line(GridLine::from(index))
}
}
impl TaffyGridLine for Line<GridPlacement> {
fn from_line_index(index: i16) -> Self {
Line { start: GridPlacement::from_line_index(index), end: GridPlacement::Auto }
}
}
impl TaffyGridSpan for GridPlacement {
fn from_span(span: u16) -> Self {
GridPlacement::Span(span)
}
}
impl TaffyGridSpan for Line<GridPlacement> {
fn from_span(span: u16) -> Self {
Line { start: GridPlacement::from_span(span), end: GridPlacement::Auto }
}
}
impl Default for GridPlacement {
fn default() -> Self {
Self::Auto
}
}
impl GridPlacement {
/// Apply a mapping function if the [`GridPlacement`] is a `Track`. Otherwise return `self` unmodified.
pub fn into_origin_zero_placement(self, explicit_track_count: u16) -> OriginZeroGridPlacement {
match self {
Self::Auto => OriginZeroGridPlacement::Auto,
Self::Span(span) => OriginZeroGridPlacement::Span(span),
// Grid line zero is an invalid index, so it gets treated as Auto
// See: https://developer.mozilla.org/en-US/docs/Web/CSS/grid-row-start#values
Self::Line(line) => match line.as_i16() {
0 => OriginZeroGridPlacement::Auto,
_ => OriginZeroGridPlacement::Line(line.into_origin_zero_line(explicit_track_count)),
},
}
}
}
impl<T: GridCoordinate> Line<GenericGridPlacement<T>> {
/// Resolves the span for an indefinite placement (a placement that does not consist of two `Track`s).
/// Panics if called on a definite placement
pub fn indefinite_span(&self) -> u16 {
use GenericGridPlacement as GP;
match (self.start, self.end) {
(GP::Line(_), GP::Auto) => 1,
(GP::Auto, GP::Line(_)) => 1,
(GP::Auto, GP::Auto) => 1,
(GP::Line(_), GP::Span(span)) => span,
(GP::Span(span), GP::Line(_)) => span,
(GP::Span(span), GP::Auto) => span,
(GP::Auto, GP::Span(span)) => span,
(GP::Span(span), GP::Span(_)) => span,
(GP::Line(_), GP::Line(_)) => panic!("indefinite_span should only be called on indefinite grid tracks"),
}
}
}
impl Line<GridPlacement> {
#[inline]
/// Whether the track position is definite in this axis (or the item will need auto placement)
/// The track position is definite if least one of the start and end positions is a NON-ZERO track index
/// (0 is an invalid line in GridLine coordinates, and falls back to "auto" which is indefinite)
pub fn is_definite(&self) -> bool {
match (self.start, self.end) {
(GenericGridPlacement::Line(line), _) if line.as_i16() != 0 => true,
(_, GenericGridPlacement::Line(line)) if line.as_i16() != 0 => true,
_ => false,
}
}
/// Apply a mapping function if the [`GridPlacement`] is a `Track`. Otherwise return `self` unmodified.
pub fn into_origin_zero(&self, explicit_track_count: u16) -> Line<OriginZeroGridPlacement> {
Line {
start: self.start.into_origin_zero_placement(explicit_track_count),
end: self.end.into_origin_zero_placement(explicit_track_count),
}
}
}
impl Line<OriginZeroGridPlacement> {
#[inline]
/// Whether the track position is definite in this axis (or the item will need auto placement)
/// The track position is definite if least one of the start and end positions is a track index
pub fn is_definite(&self) -> bool {
matches!((self.start, self.end), (GenericGridPlacement::Line(_), _) | (_, GenericGridPlacement::Line(_)))
}
/// If at least one of the of the start and end positions is a track index then the other end can be resolved
/// into a track index purely based on the information contained with the placement specification
pub fn resolve_definite_grid_lines(&self) -> Line<OriginZeroLine> {
use OriginZeroGridPlacement as GP;
match (self.start, self.end) {
(GP::Line(line1), GP::Line(line2)) => {
if line1 == line2 {
Line { start: line1, end: line1 + 1 }
} else {
Line { start: min(line1, line2), end: max(line1, line2) }
}
}
(GP::Line(line), GP::Span(span)) => Line { start: line, end: line + span },
(GP::Line(line), GP::Auto) => Line { start: line, end: line + 1 },
(GP::Span(span), GP::Line(line)) => Line { start: line - span, end: line },
(GP::Auto, GP::Line(line)) => Line { start: line - 1, end: line },
_ => panic!("resolve_definite_grid_tracks should only be called on definite grid tracks"),
}
}
/// For absolutely positioned items:
/// - Tracks resolve to definite tracks
/// - For Spans:
/// - If the other position is a Track, they resolve to a definite track relative to the other track
/// - Else resolve to None
/// - Auto resolves to None
///
/// When finally positioning the item, a value of None means that the item's grid area is bounded by the grid
/// container's border box on that side.
pub fn resolve_absolutely_positioned_grid_tracks(&self) -> Line<Option<OriginZeroLine>> {
use OriginZeroGridPlacement as GP;
match (self.start, self.end) {
(GP::Line(track1), GP::Line(track2)) => {
if track1 == track2 {
Line { start: Some(track1), end: Some(track1 + 1) }
} else {
Line { start: Some(min(track1, track2)), end: Some(max(track1, track2)) }
}
}
(GP::Line(track), GP::Span(span)) => Line { start: Some(track), end: Some(track + span) },
(GP::Line(track), GP::Auto) => Line { start: Some(track), end: None },
(GP::Span(span), GP::Line(track)) => Line { start: Some(track - span), end: Some(track) },
(GP::Auto, GP::Line(track)) => Line { start: None, end: Some(track) },
_ => Line { start: None, end: None },
}
}
/// If neither of the start and end positions is a track index then the other end can be resolved
/// into a track index if a definite start position is supplied externally
pub fn resolve_indefinite_grid_tracks(&self, start: OriginZeroLine) -> Line<OriginZeroLine> {
use OriginZeroGridPlacement as GP;
match (self.start, self.end) {
(GP::Auto, GP::Auto) => Line { start, end: start + 1 },
(GP::Span(span), GP::Auto) => Line { start, end: start + span },
(GP::Auto, GP::Span(span)) => Line { start, end: start + span },
(GP::Span(span), GP::Span(_)) => Line { start, end: start + span },
_ => panic!("resolve_indefinite_grid_tracks should only be called on indefinite grid tracks"),
}
}
}
/// Represents the start and end points of a GridItem within a given axis
impl Default for Line<GridPlacement> {
fn default() -> Self {
Line { start: GridPlacement::Auto, end: GridPlacement::Auto }
}
}
/// Maximum track sizing function
///
/// Specifies the maximum size of a grid track. A grid track will automatically size between it's minimum and maximum size based
/// on the size of it's contents, the amount of available space, and the sizing constraint the grid is being size under.
/// See <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-columns>
#[derive(Copy, Clone, PartialEq, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum MaxTrackSizingFunction {
/// Track maximum size should be a fixed length or percentage value
Fixed(LengthPercentage),
/// Track maximum size should be content sized under a min-content constraint
MinContent,
/// Track maximum size should be content sized under a max-content constraint
MaxContent,
/// Track maximum size should be sized according to the fit-content formula
FitContent(LengthPercentage),
/// Track maximum size should be automatically sized
Auto,
/// The dimension as a fraction of the total available grid space (`fr` units in CSS)
/// Specified value is the numerator of the fraction. Denominator is the sum of all fraction specified in that grid dimension
/// Spec: <https://www.w3.org/TR/css3-grid-layout/#fr-unit>
Fraction(f32),
}
impl TaffyAuto for MaxTrackSizingFunction {
const AUTO: Self = Self::Auto;
}
impl TaffyMinContent for MaxTrackSizingFunction {
const MIN_CONTENT: Self = Self::MinContent;
}
impl TaffyMaxContent for MaxTrackSizingFunction {
const MAX_CONTENT: Self = Self::MaxContent;
}
impl TaffyFitContent for MaxTrackSizingFunction {
fn fit_content(argument: LengthPercentage) -> Self {
Self::FitContent(argument)
}
}
impl TaffyZero for MaxTrackSizingFunction {
const ZERO: Self = Self::Fixed(LengthPercentage::ZERO);
}
impl FromLength for MaxTrackSizingFunction {
fn from_length<Input: Into<f32> + Copy>(value: Input) -> Self {
Self::Fixed(LengthPercentage::from_length(value))
}
}
impl FromPercent for MaxTrackSizingFunction {
fn from_percent<Input: Into<f32> + Copy>(percent: Input) -> Self {
Self::Fixed(LengthPercentage::from_percent(percent))
}
}
impl FromFlex for MaxTrackSizingFunction {
fn from_flex<Input: Into<f32> + Copy>(flex: Input) -> Self {
Self::Fraction(flex.into())
}
}
impl MaxTrackSizingFunction {
/// Returns true if the max track sizing function is `MinContent`, `MaxContent`, `FitContent` or `Auto`, else false.
#[inline(always)]
pub fn is_intrinsic(&self) -> bool {
matches!(self, Self::MinContent | Self::MaxContent | Self::FitContent(_) | Self::Auto)
}
/// Returns true if the max track sizing function is `MaxContent`, `FitContent` or `Auto` else false.
/// "In all cases, treat auto and fit-content() as max-content, except where specified otherwise for fit-content()."
/// See: <https://www.w3.org/TR/css-grid-1/#algo-terms>
#[inline(always)]
pub fn is_max_content_alike(&self) -> bool {
matches!(self, Self::MaxContent | Self::FitContent(_) | Self::Auto)
}
/// Returns true if the max track sizing function is `Flex`, else false.
#[inline(always)]
pub fn is_flexible(&self) -> bool {
matches!(self, Self::Fraction(_))
}
/// Returns fixed point values directly. Attempts to resolve percentage values against
/// the passed available_space and returns if this results in a concrete value (which it
/// will if the available_space is `Some`). Otherwise returns None.
#[inline(always)]
pub fn definite_value(self, parent_size: Option<f32>) -> Option<f32> {
use MaxTrackSizingFunction::*;
match self {
Fixed(LengthPercentage::Length(size)) => Some(size),
Fixed(LengthPercentage::Percent(fraction)) => parent_size.map(|size| fraction * size),
MinContent | MaxContent | FitContent(_) | Auto | Fraction(_) => None,
}
}
/// Resolve the maximum size of the track as defined by either:
/// - A fixed track sizing function
/// - A percentage track sizing function (with definite available space)
/// - A fit-content sizing function with fixed argument
/// - A fit-content sizing function with percentage argument (with definite available space)
/// All other kinds of track sizing function return None.
#[inline(always)]
pub fn definite_limit(self, parent_size: Option<f32>) -> Option<f32> {
use MaxTrackSizingFunction::FitContent;
match self {
FitContent(LengthPercentage::Length(size)) => Some(size),
FitContent(LengthPercentage::Percent(fraction)) => parent_size.map(|size| fraction * size),
_ => self.definite_value(parent_size),
}
}
/// Resolve percentage values against the passed parent_size, returning Some(value)
/// Non-percentage values always return None.
#[inline(always)]
pub fn resolved_percentage_size(self, parent_size: f32) -> Option<f32> {
use MaxTrackSizingFunction::*;
match self {
Fixed(LengthPercentage::Percent(fraction)) => Some(fraction * parent_size),
Fixed(LengthPercentage::Length(_)) | MinContent | MaxContent | FitContent(_) | Auto | Fraction(_) => None,
}
}
/// Whether the track sizing functions depends on the size of the parent node
#[inline(always)]
pub fn uses_percentage(self) -> bool {
use MaxTrackSizingFunction::*;
matches!(self, Fixed(LengthPercentage::Percent(_)) | FitContent(LengthPercentage::Percent(_)))
}
}
/// Minimum track sizing function
///
/// Specifies the minimum size of a grid track. A grid track will automatically size between it's minimum and maximum size based
/// on the size of it's contents, the amount of available space, and the sizing constraint the grid is being size under.
/// See <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-columns>
#[derive(Copy, Clone, PartialEq, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum MinTrackSizingFunction {
/// Track minimum size should be a fixed length or percentage value
Fixed(LengthPercentage),
/// Track minimum size should be content sized under a min-content constraint
MinContent,
/// Track minimum size should be content sized under a max-content constraint
MaxContent,
/// Track minimum size should be automatically sized
Auto,
}
impl TaffyAuto for MinTrackSizingFunction {
const AUTO: Self = Self::Auto;
}
impl TaffyMinContent for MinTrackSizingFunction {
const MIN_CONTENT: Self = Self::MinContent;
}
impl TaffyMaxContent for MinTrackSizingFunction {
const MAX_CONTENT: Self = Self::MaxContent;
}
impl TaffyZero for MinTrackSizingFunction {
const ZERO: Self = Self::Fixed(LengthPercentage::ZERO);
}
impl FromLength for MinTrackSizingFunction {
fn from_length<Input: Into<f32> + Copy>(value: Input) -> Self {
Self::Fixed(LengthPercentage::from_length(value))
}
}
impl FromPercent for MinTrackSizingFunction {
fn from_percent<Input: Into<f32> + Copy>(percent: Input) -> Self {
Self::Fixed(LengthPercentage::from_percent(percent))
}
}
impl MinTrackSizingFunction {
/// Returns true if the min track sizing function is `MinContent`, `MaxContent` or `Auto`, else false.
#[inline(always)]
pub fn is_intrinsic(&self) -> bool {
matches!(self, Self::MinContent | Self::MaxContent | Self::Auto)
}
/// Returns fixed point values directly. Attempts to resolve percentage values against
/// the passed available_space and returns if this results in a concrete value (which it
/// will if the available_space is `Some`). Otherwise returns `None`.
#[inline(always)]
pub fn definite_value(self, parent_size: Option<f32>) -> Option<f32> {
use MinTrackSizingFunction::*;
match self {
Fixed(LengthPercentage::Length(size)) => Some(size),
Fixed(LengthPercentage::Percent(fraction)) => parent_size.map(|size| fraction * size),
MinContent | MaxContent | Auto => None,
}
}
/// Resolve percentage values against the passed parent_size, returning Some(value)
/// Non-percentage values always return None.
#[inline(always)]
pub fn resolved_percentage_size(self, parent_size: f32) -> Option<f32> {
use MinTrackSizingFunction::*;
match self {
Fixed(LengthPercentage::Percent(fraction)) => Some(fraction * parent_size),
Fixed(LengthPercentage::Length(_)) | MinContent | MaxContent | Auto => None,
}
}
/// Whether the track sizing functions depends on the size of the parent node
#[inline(always)]
pub fn uses_percentage(self) -> bool {
use MinTrackSizingFunction::*;
matches!(self, Fixed(LengthPercentage::Percent(_)))
}
}
/// The sizing function for a grid track (row/column)
///
/// May either be a MinMax variant which specifies separate values for the min-/max- track sizing functions
/// or a scalar value which applies to both track sizing functions.
pub type NonRepeatedTrackSizingFunction = MinMax<MinTrackSizingFunction, MaxTrackSizingFunction>;
impl NonRepeatedTrackSizingFunction {
/// Extract the min track sizing function
pub fn min_sizing_function(&self) -> MinTrackSizingFunction {
self.min
}
/// Extract the max track sizing function
pub fn max_sizing_function(&self) -> MaxTrackSizingFunction {
self.max
}
/// Determine whether at least one of the components ("min" and "max") are fixed sizing function
pub fn has_fixed_component(&self) -> bool {
matches!(self.min, MinTrackSizingFunction::Fixed(_)) || matches!(self.max, MaxTrackSizingFunction::Fixed(_))
}
}
impl TaffyAuto for NonRepeatedTrackSizingFunction {
const AUTO: Self = Self { min: MinTrackSizingFunction::AUTO, max: MaxTrackSizingFunction::AUTO };
}
impl TaffyMinContent for NonRepeatedTrackSizingFunction {
const MIN_CONTENT: Self =
Self { min: MinTrackSizingFunction::MIN_CONTENT, max: MaxTrackSizingFunction::MIN_CONTENT };
}
impl TaffyMaxContent for NonRepeatedTrackSizingFunction {
const MAX_CONTENT: Self =
Self { min: MinTrackSizingFunction::MAX_CONTENT, max: MaxTrackSizingFunction::MAX_CONTENT };
}
impl TaffyFitContent for NonRepeatedTrackSizingFunction {
fn fit_content(argument: LengthPercentage) -> Self {
Self { min: MinTrackSizingFunction::AUTO, max: MaxTrackSizingFunction::FitContent(argument) }
}
}
impl TaffyZero for NonRepeatedTrackSizingFunction {
const ZERO: Self = Self { min: MinTrackSizingFunction::ZERO, max: MaxTrackSizingFunction::ZERO };
}
impl FromLength for NonRepeatedTrackSizingFunction {
fn from_length<Input: Into<f32> + Copy>(value: Input) -> Self {
Self { min: MinTrackSizingFunction::from_length(value), max: MaxTrackSizingFunction::from_length(value) }
}
}
impl FromPercent for NonRepeatedTrackSizingFunction {
fn from_percent<Input: Into<f32> + Copy>(percent: Input) -> Self {
Self { min: MinTrackSizingFunction::from_percent(percent), max: MaxTrackSizingFunction::from_percent(percent) }
}
}
impl FromFlex for NonRepeatedTrackSizingFunction {
fn from_flex<Input: Into<f32> + Copy>(flex: Input) -> Self {
Self { min: MinTrackSizingFunction::AUTO, max: MaxTrackSizingFunction::from_flex(flex) }
}
}
/// The first argument to a repeated track definition. This type represents the type of automatic repetition to perform.
///
/// See <https://www.w3.org/TR/css-grid-1/#auto-repeat> for an explanation of how auto-repeated track definitions work
/// and the difference between AutoFit and AutoFill.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum GridTrackRepetition {
/// Auto-repeating tracks should be generated to fit the container
/// See: <https://developer.mozilla.org/en-US/docs/Web/CSS/repeat#auto-fill>
AutoFill,
/// Auto-repeating tracks should be generated to fit the container
/// See: <https://developer.mozilla.org/en-US/docs/Web/CSS/repeat#auto-fit>
AutoFit,
/// The specified tracks should be repeated exacts N times
Count(u16),
}
impl TryFrom<u16> for GridTrackRepetition {
type Error = Infallible;
fn try_from(value: u16) -> Result<Self, Infallible> {
Ok(Self::Count(value))
}
}
/// Error returned when trying to convert a string to a GridTrackRepetition and that string is not
/// either "auto-fit" or "auto-fill"
#[derive(Debug)]
pub struct InvalidStringRepetitionValue;
#[cfg(feature = "std")]
impl std::error::Error for InvalidStringRepetitionValue {}
impl core::fmt::Display for InvalidStringRepetitionValue {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("&str can only be converted to GridTrackRepetition if it's value is 'auto-fit' or 'auto-fill'")
}
}
impl TryFrom<&str> for GridTrackRepetition {
type Error = InvalidStringRepetitionValue;
fn try_from(value: &str) -> Result<Self, InvalidStringRepetitionValue> {
match value {
"auto-fit" => Ok(Self::AutoFit),
"auto-fill" => Ok(Self::AutoFill),
_ => Err(InvalidStringRepetitionValue),
}
}
}
/// The sizing function for a grid track (row/column)
/// See <https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-columns>
#[derive(Clone, PartialEq, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum TrackSizingFunction {
/// A single non-repeated track
Single(NonRepeatedTrackSizingFunction),
/// Automatically generate grid tracks to fit the available space using the specified definite track lengths
/// Only valid if every track in template (not just the repetition) has a fixed size.
Repeat(GridTrackRepetition, GridTrackVec<NonRepeatedTrackSizingFunction>),
}
impl TrackSizingFunction {
/// Whether the track definition is a auto-repeated fragment
pub fn is_auto_repetition(&self) -> bool {
matches!(self, Self::Repeat(GridTrackRepetition::AutoFit | GridTrackRepetition::AutoFill, _))
}
}
impl TaffyAuto for TrackSizingFunction {
const AUTO: Self = Self::Single(NonRepeatedTrackSizingFunction::AUTO);
}
impl TaffyMinContent for TrackSizingFunction {
const MIN_CONTENT: Self = Self::Single(NonRepeatedTrackSizingFunction::MIN_CONTENT);
}
impl TaffyMaxContent for TrackSizingFunction {
const MAX_CONTENT: Self = Self::Single(NonRepeatedTrackSizingFunction::MAX_CONTENT);
}
impl TaffyFitContent for TrackSizingFunction {
fn fit_content(argument: LengthPercentage) -> Self {
Self::Single(NonRepeatedTrackSizingFunction::fit_content(argument))
}
}
impl TaffyZero for TrackSizingFunction {
const ZERO: Self = Self::Single(NonRepeatedTrackSizingFunction::ZERO);
}
impl FromLength for TrackSizingFunction {
fn from_length<Input: Into<f32> + Copy>(value: Input) -> Self {
Self::Single(NonRepeatedTrackSizingFunction::from_length(value))
}
}
impl FromPercent for TrackSizingFunction {
fn from_percent<Input: Into<f32> + Copy>(percent: Input) -> Self {
Self::Single(NonRepeatedTrackSizingFunction::from_percent(percent))
}
}
impl FromFlex for TrackSizingFunction {
fn from_flex<Input: Into<f32> + Copy>(flex: Input) -> Self {
Self::Single(NonRepeatedTrackSizingFunction::from_flex(flex))
}
}
impl From<MinMax<MinTrackSizingFunction, MaxTrackSizingFunction>> for TrackSizingFunction {
fn from(input: MinMax<MinTrackSizingFunction, MaxTrackSizingFunction>) -> Self {
Self::Single(input)
}
}

1061
vendor/taffy/src/style/mod.rs vendored Normal file

File diff suppressed because it is too large Load Diff

546
vendor/taffy/src/style_helpers.rs vendored Normal file
View File

@@ -0,0 +1,546 @@
//! Helper functions which it make it easier to create instances of types in the `style` and `geometry` modules.
use crate::{
geometry::{Line, Point, Rect, Size},
style::LengthPercentage,
};
#[cfg(feature = "grid")]
use crate::{
geometry::MinMax,
style::{
GridTrackRepetition, MaxTrackSizingFunction, MinTrackSizingFunction, NonRepeatedTrackSizingFunction,
TrackSizingFunction,
},
util::sys::Vec,
};
#[cfg(feature = "grid")]
use core::fmt::Debug;
/// Returns an auto-repeated track definition
#[cfg(feature = "grid")]
pub fn repeat<Input>(repetition_kind: Input, track_list: Vec<NonRepeatedTrackSizingFunction>) -> TrackSizingFunction
where
Input: TryInto<GridTrackRepetition>,
<Input as TryInto<GridTrackRepetition>>::Error: Debug,
{
TrackSizingFunction::Repeat(repetition_kind.try_into().unwrap(), track_list)
}
#[cfg(feature = "grid")]
/// Returns a grid template containing `count` evenly sized tracks
pub fn evenly_sized_tracks(count: u16) -> Vec<TrackSizingFunction> {
use crate::util::sys::new_vec_with_capacity;
let mut repeated_tracks = new_vec_with_capacity(1);
repeated_tracks.push(flex(1.0f32));
let mut tracks = new_vec_with_capacity(1);
tracks.push(repeat(count, repeated_tracks));
tracks
}
/// Specifies a grid line to place a grid item between in CSS Grid Line coordinates:
/// - Positive indices count upwards from the start (top or left) of the explicit grid
/// - Negative indices count downwards from the end (bottom or right) of the explicit grid
/// - ZERO IS INVALID index, and will be treated as a GridPlacement::Auto.
pub fn line<T: TaffyGridLine>(index: i16) -> T {
T::from_line_index(index)
}
/// Trait to abstract over grid line values
pub trait TaffyGridLine {
/// Converts an i16 into Self
fn from_line_index(index: i16) -> Self;
}
/// Returns a GridPlacement::Span
pub fn span<T: TaffyGridSpan>(span: u16) -> T {
T::from_span(span)
}
/// Trait to abstract over grid span values
pub trait TaffyGridSpan {
/// Converts an iu6 into Self
fn from_span(span: u16) -> Self;
}
/// Returns a MinMax with min value of min and max value of max
#[cfg(feature = "grid")]
pub fn minmax<Output>(min: MinTrackSizingFunction, max: MaxTrackSizingFunction) -> Output
where
Output: From<MinMax<MinTrackSizingFunction, MaxTrackSizingFunction>>,
{
MinMax { min, max }.into()
}
/// Shorthand for minmax(0, Nfr). Probably what you want if you want exactly evenly sized tracks.
#[cfg(feature = "grid")]
pub fn flex<Input, Output>(flex_fraction: Input) -> Output
where
Input: Into<f32> + Copy,
Output: From<MinMax<MinTrackSizingFunction, MaxTrackSizingFunction>>,
{
MinMax { min: zero(), max: fr(flex_fraction.into()) }.into()
}
/// Returns the zero value for that type
pub const fn zero<T: TaffyZero>() -> T {
T::ZERO
}
/// Trait to abstract over zero values
pub trait TaffyZero {
/// The zero value for type implementing TaffyZero
const ZERO: Self;
}
impl TaffyZero for f32 {
const ZERO: f32 = 0.0;
}
impl<T: TaffyZero> TaffyZero for Option<T> {
const ZERO: Option<T> = Some(T::ZERO);
}
impl<T: TaffyZero> TaffyZero for Point<T> {
const ZERO: Point<T> = Point { x: T::ZERO, y: T::ZERO };
}
impl<T: TaffyZero> Point<T> {
/// Returns a Point where both the x and y values are the zero value of the contained type
/// (e.g. 0.0, Some(0.0), or Dimension::Length(0.0))
pub const fn zero() -> Self {
zero::<Self>()
}
}
impl<T: TaffyZero> TaffyZero for Line<T> {
const ZERO: Line<T> = Line { start: T::ZERO, end: T::ZERO };
}
impl<T: TaffyZero> Line<T> {
/// Returns a Line where both the start and end values are the zero value of the contained type
/// (e.g. 0.0, Some(0.0), or Dimension::Length(0.0))
pub const fn zero() -> Self {
zero::<Self>()
}
}
impl<T: TaffyZero> TaffyZero for Size<T> {
const ZERO: Size<T> = Size { width: T::ZERO, height: T::ZERO };
}
impl<T: TaffyZero> Size<T> {
/// Returns a Size where both the width and height values are the zero value of the contained type
/// (e.g. 0.0, Some(0.0), or Dimension::Length(0.0))
pub const fn zero() -> Self {
zero::<Self>()
}
}
impl<T: TaffyZero> TaffyZero for Rect<T> {
const ZERO: Rect<T> = Rect { left: T::ZERO, right: T::ZERO, top: T::ZERO, bottom: T::ZERO };
}
impl<T: TaffyZero> Rect<T> {
/// Returns a Rect where the left, right, top, and bottom values are all the zero value of the contained type
/// (e.g. 0.0, Some(0.0), or Dimension::Length(0.0))
pub const fn zero() -> Self {
zero::<Self>()
}
}
/// Returns the auto value for that type
pub const fn auto<T: TaffyAuto>() -> T {
T::AUTO
}
/// Trait to abstract over auto values
pub trait TaffyAuto {
/// The auto value for type implementing TaffyAuto
const AUTO: Self;
}
impl<T: TaffyAuto> TaffyAuto for Option<T> {
const AUTO: Option<T> = Some(T::AUTO);
}
impl<T: TaffyAuto> TaffyAuto for Point<T> {
const AUTO: Point<T> = Point { x: T::AUTO, y: T::AUTO };
}
impl<T: TaffyAuto> Point<T> {
/// Returns a Point where both the x and y values are the auto value of the contained type
/// (e.g. Dimension::Auto or LengthPercentageAuto::Auto)
pub const fn auto() -> Self {
auto::<Self>()
}
}
impl<T: TaffyAuto> TaffyAuto for Line<T> {
const AUTO: Line<T> = Line { start: T::AUTO, end: T::AUTO };
}
impl<T: TaffyAuto> Line<T> {
/// Returns a Line where both the start and end values are the auto value of the contained type
/// (e.g. Dimension::Auto or LengthPercentageAuto::Auto)
pub const fn auto() -> Self {
auto::<Self>()
}
}
impl<T: TaffyAuto> TaffyAuto for Size<T> {
const AUTO: Size<T> = Size { width: T::AUTO, height: T::AUTO };
}
impl<T: TaffyAuto> Size<T> {
/// Returns a Size where both the width and height values are the auto value of the contained type
/// (e.g. Dimension::Auto or LengthPercentageAuto::Auto)
pub const fn auto() -> Self {
auto::<Self>()
}
}
impl<T: TaffyAuto> TaffyAuto for Rect<T> {
const AUTO: Rect<T> = Rect { left: T::AUTO, right: T::AUTO, top: T::AUTO, bottom: T::AUTO };
}
impl<T: TaffyAuto> Rect<T> {
/// Returns a Rect where the left, right, top, and bottom values are all the auto value of the contained type
/// (e.g. Dimension::Auto or LengthPercentageAuto::Auto)
pub const fn auto() -> Self {
auto::<Self>()
}
}
/// Returns the auto value for that type
pub const fn min_content<T: TaffyMinContent>() -> T {
T::MIN_CONTENT
}
/// Trait to abstract over min_content values
pub trait TaffyMinContent {
/// The min_content value for type implementing TaffyZero
const MIN_CONTENT: Self;
}
impl<T: TaffyMinContent> TaffyMinContent for Option<T> {
const MIN_CONTENT: Option<T> = Some(T::MIN_CONTENT);
}
impl<T: TaffyMinContent> TaffyMinContent for Point<T> {
const MIN_CONTENT: Point<T> = Point { x: T::MIN_CONTENT, y: T::MIN_CONTENT };
}
impl<T: TaffyMinContent> Point<T> {
/// Returns a Point where both the x and y values are the min_content value of the contained type
/// (e.g. Dimension::Auto or LengthPercentageAuto::Auto)
pub const fn min_content() -> Self {
min_content::<Self>()
}
}
impl<T: TaffyMinContent> TaffyMinContent for Line<T> {
const MIN_CONTENT: Line<T> = Line { start: T::MIN_CONTENT, end: T::MIN_CONTENT };
}
impl<T: TaffyMinContent> Line<T> {
/// Returns a Line where both the start and end values are the min_content value of the contained type
/// (e.g. Dimension::Auto or LengthPercentageAuto::Auto)
pub const fn min_content() -> Self {
min_content::<Self>()
}
}
impl<T: TaffyMinContent> TaffyMinContent for Size<T> {
const MIN_CONTENT: Size<T> = Size { width: T::MIN_CONTENT, height: T::MIN_CONTENT };
}
impl<T: TaffyMinContent> Size<T> {
/// Returns a Size where both the width and height values are the min_content value of the contained type
/// (e.g. Dimension::Auto or LengthPercentageAuto::Auto)
pub const fn min_content() -> Self {
min_content::<Self>()
}
}
impl<T: TaffyMinContent> TaffyMinContent for Rect<T> {
const MIN_CONTENT: Rect<T> =
Rect { left: T::MIN_CONTENT, right: T::MIN_CONTENT, top: T::MIN_CONTENT, bottom: T::MIN_CONTENT };
}
impl<T: TaffyMinContent> Rect<T> {
/// Returns a Rect where the left, right, top, and bottom values are all the min_content value of the contained type
/// (e.g. Dimension::Auto or LengthPercentageAuto::Auto)
pub const fn min_content() -> Self {
min_content::<Self>()
}
}
/// Returns the auto value for that type
pub const fn max_content<T: TaffyMaxContent>() -> T {
T::MAX_CONTENT
}
/// Trait to abstract over max_content values
pub trait TaffyMaxContent {
/// The max_content value for type implementing TaffyZero
const MAX_CONTENT: Self;
}
impl<T: TaffyMaxContent> TaffyMaxContent for Option<T> {
const MAX_CONTENT: Option<T> = Some(T::MAX_CONTENT);
}
impl<T: TaffyMaxContent> TaffyMaxContent for Point<T> {
const MAX_CONTENT: Point<T> = Point { x: T::MAX_CONTENT, y: T::MAX_CONTENT };
}
impl<T: TaffyMaxContent> Point<T> {
/// Returns a Point where both the x and y values are the max_content value of the contained type
/// (e.g. Dimension::Auto or LengthPercentageAuto::Auto)
pub const fn max_content() -> Self {
max_content::<Self>()
}
}
impl<T: TaffyMaxContent> TaffyMaxContent for Line<T> {
const MAX_CONTENT: Line<T> = Line { start: T::MAX_CONTENT, end: T::MAX_CONTENT };
}
impl<T: TaffyMaxContent> Line<T> {
/// Returns a Line where both the start and end values are the max_content value of the contained type
/// (e.g. Dimension::Auto or LengthPercentageAuto::Auto)
pub const fn max_content() -> Self {
max_content::<Self>()
}
}
impl<T: TaffyMaxContent> TaffyMaxContent for Size<T> {
const MAX_CONTENT: Size<T> = Size { width: T::MAX_CONTENT, height: T::MAX_CONTENT };
}
impl<T: TaffyMaxContent> Size<T> {
/// Returns a Size where both the width and height values are the max_content value of the contained type
/// (e.g. Dimension::Auto or LengthPercentageAuto::Auto)
pub const fn max_content() -> Self {
max_content::<Self>()
}
}
impl<T: TaffyMaxContent> TaffyMaxContent for Rect<T> {
const MAX_CONTENT: Rect<T> =
Rect { left: T::MAX_CONTENT, right: T::MAX_CONTENT, top: T::MAX_CONTENT, bottom: T::MAX_CONTENT };
}
impl<T: TaffyMaxContent> Rect<T> {
/// Returns a Rect where the left, right, top, and bottom values are all the max_content value of the contained type
/// (e.g. Dimension::Auto or LengthPercentageAuto::Auto)
pub const fn max_content() -> Self {
max_content::<Self>()
}
}
/// Returns a value of the inferred type which represent a `fit-content(…)` value
/// with the given argument.
pub fn fit_content<T: TaffyFitContent>(argument: LengthPercentage) -> T {
T::fit_content(argument)
}
/// Trait to create `fit-content(…)` values from plain numbers
pub trait TaffyFitContent {
/// Converts a LengthPercentage into Self
fn fit_content(argument: LengthPercentage) -> Self;
}
impl<T: TaffyFitContent> TaffyFitContent for Point<T> {
fn fit_content(argument: LengthPercentage) -> Self {
Point { x: T::fit_content(argument), y: T::fit_content(argument) }
}
}
impl<T: TaffyFitContent> Point<T> {
/// Returns a Point with x and y set to the same `fit-content(…)` value
/// with the given argument.
pub fn fit_content(argument: LengthPercentage) -> Self {
fit_content(argument)
}
}
impl<T: TaffyFitContent> TaffyFitContent for Line<T> {
fn fit_content(argument: LengthPercentage) -> Self {
Line { start: T::fit_content(argument), end: T::fit_content(argument) }
}
}
impl<T: TaffyFitContent> Line<T> {
/// Returns a Line with start and end set to the same `fit-content(…)` value
/// with the given argument.
pub fn fit_content(argument: LengthPercentage) -> Self {
fit_content(argument)
}
}
impl<T: TaffyFitContent> TaffyFitContent for Size<T> {
fn fit_content(argument: LengthPercentage) -> Self {
Size { width: T::fit_content(argument), height: T::fit_content(argument) }
}
}
impl<T: TaffyFitContent> Size<T> {
/// Returns a Size where with width and height set to the same `fit-content(…)` value
/// with the given argument.
pub fn fit_content(argument: LengthPercentage) -> Self {
fit_content(argument)
}
}
impl<T: TaffyFitContent> TaffyFitContent for Rect<T> {
fn fit_content(argument: LengthPercentage) -> Self {
Rect {
left: T::fit_content(argument),
right: T::fit_content(argument),
top: T::fit_content(argument),
bottom: T::fit_content(argument),
}
}
}
impl<T: TaffyFitContent> Rect<T> {
/// Returns a Rect where the left, right, top and bottom values are all constant fit_content value of the contained type
/// (e.g. 2.1, Some(2.1), or Dimension::Length(2.1))
pub fn fit_content(argument: LengthPercentage) -> Self {
fit_content(argument)
}
}
/// Returns a value of the inferred type which represent an absolute length
pub fn length<Input: Into<f32> + Copy, T: FromLength>(value: Input) -> T {
T::from_length(value)
}
/// Trait to create absolute length values from plain numbers
pub trait FromLength {
/// Converts into an `Into<f32>` into Self
fn from_length<Input: Into<f32> + Copy>(value: Input) -> Self;
}
impl FromLength for f32 {
fn from_length<Input: Into<f32> + Copy>(value: Input) -> Self {
value.into()
}
}
impl FromLength for Option<f32> {
fn from_length<Input: Into<f32> + Copy>(value: Input) -> Self {
Some(value.into())
}
}
impl<T: FromLength> FromLength for Point<T> {
fn from_length<Input: Into<f32> + Copy>(value: Input) -> Self {
Point { x: T::from_length(value.into()), y: T::from_length(value.into()) }
}
}
impl<T: FromLength> Point<T> {
/// Returns a Point where x and y values are the same given absolute length
pub fn length<Input: Into<f32> + Copy>(value: Input) -> Self {
length::<Input, Self>(value)
}
}
impl<T: FromLength> FromLength for Line<T> {
fn from_length<Input: Into<f32> + Copy>(value: Input) -> Self {
Line { start: T::from_length(value.into()), end: T::from_length(value.into()) }
}
}
impl<T: FromLength> Line<T> {
/// Returns a Line where both the start and end values are the same given absolute length
pub fn length<Input: Into<f32> + Copy>(value: Input) -> Self {
length::<Input, Self>(value)
}
}
impl<T: FromLength> FromLength for Size<T> {
fn from_length<Input: Into<f32> + Copy>(value: Input) -> Self {
Size { width: T::from_length(value.into()), height: T::from_length(value.into()) }
}
}
impl<T: FromLength> Size<T> {
/// Returns a Size where both the width and height values the same given absolute length
pub fn length<Input: Into<f32> + Copy>(value: Input) -> Self {
length::<Input, Self>(value)
}
}
impl<T: FromLength> FromLength for Rect<T> {
fn from_length<Input: Into<f32> + Copy>(value: Input) -> Self {
Rect {
left: T::from_length(value.into()),
right: T::from_length(value.into()),
top: T::from_length(value.into()),
bottom: T::from_length(value.into()),
}
}
}
impl<T: FromLength> Rect<T> {
/// Returns a Rect where the left, right, top and bottom values are all the same given absolute length
pub fn length<Input: Into<f32> + Copy>(value: Input) -> Self {
length::<Input, Self>(value)
}
}
/// Returns a value of the inferred type which represent a percentage
pub fn percent<Input: Into<f32> + Copy, T: FromPercent>(percent: Input) -> T {
T::from_percent(percent)
}
/// Trait to create constant percent values from plain numbers
pub trait FromPercent {
/// Converts into an `Into<f32>` into Self
fn from_percent<Input: Into<f32> + Copy>(percent: Input) -> Self;
}
impl FromPercent for f32 {
fn from_percent<Input: Into<f32> + Copy>(percent: Input) -> Self {
percent.into()
}
}
impl FromPercent for Option<f32> {
fn from_percent<Input: Into<f32> + Copy>(percent: Input) -> Self {
Some(percent.into())
}
}
impl<T: FromPercent> FromPercent for Point<T> {
fn from_percent<Input: Into<f32> + Copy>(percent: Input) -> Self {
Point { x: T::from_percent(percent.into()), y: T::from_percent(percent.into()) }
}
}
impl<T: FromPercent> Point<T> {
/// Returns a Point where both the x and y values are the constant percent value of the contained type
/// (e.g. 2.1, Some(2.1), or Dimension::Length(2.1))
pub fn percent<Input: Into<f32> + Copy>(percent_value: Input) -> Self {
percent::<Input, Self>(percent_value)
}
}
impl<T: FromPercent> FromPercent for Line<T> {
fn from_percent<Input: Into<f32> + Copy>(percent: Input) -> Self {
Line { start: T::from_percent(percent.into()), end: T::from_percent(percent.into()) }
}
}
impl<T: FromPercent> Line<T> {
/// Returns a Line where both the start and end values are the constant percent value of the contained type
/// (e.g. 2.1, Some(2.1), or Dimension::Length(2.1))
pub fn percent<Input: Into<f32> + Copy>(percent_value: Input) -> Self {
percent::<Input, Self>(percent_value)
}
}
impl<T: FromPercent> FromPercent for Size<T> {
fn from_percent<Input: Into<f32> + Copy>(percent: Input) -> Self {
Size { width: T::from_percent(percent.into()), height: T::from_percent(percent.into()) }
}
}
impl<T: FromPercent> Size<T> {
/// Returns a Size where both the width and height values are the constant percent value of the contained type
/// (e.g. 2.1, Some(2.1), or Dimension::Length(2.1))
pub fn percent<Input: Into<f32> + Copy>(percent_value: Input) -> Self {
percent::<Input, Self>(percent_value)
}
}
impl<T: FromPercent> FromPercent for Rect<T> {
fn from_percent<Input: Into<f32> + Copy>(percent: Input) -> Self {
Rect {
left: T::from_percent(percent.into()),
right: T::from_percent(percent.into()),
top: T::from_percent(percent.into()),
bottom: T::from_percent(percent.into()),
}
}
}
impl<T: FromPercent> Rect<T> {
/// Returns a Rect where the left, right, top and bottom values are all constant percent value of the contained type
/// (e.g. 2.1, Some(2.1), or Dimension::Length(2.1))
pub fn percent<Input: Into<f32> + Copy>(percent_value: Input) -> Self {
percent::<Input, Self>(percent_value)
}
}
/// Create a `Fraction` track sizing function (`fr` in CSS)
#[cfg(feature = "grid")]
pub fn fr<Input: Into<f32> + Copy, T: FromFlex>(flex: Input) -> T {
T::from_flex(flex)
}
/// Trait to create constant percent values from plain numbers
pub trait FromFlex {
/// Converts into an `Into<f32>` into Self
fn from_flex<Input: Into<f32> + Copy>(flex: Input) -> Self;
}
#[cfg(feature = "grid")]
#[cfg(test)]
mod repeat_fn_tests {
use super::repeat;
use crate::style::{GridTrackRepetition, NonRepeatedTrackSizingFunction, TrackSizingFunction};
const TEST_VEC: Vec<NonRepeatedTrackSizingFunction> = Vec::new();
#[test]
fn test_repeat_u16() {
assert_eq!(repeat(123, TEST_VEC), TrackSizingFunction::Repeat(GridTrackRepetition::Count(123), TEST_VEC));
}
#[test]
fn test_repeat_auto_fit_str() {
assert_eq!(repeat("auto-fit", TEST_VEC), TrackSizingFunction::Repeat(GridTrackRepetition::AutoFit, TEST_VEC));
}
#[test]
fn test_repeat_auto_fill_str() {
assert_eq!(repeat("auto-fill", TEST_VEC), TrackSizingFunction::Repeat(GridTrackRepetition::AutoFill, TEST_VEC));
}
}

174
vendor/taffy/src/test.rs vendored Normal file
View File

@@ -0,0 +1,174 @@
#![allow(dead_code)]
use taffy::{AvailableSpace, NodeId, Size, Style};
/// A shared measure function for tests which means that tests compiled with separate crates
/// and using different styles of measure function. This saves on compile time when running tests.
#[derive(Debug, Copy, Clone)]
pub struct TestNodeContext {
/// How many times the measure function has been called
pub count: usize,
/// The measurement data to compute the intrinsic size from
pub measure_data: TestMeasureData,
}
impl TestNodeContext {
/// Create a new `TestNodeContext` from `TestMeasureData`
pub const fn new(measure_data: TestMeasureData) -> Self {
Self { count: 0, measure_data }
}
/// Create a `TestNodeContext` for a zero-sized node
pub const fn zero() -> Self {
Self::new(TestMeasureData::Zero)
}
/// Create a `TestNodeContext` for a fixed-sized node
pub const fn fixed(size: Size<f32>) -> Self {
Self::new(TestMeasureData::Fixed(size))
}
/// Create a `TestNodeContext` for a node with a width and aspect-ratio
pub const fn aspect_ratio(width: f32, height_ratio: f32) -> Self {
let data = AspectRatioMeasureData { width, height_ratio };
Self::new(TestMeasureData::AspectRatio(data))
}
/// Create a `TestNodeContext` for a node with text using the Ahem font
pub const fn ahem_text(text_content: &'static str, writing_mode: WritingMode) -> Self {
let data = AhemTextMeasureData { text_content, writing_mode };
Self::new(TestMeasureData::AhemText(data))
}
}
/// The measurement data for the node
#[derive(Debug, Copy, Clone)]
pub enum TestMeasureData {
/// A zero-sized node
Zero,
/// A node with a fixed size
Fixed(Size<f32>),
/// A node with a fixed size
AspectRatio(AspectRatioMeasureData),
/// A node with text using the Ahem font
AhemText(AhemTextMeasureData),
}
/// A measure function for tests that works with `TestNodeContext`
pub fn test_measure_function(
known_dimensions: Size<Option<f32>>,
available_space: Size<AvailableSpace>,
_node_id: NodeId,
context: Option<&mut TestNodeContext>,
_style: &Style,
) -> Size<f32> {
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)) };
// 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),
};
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
#[derive(Debug, Copy, Clone)]
pub struct AspectRatioMeasureData {
width: f32,
height_ratio: f32,
}
impl AspectRatioMeasureData {
fn measure(&self, known_dimensions: Size<Option<f32>>) -> Size<f32> {
let width = known_dimensions.width.unwrap_or(self.width);
let height = known_dimensions.height.unwrap_or(width * self.height_ratio);
Size { width, height }
}
}
/// Whether text is horizontal or vertical
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WritingMode {
/// Horizontal text
Horizontal,
/// Vertical text
Vertical,
}
/// Measure data for nodes contain text using the Ahem testing font
#[derive(Debug, Clone, Copy)]
pub struct AhemTextMeasureData {
/// The text string
pub text_content: &'static str,
/// The writing mode
pub writing_mode: WritingMode,
}
impl AhemTextMeasureData {
fn measure(
&self,
known_dimensions: taffy::Size<Option<f32>>,
available_space: taffy::Size<taffy::AvailableSpace>,
) -> taffy::Size<f32> {
use taffy::prelude::*;
use taffy::AbsoluteAxis;
const ZWS: char = '\u{200B}';
const H_WIDTH: f32 = 10.0;
const H_HEIGHT: f32 = 10.0;
let inline_axis = match self.writing_mode {
WritingMode::Horizontal => AbsoluteAxis::Horizontal,
WritingMode::Vertical => AbsoluteAxis::Vertical,
};
let block_axis = inline_axis.other_axis();
let lines: Vec<&str> = self.text_content.split(ZWS).collect();
if lines.is_empty() {
return Size::ZERO;
}
let min_line_length: usize = lines.iter().map(|line| line.len()).max().unwrap_or(0);
let max_line_length: usize = lines.iter().map(|line| line.len()).sum();
let inline_size = known_dimensions
.get_abs(inline_axis)
.unwrap_or_else(|| match available_space.get_abs(inline_axis) {
AvailableSpace::MinContent => min_line_length as f32 * H_WIDTH,
AvailableSpace::MaxContent => max_line_length as f32 * H_WIDTH,
AvailableSpace::Definite(inline_size) => inline_size.min(max_line_length as f32 * H_WIDTH),
})
.max(min_line_length as f32 * H_WIDTH);
let block_size = known_dimensions.get_abs(block_axis).unwrap_or_else(|| {
let inline_line_length = (inline_size / H_WIDTH).floor() as usize;
let mut line_count = 1;
let mut current_line_length = 0;
for line in &lines {
if current_line_length + line.len() > inline_line_length {
if current_line_length > 0 {
line_count += 1
};
current_line_length = line.len();
} else {
current_line_length += line.len();
};
}
(line_count as f32) * H_HEIGHT
});
match self.writing_mode {
WritingMode::Horizontal => Size { width: inline_size, height: block_size },
WritingMode::Vertical => Size { width: block_size, height: inline_size },
}
}
}

184
vendor/taffy/src/tree/cache.rs vendored Normal file
View File

@@ -0,0 +1,184 @@
//! A cache for storing the results of layout computation
use crate::geometry::Size;
use crate::style::AvailableSpace;
use crate::tree::{LayoutOutput, RunMode};
/// The number of cache entries for each node in the tree
const CACHE_SIZE: usize = 9;
/// Cached intermediate layout results
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub(crate) struct CacheEntry<T> {
/// The initial cached size of the node itself
known_dimensions: Size<Option<f32>>,
/// The initial cached size of the parent's node
available_space: Size<AvailableSpace>,
/// The cached size and baselines of the item
content: T,
}
/// A cache for caching the results of a sizing a Grid Item or Flexbox Item
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct Cache {
/// The cache entry for the node's final layout
final_layout_entry: Option<CacheEntry<LayoutOutput>>,
/// The cache entries for the node's preliminary size measurements
measure_entries: [Option<CacheEntry<Size<f32>>>; CACHE_SIZE],
}
impl Default for Cache {
fn default() -> Self {
Self::new()
}
}
impl Cache {
/// Create a new empty cache
pub const fn new() -> Self {
Self { final_layout_entry: None, measure_entries: [None; CACHE_SIZE] }
}
/// Return the cache slot to cache the current computed result in
///
/// ## Caching Strategy
///
/// We need multiple cache slots, because a node's size is often queried by it's parent multiple times in the course of the layout
/// process, and we don't want later results to clobber earlier ones.
///
/// The two variables that we care about when determining cache slot are:
///
/// - How many "known_dimensions" are set. In the worst case, a node may be called first with neither dimension known, then with one
/// dimension known (either width of height - which doesn't matter for our purposes here), and then with both dimensions known.
/// - Whether unknown dimensions are being sized under a min-content or a max-content available space constraint (definite available space
/// shares a cache slot with max-content because a node will generally be sized under one or the other but not both).
///
/// ## Cache slots:
///
/// - Slot 0: Both known_dimensions were set
/// - Slots 1-4: 1 of 2 known_dimensions were set and:
/// - Slot 1: width but not height known_dimension was set and the other dimension was either a MaxContent or Definite available space constraintraint
/// - Slot 2: width but not height known_dimension was set and the other dimension was a MinContent constraint
/// - Slot 3: height but not width known_dimension was set and the other dimension was either a MaxContent or Definite available space constraintable space constraint
/// - Slot 4: height but not width known_dimension was set and the other dimension was a MinContent constraint
/// - Slots 5-8: Neither known_dimensions were set and:
/// - Slot 5: x-axis available space is MaxContent or Definite and y-axis available space is MaxContent or Definite
/// - Slot 6: x-axis available space is MaxContent or Definite and y-axis available space is MinContent
/// - Slot 7: x-axis available space is MinContent and y-axis available space is MaxContent or Definite
/// - Slot 8: x-axis available space is MinContent and y-axis available space is MinContent
#[inline]
fn compute_cache_slot(known_dimensions: Size<Option<f32>>, available_space: Size<AvailableSpace>) -> usize {
use AvailableSpace::{Definite, MaxContent, MinContent};
let has_known_width = known_dimensions.width.is_some();
let has_known_height = known_dimensions.height.is_some();
// Slot 0: Both known_dimensions were set
if has_known_width && has_known_height {
return 0;
}
// Slot 1: width but not height known_dimension was set and the other dimension was either a MaxContent or Definite available space constraint
// Slot 2: width but not height known_dimension was set and the other dimension was a MinContent constraint
if has_known_width && !has_known_height {
return 1 + (available_space.height == MinContent) as usize;
}
// Slot 3: height but not width known_dimension was set and the other dimension was either a MaxContent or Definite available space constraint
// Slot 4: height but not width known_dimension was set and the other dimension was a MinContent constraint
if has_known_height && !has_known_width {
return 3 + (available_space.width == MinContent) as usize;
}
// Slots 5-8: Neither known_dimensions were set and:
match (available_space.width, available_space.height) {
// Slot 5: x-axis available space is MaxContent or Definite and y-axis available space is MaxContent or Definite
(MaxContent | Definite(_), MaxContent | Definite(_)) => 5,
// Slot 6: x-axis available space is MaxContent or Definite and y-axis available space is MinContent
(MaxContent | Definite(_), MinContent) => 6,
// Slot 7: x-axis available space is MinContent and y-axis available space is MaxContent or Definite
(MinContent, MaxContent | Definite(_)) => 7,
// Slot 8: x-axis available space is MinContent and y-axis available space is MinContent
(MinContent, MinContent) => 8,
}
}
/// Try to retrieve a cached result from the cache
#[inline]
pub fn get(
&self,
known_dimensions: Size<Option<f32>>,
available_space: Size<AvailableSpace>,
run_mode: RunMode,
) -> Option<LayoutOutput> {
match run_mode {
RunMode::PerformLayout => self
.final_layout_entry
.filter(|entry| {
let cached_size = entry.content.size;
(known_dimensions.width == entry.known_dimensions.width
|| known_dimensions.width == Some(cached_size.width))
&& (known_dimensions.height == entry.known_dimensions.height
|| known_dimensions.height == Some(cached_size.height))
&& (known_dimensions.width.is_some()
|| entry.available_space.width.is_roughly_equal(available_space.width))
&& (known_dimensions.height.is_some()
|| entry.available_space.height.is_roughly_equal(available_space.height))
})
.map(|e| e.content),
RunMode::ComputeSize => {
for entry in self.measure_entries.iter().flatten() {
let cached_size = entry.content;
if (known_dimensions.width == entry.known_dimensions.width
|| known_dimensions.width == Some(cached_size.width))
&& (known_dimensions.height == entry.known_dimensions.height
|| known_dimensions.height == Some(cached_size.height))
&& (known_dimensions.width.is_some()
|| entry.available_space.width.is_roughly_equal(available_space.width))
&& (known_dimensions.height.is_some()
|| entry.available_space.height.is_roughly_equal(available_space.height))
{
return Some(LayoutOutput::from_outer_size(cached_size));
}
}
None
}
RunMode::PerformHiddenLayout => None,
}
}
/// Store a computed size in the cache
pub fn store(
&mut self,
known_dimensions: Size<Option<f32>>,
available_space: Size<AvailableSpace>,
run_mode: RunMode,
layout_output: LayoutOutput,
) {
match run_mode {
RunMode::PerformLayout => {
self.final_layout_entry = Some(CacheEntry { known_dimensions, available_space, content: layout_output })
}
RunMode::ComputeSize => {
let cache_slot = Self::compute_cache_slot(known_dimensions, available_space);
self.measure_entries[cache_slot] =
Some(CacheEntry { known_dimensions, available_space, content: layout_output.size });
}
RunMode::PerformHiddenLayout => {}
}
}
/// Clear all cache entries
pub fn clear(&mut self) {
self.final_layout_entry = None;
self.measure_entries = [None; CACHE_SIZE];
}
/// Returns true if all cache entries are None, else false
pub fn is_empty(&self) -> bool {
self.final_layout_entry.is_none() && !self.measure_entries.iter().any(|entry| entry.is_some())
}
}

357
vendor/taffy/src/tree/layout.rs vendored Normal file
View File

@@ -0,0 +1,357 @@
//! Final data structures that represent the high-level UI layout
use crate::geometry::{AbsoluteAxis, Line, Point, Rect, Size};
use crate::style::AvailableSpace;
use crate::style_helpers::TaffyMaxContent;
use crate::util::sys::{f32_max, f32_min};
/// Whether we are performing a full layout, or we merely need to size the node
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub enum RunMode {
/// A full layout for this node and all children should be computed
PerformLayout,
/// The layout algorithm should be executed such that an accurate container size for the node can be determined.
/// Layout steps that aren't necessary for determining the container size of the current node can be skipped.
ComputeSize,
/// This node should have a null layout set as it has been hidden (i.e. using `Display::None`)
PerformHiddenLayout,
}
/// Whether styles should be taken into account when computing size
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub enum SizingMode {
/// Only content contributions should be taken into account
ContentSize,
/// Inherent size styles should be taken into account in addition to content contributions
InherentSize,
}
/// A set of margins that are available for collapsing with for block layout's margin collapsing
#[derive(Copy, Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct CollapsibleMarginSet {
/// The largest positive margin
positive: f32,
/// The smallest negative margin (with largest absolute value)
negative: f32,
}
impl CollapsibleMarginSet {
/// A default margin set with no collapsible margins
pub const ZERO: Self = Self { positive: 0.0, negative: 0.0 };
/// Create a set from a single margin
pub fn from_margin(margin: f32) -> Self {
if margin >= 0.0 {
Self { positive: margin, negative: 0.0 }
} else {
Self { positive: 0.0, negative: margin }
}
}
/// Collapse a single margin with this set
pub fn collapse_with_margin(mut self, margin: f32) -> Self {
if margin >= 0.0 {
self.positive = f32_max(self.positive, margin);
} else {
self.negative = f32_min(self.negative, margin);
}
self
}
/// Collapse another margin set with this set
pub fn collapse_with_set(mut self, other: CollapsibleMarginSet) -> Self {
self.positive = f32_max(self.positive, other.positive);
self.negative = f32_min(self.negative, other.negative);
self
}
/// Resolve the resultant margin from this set once all collapsible margins
/// have been collapsed into it
pub fn resolve(&self) -> f32 {
self.positive + self.negative
}
}
/// An axis that layout algorithms can be requested to compute a size for
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub enum RequestedAxis {
/// The horizontal axis
Horizontal,
/// The vertical axis
Vertical,
/// Both axes
Both,
}
impl From<AbsoluteAxis> for RequestedAxis {
fn from(value: AbsoluteAxis) -> Self {
match value {
AbsoluteAxis::Horizontal => RequestedAxis::Horizontal,
AbsoluteAxis::Vertical => RequestedAxis::Vertical,
}
}
}
impl TryFrom<RequestedAxis> for AbsoluteAxis {
type Error = ();
fn try_from(value: RequestedAxis) -> Result<Self, Self::Error> {
match value {
RequestedAxis::Horizontal => Ok(AbsoluteAxis::Horizontal),
RequestedAxis::Vertical => Ok(AbsoluteAxis::Vertical),
RequestedAxis::Both => Err(()),
}
}
}
/// A struct containing the inputs constraints/hints for laying out a node, which are passed in by the parent
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct LayoutInput {
/// Whether we only need to know the Node's size, or whe
pub run_mode: RunMode,
/// Whether a Node's style sizes should be taken into account or ignored
pub sizing_mode: SizingMode,
/// Which axis we need the size of
pub axis: RequestedAxis,
/// Known dimensions represent dimensions (width/height) which should be taken as fixed when performing layout.
/// For example, if known_dimensions.width is set to Some(WIDTH) then this means something like:
///
/// "What would the height of this node be, assuming the width is WIDTH"
///
/// Layout functions will be called with both known_dimensions set for final layout. Where the meaning is:
///
/// "The exact size of this node is WIDTHxHEIGHT. Please lay out your children"
///
pub known_dimensions: Size<Option<f32>>,
/// Parent size dimensions are intended to be used for percentage resolution.
pub parent_size: Size<Option<f32>>,
/// Available space represents an amount of space to layout into, and is used as a soft constraint
/// for the purpose of wrapping.
pub available_space: Size<AvailableSpace>,
/// Specific to CSS Block layout. Used for correctly computing margin collapsing. You probably want to set this to `Line::FALSE`.
pub vertical_margins_are_collapsible: Line<bool>,
}
impl LayoutInput {
/// A LayoutInput that can be used to request hidden layout
pub const HIDDEN: LayoutInput = LayoutInput {
// The important property for hidden layout
run_mode: RunMode::PerformHiddenLayout,
// The rest will be ignored
known_dimensions: Size::NONE,
parent_size: Size::NONE,
available_space: Size::MAX_CONTENT,
sizing_mode: SizingMode::InherentSize,
axis: RequestedAxis::Both,
vertical_margins_are_collapsible: Line::FALSE,
};
}
/// A struct containing the result of laying a single node, which is returned up to the parent node
///
/// A baseline is the line on which text sits. Your node likely has a baseline if it is a text node, or contains
/// children that may be text nodes. See <https://www.w3.org/TR/css-writing-modes-3/#intro-baselines> for details.
/// If your node does not have a baseline (or you are unsure how to compute it), then simply return `Point::NONE`
/// for the first_baselines field
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct LayoutOutput {
/// The size of the node
pub size: Size<f32>,
#[cfg(feature = "content_size")]
/// The size of the content within the node
pub content_size: Size<f32>,
/// The first baseline of the node in each dimension, if any
pub first_baselines: Point<Option<f32>>,
/// Top margin that can be collapsed with. This is used for CSS block layout and can be set to
/// `CollapsibleMarginSet::ZERO` for other layout modes that don't support margin collapsing
pub top_margin: CollapsibleMarginSet,
/// Bottom margin that can be collapsed with. This is used for CSS block layout and can be set to
/// `CollapsibleMarginSet::ZERO` for other layout modes that don't support margin collapsing
pub bottom_margin: CollapsibleMarginSet,
/// Whether margins can be collapsed through this node. This is used for CSS block layout and can
/// be set to `false` for other layout modes that don't support margin collapsing
pub margins_can_collapse_through: bool,
}
impl LayoutOutput {
/// An all-zero `LayoutOutput` for hidden nodes
pub const HIDDEN: Self = Self {
size: Size::ZERO,
#[cfg(feature = "content_size")]
content_size: Size::ZERO,
first_baselines: Point::NONE,
top_margin: CollapsibleMarginSet::ZERO,
bottom_margin: CollapsibleMarginSet::ZERO,
margins_can_collapse_through: false,
};
/// A blank layout output
pub const DEFAULT: Self = Self::HIDDEN;
/// Constructor to create a `LayoutOutput` from just the size and baselines
pub fn from_sizes_and_baselines(
size: Size<f32>,
#[cfg_attr(not(feature = "content_size"), allow(unused_variables))] content_size: Size<f32>,
first_baselines: Point<Option<f32>>,
) -> Self {
Self {
size,
#[cfg(feature = "content_size")]
content_size,
first_baselines,
top_margin: CollapsibleMarginSet::ZERO,
bottom_margin: CollapsibleMarginSet::ZERO,
margins_can_collapse_through: false,
}
}
/// Construct a SizeBaselinesAndMargins from just the container and content sizes
pub fn from_sizes(size: Size<f32>, content_size: Size<f32>) -> Self {
Self::from_sizes_and_baselines(size, content_size, Point::NONE)
}
/// Construct a SizeBaselinesAndMargins from just the container's size.
pub fn from_outer_size(size: Size<f32>) -> Self {
Self::from_sizes(size, Size::zero())
}
}
/// The final result of a layout algorithm for a single node.
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct Layout {
/// The relative ordering of the node
///
/// Nodes with a higher order should be rendered on top of those with a lower order.
/// This is effectively a topological sort of each tree.
pub order: u32,
/// The top-left corner of the node
pub location: Point<f32>,
/// The width and height of the node
pub size: Size<f32>,
#[cfg(feature = "content_size")]
/// The width and height of the content inside the node. This may be larger than the size of the node in the case of
/// overflowing content and is useful for computing a "scroll width/height" for scrollable nodes
pub content_size: Size<f32>,
/// The size of the scrollbars in each dimension. If there is no scrollbar then the size will be zero.
pub scrollbar_size: Size<f32>,
/// The size of the borders of the node
pub border: Rect<f32>,
/// The size of the padding of the node
pub padding: Rect<f32>,
/// The size of the margin of the node
pub margin: Rect<f32>,
}
impl Default for Layout {
fn default() -> Self {
Self::new()
}
}
impl Layout {
/// Creates a new zero-[`Layout`].
///
/// The Zero-layout has size and location set to ZERO.
/// The `order` value of this layout is set to the minimum value of 0.
/// This means it should be rendered below all other [`Layout`]s.
#[must_use]
pub const fn new() -> Self {
Self {
order: 0,
location: Point::ZERO,
size: Size::zero(),
#[cfg(feature = "content_size")]
content_size: Size::zero(),
scrollbar_size: Size::zero(),
border: Rect::zero(),
padding: Rect::zero(),
margin: Rect::zero(),
}
}
/// Creates a new zero-[`Layout`] with the supplied `order` value.
///
/// Nodes with a higher order should be rendered on top of those with a lower order.
/// The Zero-layout has size and location set to ZERO.
#[must_use]
pub const fn with_order(order: u32) -> Self {
Self {
order,
size: Size::zero(),
location: Point::ZERO,
#[cfg(feature = "content_size")]
content_size: Size::zero(),
scrollbar_size: Size::zero(),
border: Rect::zero(),
padding: Rect::zero(),
margin: Rect::zero(),
}
}
/// Get the width of the node's content box
#[inline]
pub fn content_box_width(&self) -> f32 {
self.size.width - self.padding.left - self.padding.right - self.border.left - self.border.right
}
/// Get the height of the node's content box
#[inline]
pub fn content_box_height(&self) -> f32 {
self.size.height - self.padding.top - self.padding.bottom - self.border.top - self.border.bottom
}
/// Get the size of the node's content box
#[inline]
pub fn content_box_size(&self) -> Size<f32> {
Size { width: self.content_box_width(), height: self.content_box_height() }
}
/// Get x offset of the node's content box relative to it's parent's border box
pub fn content_box_x(&self) -> f32 {
self.location.x + self.border.left + self.padding.left
}
/// Get x offset of the node's content box relative to it's parent's border box
pub fn content_box_y(&self) -> f32 {
self.location.y + self.border.top + self.padding.top
}
}
#[cfg(feature = "content_size")]
impl Layout {
/// Return the scroll width of the node.
/// The scroll width is the difference between the width and the content width, floored at zero
pub fn scroll_width(&self) -> f32 {
f32_max(
0.0,
self.content_size.width + f32_min(self.scrollbar_size.width, self.size.width) - self.size.width
+ self.border.right,
)
}
/// Return the scroll height of the node.
/// The scroll height is the difference between the height and the content height, floored at zero
pub fn scroll_height(&self) -> f32 {
f32_max(
0.0,
self.content_size.height + f32_min(self.scrollbar_size.height, self.size.height) - self.size.height
+ self.border.bottom,
)
}
}
/// The additional information from layout algorithm
#[cfg(feature = "detailed_layout_info")]
#[derive(Debug, Clone, PartialEq)]
pub enum DetailedLayoutInfo {
/// Enum variant for [`DetailedGridInfo`](crate::compute::grid::DetailedGridInfo)
#[cfg(feature = "grid")]
Grid(Box<crate::compute::grid::DetailedGridInfo>),
/// For node that hasn't had any detailed information yet
None,
}

33
vendor/taffy/src/tree/mod.rs vendored Normal file
View File

@@ -0,0 +1,33 @@
//! Contains both a high-level interface to Taffy using a ready-made node tree, and a set of traits for defining custom node trees.
//!
//! - For documentation on the high-level API, see the [`TaffyTree`] struct.
//! - For documentation on the low-level trait-based API, see the [`traits`] module.
// Submodules
mod cache;
mod layout;
mod node;
pub mod traits;
pub use cache::Cache;
pub use layout::{CollapsibleMarginSet, Layout, LayoutInput, LayoutOutput, RequestedAxis, RunMode, SizingMode};
pub use node::NodeId;
pub(crate) use traits::LayoutPartialTreeExt;
pub use traits::{LayoutPartialTree, PrintTree, RoundTree, TraversePartialTree, TraverseTree};
#[cfg(feature = "flexbox")]
pub use traits::LayoutFlexboxContainer;
#[cfg(feature = "grid")]
pub use traits::LayoutGridContainer;
#[cfg(feature = "block_layout")]
pub use traits::LayoutBlockContainer;
#[cfg(feature = "taffy_tree")]
mod taffy_tree;
#[cfg(feature = "taffy_tree")]
pub use taffy_tree::{TaffyError, TaffyResult, TaffyTree};
#[cfg(feature = "detailed_layout_info")]
pub use layout::DetailedLayoutInfo;

60
vendor/taffy/src/tree/node.rs vendored Normal file
View File

@@ -0,0 +1,60 @@
//! UI node types and related data structures.
//!
//! Layouts are composed of multiple nodes, which live in a tree-like data structure.
#[cfg(feature = "taffy_tree")]
use slotmap::{DefaultKey, Key, KeyData};
/// A type representing the id of a single node in a tree of nodes
///
/// Internally it is a wrapper around a u64 and a `NodeId` can be converted to and from
/// and u64 if needed.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct NodeId(u64);
impl NodeId {
/// Create a new NodeId from a u64 value
pub const fn new(val: u64) -> Self {
Self(val)
}
}
impl From<u64> for NodeId {
#[inline]
fn from(raw: u64) -> Self {
Self(raw)
}
}
impl From<NodeId> for u64 {
#[inline]
fn from(id: NodeId) -> Self {
id.0
}
}
impl From<usize> for NodeId {
#[inline]
fn from(raw: usize) -> Self {
Self(raw as u64)
}
}
impl From<NodeId> for usize {
#[inline]
fn from(id: NodeId) -> Self {
id.0 as usize
}
}
#[cfg(feature = "taffy_tree")]
impl From<DefaultKey> for NodeId {
#[inline]
fn from(key: DefaultKey) -> Self {
Self(key.data().as_ffi())
}
}
#[cfg(feature = "taffy_tree")]
impl From<NodeId> for DefaultKey {
#[inline]
fn from(key: NodeId) -> Self {
KeyData::from_ffi(key.0).into()
}
}

1388
vendor/taffy/src/tree/taffy_tree.rs vendored Normal file

File diff suppressed because it is too large Load Diff

365
vendor/taffy/src/tree/traits.rs vendored Normal file
View File

@@ -0,0 +1,365 @@
//! The abstractions that make up the core of Taffy's low-level API
//!
//! ## Examples
//!
//! The following examples demonstrate end-to-end implementation of Taffy's traits and usage of the low-level compute APIs:
//!
//! - [custom_tree_vec](https://github.com/DioxusLabs/taffy/blob/main/examples/custom_tree_vec.rs) which implements a custom Taffy tree using a `Vec` as an arena with NodeId's being index's into the Vec.
//! - [custom_tree_owned_partial](https://github.com/DioxusLabs/taffy/blob/main/examples/custom_tree_owned_partial.rs) which implements a custom Taffy tree using directly owned children with NodeId's being index's into vec on parent node.
//! - [custom_tree_owned_unsafe](https://github.com/DioxusLabs/taffy/blob/main/examples/custom_tree_owned_unsafe.rs) which implements a custom Taffy tree using directly owned children with NodeId's being pointers.
//!
//! ## Overview
//!
//! ### Trait dependency tree
//!
//! The tree below illustrates which traits depend on which other traits.
//!
//! ```text
//! TraversePartialTree - Access a node's children
//! ├── LayoutPartialTree - Run layout algorithms on a node and it's direct children
//! └── TraverseTree - Recursively access a node's descendants
//! ├── RoundTree - Round a float-valued` layout to integer pixels
//! └── PrintTree - Print a debug representation of a node tree
//! ```
//!
//! ### A table of traits
//!
//! | Trait | Requires | Enables |
//! | --- | --- | --- |
//! | [`LayoutPartialTree`] | [`TraversePartialTree`] | [`compute_flexbox_layout`](crate::compute_flexbox_layout)<br />[`compute_grid_layout`](crate::compute_grid_layout)<br />[`compute_block_layout`](crate::compute_block_layout)<br />[`compute_root_layout`](crate::compute_root_layout)<br />[`compute_leaf_layout`](crate::compute_leaf_layout)<br />[`compute_hidden_layout`](crate::compute_hidden_layout)<br />[`compute_cached_layout`](crate::compute_cached_layout) |
//! | [`RoundTree`] | [`TraverseTree`] | [`round_layout`](crate::round_layout) |
//! | [`PrintTree`] | [`TraverseTree`] | [`print_tree`](crate::print_tree) |
//!
//! ## All of the traits on one page
//!
//! ### TraversePartialTree and TraverseTree
//! These traits are Taffy's abstraction for downward tree traversal:
//! - [`TraversePartialTree`] allows access to a single container node, and it's immediate children. This is the only "traverse" trait that is required
//! for use of Taffy's core layout algorithms (flexbox, grid, etc).
//! - [`TraverseTree`] is a marker trait which uses the same API signature as `TraversePartialTree`, but extends it with a guarantee that the child/children methods can be used to recurse
//! infinitely down the tree. It is required by the `RoundTree` and
//! the `PrintTree` traits.
//! ```rust
//! # use taffy::*;
//! pub trait TraversePartialTree {
//! /// Type representing an iterator of the children of a node
//! type ChildIter<'a>: Iterator<Item = NodeId>
//! where
//! Self: 'a;
//!
//! /// Get the list of children IDs for the given node
//! fn child_ids(&self, parent_node_id: NodeId) -> Self::ChildIter<'_>;
//!
//! /// Get the number of children for the given node
//! fn child_count(&self, parent_node_id: NodeId) -> usize;
//!
//! /// Get a specific child of a node, where the index represents the nth child
//! fn get_child_id(&self, parent_node_id: NodeId, child_index: usize) -> NodeId;
//! }
//!
//! pub trait TraverseTree: TraversePartialTree {}
//! ```
//!
//! You must implement [`TraversePartialTree`] to access any of Taffy's low-level API. If your tree implementation allows you to implement [`TraverseTree`] with
//! the correct semantics (full recursive traversal is available) then you should.
//!
//! ### LayoutPartialTree
//!
//! **Requires:** `TraversePartialTree`<br />
//! **Enables:** Flexbox, Grid, Block and Leaf layout algorithms from the [`crate::compute`] module
//!
//! Any type that implements [`LayoutPartialTree`] can be laid out using [Taffy's algorithms](crate::compute)
//!
//! Note that this trait extends [`TraversePartialTree`] (not [`TraverseTree`]). Taffy's algorithm implementations have been designed such that they can be used for a laying out a single
//! node that only has access to it's immediate children.
//!
//! ```rust
//! # use taffy::*;
//! pub trait LayoutPartialTree: TraversePartialTree {
//! /// Get a reference to the [`Style`] for this node.
//! fn get_style(&self, node_id: NodeId) -> &Style;
//!
//! /// Set the node's unrounded layout
//! fn set_unrounded_layout(&mut self, node_id: NodeId, layout: &Layout);
//!
//! /// Get a mutable reference to the [`Cache`] for this node.
//! fn get_cache_mut(&mut self, node_id: NodeId) -> &mut Cache;
//!
//! /// Compute the specified node's size or full layout given the specified constraints
//! fn compute_child_layout(&mut self, node_id: NodeId, inputs: LayoutInput) -> LayoutOutput;
//! }
//! ```
//!
//! ### RoundTree
//!
//! **Requires:** `TraverseTree`
//!
//! Trait used by the `round_layout` method which takes a tree of unrounded float-valued layouts and performs
//! rounding to snap the values to the pixel grid.
//!
//! As indicated by it's dependence on `TraverseTree`, it required full recursive access to the tree.
//!
//! ```rust
//! # use taffy::*;
//! pub trait RoundTree: TraverseTree {
//! /// Get the node's unrounded layout
//! fn get_unrounded_layout(&self, node_id: NodeId) -> &Layout;
//! /// Get a reference to the node's final layout
//! fn set_final_layout(&mut self, node_id: NodeId, layout: &Layout);
//! }
//! ```
//!
//! ### PrintTree
//!
//! **Requires:** `TraverseTree`
//!
//! ```rust
//! /// Trait used by the `print_tree` method which prints a debug representation
//! ///
//! /// As indicated by it's dependence on `TraverseTree`, it required full recursive access to the tree.
//! # use taffy::*;
//! pub trait PrintTree: TraverseTree {
//! /// Get a debug label for the node (typically the type of node: flexbox, grid, text, image, etc)
//! fn get_debug_label(&self, node_id: NodeId) -> &'static str;
//! /// Get a reference to the node's final layout
//! fn get_final_layout(&self, node_id: NodeId) -> &Layout;
//! }
//! ```
//!
use super::{Layout, LayoutInput, LayoutOutput, NodeId, RequestedAxis, RunMode, SizingMode};
#[cfg(feature = "detailed_layout_info")]
use crate::debug::debug_log;
use crate::geometry::{AbsoluteAxis, Line, Size};
use crate::style::{AvailableSpace, CoreStyle};
#[cfg(feature = "flexbox")]
use crate::style::{FlexboxContainerStyle, FlexboxItemStyle};
#[cfg(feature = "grid")]
use crate::style::{GridContainerStyle, GridItemStyle};
#[cfg(feature = "block_layout")]
use crate::{BlockContainerStyle, BlockItemStyle};
#[cfg(all(feature = "grid", feature = "detailed_layout_info"))]
use crate::compute::grid::DetailedGridInfo;
/// Taffy's abstraction for downward tree traversal.
///
/// However, this trait does *not* require access to any node's other than a single container node's immediate children unless you also intend to implement `TraverseTree`.
pub trait TraversePartialTree {
/// Type representing an iterator of the children of a node
type ChildIter<'a>: Iterator<Item = NodeId>
where
Self: 'a;
/// Get the list of children IDs for the given node
fn child_ids(&self, parent_node_id: NodeId) -> Self::ChildIter<'_>;
/// Get the number of children for the given node
fn child_count(&self, parent_node_id: NodeId) -> usize;
/// Get a specific child of a node, where the index represents the nth child
fn get_child_id(&self, parent_node_id: NodeId, child_index: usize) -> NodeId;
}
/// A marker trait which extends `TraversePartialTree`
///
/// Implementing this trait implies the additional guarantee that the child/children methods can be used to recurse
/// infinitely down the tree. Is required by the `RoundTree` and the `PrintTree` traits.
pub trait TraverseTree: TraversePartialTree {}
/// Any type that implements [`LayoutPartialTree`] can be laid out using [Taffy's algorithms](crate::compute)
///
/// Note that this trait extends [`TraversePartialTree`] (not [`TraverseTree`]). Taffy's algorithm implementations have been designed such that they can be used for a laying out a single
/// node that only has access to it's immediate children.
pub trait LayoutPartialTree: TraversePartialTree {
/// The style type representing the core container styles that all containers should have
/// Used when laying out the root node of a tree
type CoreContainerStyle<'a>: CoreStyle
where
Self: 'a;
/// Get core style
fn get_core_container_style(&self, node_id: NodeId) -> Self::CoreContainerStyle<'_>;
/// Set the node's unrounded layout
fn set_unrounded_layout(&mut self, node_id: NodeId, layout: &Layout);
/// Compute the specified node's size or full layout given the specified constraints
fn compute_child_layout(&mut self, node_id: NodeId, inputs: LayoutInput) -> LayoutOutput;
}
/// Trait used by the `compute_cached_layout` method which allows cached layout results to be stored and retrieved.
///
/// The `Cache` struct implements a per-node cache that is compatible with this trait.
pub trait CacheTree {
/// Try to retrieve a cached result from the cache
fn cache_get(
&self,
node_id: NodeId,
known_dimensions: Size<Option<f32>>,
available_space: Size<AvailableSpace>,
run_mode: RunMode,
) -> Option<LayoutOutput>;
/// Store a computed size in the cache
fn cache_store(
&mut self,
node_id: NodeId,
known_dimensions: Size<Option<f32>>,
available_space: Size<AvailableSpace>,
run_mode: RunMode,
layout_output: LayoutOutput,
);
/// Clear all cache entries for the node
fn cache_clear(&mut self, node_id: NodeId);
}
/// Trait used by the `round_layout` method which takes a tree of unrounded float-valued layouts and performs
/// rounding to snap the values to the pixel grid.
///
/// As indicated by it's dependence on `TraverseTree`, it required full recursive access to the tree.
pub trait RoundTree: TraverseTree {
/// Get the node's unrounded layout
fn get_unrounded_layout(&self, node_id: NodeId) -> &Layout;
/// Get a reference to the node's final layout
fn set_final_layout(&mut self, node_id: NodeId, layout: &Layout);
}
/// Trait used by the `print_tree` method which prints a debug representation
///
/// As indicated by it's dependence on `TraverseTree`, it required full recursive access to the tree.
pub trait PrintTree: TraverseTree {
/// Get a debug label for the node (typically the type of node: flexbox, grid, text, image, etc)
fn get_debug_label(&self, node_id: NodeId) -> &'static str;
/// Get a reference to the node's final layout
fn get_final_layout(&self, node_id: NodeId) -> &Layout;
}
#[cfg(feature = "flexbox")]
/// Extends [`LayoutPartialTree`] with getters for the styles required for Flexbox layout
pub trait LayoutFlexboxContainer: LayoutPartialTree {
/// The style type representing the Flexbox container's styles
type FlexboxContainerStyle<'a>: FlexboxContainerStyle
where
Self: 'a;
/// The style type representing each Flexbox item's styles
type FlexboxItemStyle<'a>: FlexboxItemStyle
where
Self: 'a;
/// Get the container's styles
fn get_flexbox_container_style(&self, node_id: NodeId) -> Self::FlexboxContainerStyle<'_>;
/// Get the child's styles
fn get_flexbox_child_style(&self, child_node_id: NodeId) -> Self::FlexboxItemStyle<'_>;
}
#[cfg(feature = "grid")]
/// Extends [`LayoutPartialTree`] with getters for the styles required for CSS Grid layout
pub trait LayoutGridContainer: LayoutPartialTree {
/// The style type representing the CSS Grid container's styles
type GridContainerStyle<'a>: GridContainerStyle
where
Self: 'a;
/// The style type representing each CSS Grid item's styles
type GridItemStyle<'a>: GridItemStyle
where
Self: 'a;
/// Get the container's styles
fn get_grid_container_style(&self, node_id: NodeId) -> Self::GridContainerStyle<'_>;
/// Get the child's styles
fn get_grid_child_style(&self, child_node_id: NodeId) -> Self::GridItemStyle<'_>;
/// Set the node's detailed grid information
///
/// Implementing this method is optional. Doing so allows you to access details about the the grid such as
/// the computed size of each grid track and the computed placement of each grid item.
#[cfg(feature = "detailed_layout_info")]
fn set_detailed_grid_info(&mut self, _node_id: NodeId, _detailed_grid_info: DetailedGridInfo) {
debug_log!("LayoutGridContainer::set_detailed_grid_info called");
}
}
#[cfg(feature = "block_layout")]
/// Extends [`LayoutPartialTree`] with getters for the styles required for CSS Block layout
pub trait LayoutBlockContainer: LayoutPartialTree {
/// The style type representing the CSS Block container's styles
type BlockContainerStyle<'a>: BlockContainerStyle
where
Self: 'a;
/// The style type representing each CSS Block item's styles
type BlockItemStyle<'a>: BlockItemStyle
where
Self: 'a;
/// Get the container's styles
fn get_block_container_style(&self, node_id: NodeId) -> Self::BlockContainerStyle<'_>;
/// Get the child's styles
fn get_block_child_style(&self, child_node_id: NodeId) -> Self::BlockItemStyle<'_>;
}
// --- PRIVATE TRAITS
/// A private trait which allows us to add extra convenience methods to types which implement
/// LayoutTree without making those methods public.
pub(crate) trait LayoutPartialTreeExt: LayoutPartialTree {
/// Compute the size of the node given the specified constraints
#[inline(always)]
#[allow(clippy::too_many_arguments)]
fn measure_child_size(
&mut self,
node_id: NodeId,
known_dimensions: Size<Option<f32>>,
parent_size: Size<Option<f32>>,
available_space: Size<AvailableSpace>,
sizing_mode: SizingMode,
axis: AbsoluteAxis,
vertical_margins_are_collapsible: Line<bool>,
) -> f32 {
self.compute_child_layout(
node_id,
LayoutInput {
known_dimensions,
parent_size,
available_space,
sizing_mode,
axis: axis.into(),
run_mode: RunMode::ComputeSize,
vertical_margins_are_collapsible,
},
)
.size
.get_abs(axis)
}
/// Perform a full layout on the node given the specified constraints
#[inline(always)]
fn perform_child_layout(
&mut self,
node_id: NodeId,
known_dimensions: Size<Option<f32>>,
parent_size: Size<Option<f32>>,
available_space: Size<AvailableSpace>,
sizing_mode: SizingMode,
vertical_margins_are_collapsible: Line<bool>,
) -> LayoutOutput {
self.compute_child_layout(
node_id,
LayoutInput {
known_dimensions,
parent_size,
available_space,
sizing_mode,
axis: RequestedAxis::Both,
run_mode: RunMode::PerformLayout,
vertical_margins_are_collapsible,
},
)
}
}
impl<T: LayoutPartialTree> LayoutPartialTreeExt for T {}

143
vendor/taffy/src/util/debug.rs vendored Normal file
View File

@@ -0,0 +1,143 @@
#![allow(dead_code)]
#[cfg(any(feature = "debug", feature = "profile"))]
use core::fmt::{Debug, Display, Write};
#[cfg(any(feature = "debug", feature = "profile"))]
use std::sync::Mutex;
#[doc(hidden)]
#[cfg(any(feature = "debug", feature = "profile"))]
pub struct DebugLogger {
stack: Mutex<Vec<String>>,
}
#[cfg(any(feature = "debug", feature = "profile"))]
static EMPTY_STRING: String = String::new();
#[cfg(any(feature = "debug", feature = "profile"))]
impl DebugLogger {
pub const fn new() -> Self {
Self { stack: Mutex::new(Vec::new()) }
}
pub fn push_node(&self, new_key: crate::NodeId) {
let mut stack = self.stack.lock().unwrap();
let mut key_string = String::new();
write!(&mut key_string, "{:?}", new_key).unwrap();
stack.push(key_string);
}
pub fn pop_node(&self) {
let mut stack = self.stack.lock().unwrap();
stack.pop();
}
pub fn log(&self, message: impl Display) {
let stack = self.stack.lock().unwrap();
let key = stack.last().unwrap_or(&EMPTY_STRING);
let level = stack.len() * 4;
let space = " ";
println!("{space:level$}{key}: {message}");
}
pub fn labelled_log(&self, label: &str, message: impl Display) {
let stack = self.stack.lock().unwrap();
let key = stack.last().unwrap_or(&EMPTY_STRING);
let level = stack.len() * 4;
let space = " ";
println!("{space:level$}{key}: {label} {message}");
}
pub fn debug_log(&self, message: impl Debug) {
let stack = self.stack.lock().unwrap();
let key = stack.last().unwrap_or(&EMPTY_STRING);
let level = stack.len() * 4;
let space = " ";
println!("{space:level$}{key}: {message:?}");
}
pub fn labelled_debug_log(&self, label: &str, message: impl Debug) {
let stack = self.stack.lock().unwrap();
let key = stack.last().unwrap_or(&EMPTY_STRING);
let level = stack.len() * 4;
let space = " ";
println!("{space:level$}{key}: {label} {message:?}");
}
}
#[cfg(any(feature = "debug", feature = "profile"))]
pub(crate) static NODE_LOGGER: DebugLogger = DebugLogger::new();
macro_rules! debug_log {
// String literal label with debug printing
($label:literal, dbg:$item:expr) => {{
#[cfg(feature = "debug")]
$crate::util::debug::NODE_LOGGER.labelled_debug_log($label, $item);
}};
// String literal label with display printing
($label:literal, $item:expr) => {{
#[cfg(feature = "debug")]
$crate::util::debug::NODE_LOGGER.labelled_log($label, $item);
}};
// Debug printing
(dbg:$item:expr) => {{
#[cfg(feature = "debug")]
$crate::util::debug::NODE_LOGGER.debug_log($item);
}};
// Display printing
($item:expr) => {{
#[cfg(feature = "debug")]
$crate::util::debug::NODE_LOGGER.log($item);
}};
// Blank newline
() => {{
#[cfg(feature = "debug")]
println!();
}};
}
macro_rules! debug_log_node {
($known_dimensions: expr, $parent_size: expr, $available_space: expr, $run_mode: expr, $sizing_mode: expr) => {
debug_log!(dbg:$run_mode);
debug_log!("sizing_mode", dbg:$sizing_mode);
debug_log!("known_dimensions", dbg:$known_dimensions);
debug_log!("parent_size", dbg:$parent_size);
debug_log!("available_space", dbg:$available_space);
};
}
macro_rules! debug_push_node {
($node_id:expr) => {
#[cfg(any(feature = "debug", feature = "profile"))]
$crate::util::debug::NODE_LOGGER.push_node($node_id);
debug_log!("");
};
}
macro_rules! debug_pop_node {
() => {
#[cfg(any(feature = "debug", feature = "profile"))]
$crate::util::debug::NODE_LOGGER.pop_node();
};
}
#[cfg(feature = "profile")]
#[allow(unused_macros)]
macro_rules! time {
($label:expr, $($code:tt)*) => {
let start = ::std::time::Instant::now();
$($code)*
let duration = ::std::time::Instant::now().duration_since(start);
crate::util::debug::NODE_LOGGER.log(format_args!("Performed {} in {}ms", $label, duration.as_millis()));
};
}
#[cfg(not(feature = "profile"))]
#[allow(unused_macros)]
macro_rules! time {
($label:expr, $($code:tt)*) => {
$($code)*
};
}
#[allow(unused_imports)]
pub(crate) use {debug_log, debug_log_node, debug_pop_node, debug_push_node, time};

356
vendor/taffy/src/util/math.rs vendored Normal file
View File

@@ -0,0 +1,356 @@
//! Contains numerical helper traits and functions
#![allow(clippy::manual_clamp)]
use crate::geometry::Size;
use crate::style::AvailableSpace;
/// A trait to conveniently calculate minimums and maximums when some data may not be defined
///
/// If the left-hand value is [`None`], these operations return [`None`].
/// If the right-hand value is [`None`], it is treated as zero.
pub trait MaybeMath<In, Out> {
/// Returns the minimum of `self` and `rhs`
fn maybe_min(self, rhs: In) -> Out;
/// Returns the maximum of `self` and `rhs`
fn maybe_max(self, rhs: In) -> Out;
/// Returns `self` clamped between `min` and `max`
fn maybe_clamp(self, min: In, max: In) -> Out;
/// Adds `self` and `rhs`.
fn maybe_add(self, rhs: In) -> Out;
/// Subtracts rhs from `self`, treating [`None`] values as default
fn maybe_sub(self, rhs: In) -> Out;
}
impl MaybeMath<Option<f32>, Option<f32>> for Option<f32> {
fn maybe_min(self, rhs: Option<f32>) -> Option<f32> {
match (self, rhs) {
(Some(l), Some(r)) => Some(l.min(r)),
(Some(_l), None) => self,
(None, Some(_r)) => None,
(None, None) => None,
}
}
fn maybe_max(self, rhs: Option<f32>) -> Option<f32> {
match (self, rhs) {
(Some(l), Some(r)) => Some(l.max(r)),
(Some(_l), None) => self,
(None, Some(_r)) => None,
(None, None) => None,
}
}
fn maybe_clamp(self, min: Option<f32>, max: Option<f32>) -> Option<f32> {
match (self, min, max) {
(Some(base), Some(min), Some(max)) => Some(base.min(max).max(min)),
(Some(base), None, Some(max)) => Some(base.min(max)),
(Some(base), Some(min), None) => Some(base.max(min)),
(Some(_), None, None) => self,
(None, _, _) => None,
}
}
fn maybe_add(self, rhs: Option<f32>) -> Option<f32> {
match (self, rhs) {
(Some(l), Some(r)) => Some(l + r),
(Some(_l), None) => self,
(None, Some(_r)) => None,
(None, None) => None,
}
}
fn maybe_sub(self, rhs: Option<f32>) -> Option<f32> {
match (self, rhs) {
(Some(l), Some(r)) => Some(l - r),
(Some(_l), None) => self,
(None, Some(_r)) => None,
(None, None) => None,
}
}
}
impl MaybeMath<f32, Option<f32>> for Option<f32> {
fn maybe_min(self, rhs: f32) -> Option<f32> {
self.map(|val| val.min(rhs))
}
fn maybe_max(self, rhs: f32) -> Option<f32> {
self.map(|val| val.max(rhs))
}
fn maybe_clamp(self, min: f32, max: f32) -> Option<f32> {
self.map(|val| val.min(max).max(min))
}
fn maybe_add(self, rhs: f32) -> Option<f32> {
self.map(|val| val + rhs)
}
fn maybe_sub(self, rhs: f32) -> Option<f32> {
self.map(|val| val - rhs)
}
}
impl MaybeMath<Option<f32>, f32> for f32 {
fn maybe_min(self, rhs: Option<f32>) -> f32 {
match rhs {
Some(val) => self.min(val),
None => self,
}
}
fn maybe_max(self, rhs: Option<f32>) -> f32 {
match rhs {
Some(val) => self.max(val),
None => self,
}
}
fn maybe_clamp(self, min: Option<f32>, max: Option<f32>) -> f32 {
match (min, max) {
(Some(min), Some(max)) => self.min(max).max(min),
(None, Some(max)) => self.min(max),
(Some(min), None) => self.max(min),
(None, None) => self,
}
}
fn maybe_add(self, rhs: Option<f32>) -> f32 {
match rhs {
Some(val) => self + val,
None => self,
}
}
fn maybe_sub(self, rhs: Option<f32>) -> f32 {
match rhs {
Some(val) => self - val,
None => self,
}
}
}
impl MaybeMath<f32, AvailableSpace> for AvailableSpace {
fn maybe_min(self, rhs: f32) -> AvailableSpace {
match self {
AvailableSpace::Definite(val) => AvailableSpace::Definite(val.min(rhs)),
AvailableSpace::MinContent => AvailableSpace::Definite(rhs),
AvailableSpace::MaxContent => AvailableSpace::Definite(rhs),
}
}
fn maybe_max(self, rhs: f32) -> AvailableSpace {
match self {
AvailableSpace::Definite(val) => AvailableSpace::Definite(val.max(rhs)),
AvailableSpace::MinContent => AvailableSpace::MinContent,
AvailableSpace::MaxContent => AvailableSpace::MaxContent,
}
}
fn maybe_clamp(self, min: f32, max: f32) -> AvailableSpace {
match self {
AvailableSpace::Definite(val) => AvailableSpace::Definite(val.min(max).max(min)),
AvailableSpace::MinContent => AvailableSpace::MinContent,
AvailableSpace::MaxContent => AvailableSpace::MaxContent,
}
}
fn maybe_add(self, rhs: f32) -> AvailableSpace {
match self {
AvailableSpace::Definite(val) => AvailableSpace::Definite(val + rhs),
AvailableSpace::MinContent => AvailableSpace::MinContent,
AvailableSpace::MaxContent => AvailableSpace::MaxContent,
}
}
fn maybe_sub(self, rhs: f32) -> AvailableSpace {
match self {
AvailableSpace::Definite(val) => AvailableSpace::Definite(val - rhs),
AvailableSpace::MinContent => AvailableSpace::MinContent,
AvailableSpace::MaxContent => AvailableSpace::MaxContent,
}
}
}
impl MaybeMath<Option<f32>, AvailableSpace> for AvailableSpace {
fn maybe_min(self, rhs: Option<f32>) -> AvailableSpace {
match (self, rhs) {
(AvailableSpace::Definite(val), Some(rhs)) => AvailableSpace::Definite(val.min(rhs)),
(AvailableSpace::Definite(val), None) => AvailableSpace::Definite(val),
(AvailableSpace::MinContent, Some(rhs)) => AvailableSpace::Definite(rhs),
(AvailableSpace::MinContent, None) => AvailableSpace::MinContent,
(AvailableSpace::MaxContent, Some(rhs)) => AvailableSpace::Definite(rhs),
(AvailableSpace::MaxContent, None) => AvailableSpace::MaxContent,
}
}
fn maybe_max(self, rhs: Option<f32>) -> AvailableSpace {
match (self, rhs) {
(AvailableSpace::Definite(val), Some(rhs)) => AvailableSpace::Definite(val.max(rhs)),
(AvailableSpace::Definite(val), None) => AvailableSpace::Definite(val),
(AvailableSpace::MinContent, _) => AvailableSpace::MinContent,
(AvailableSpace::MaxContent, _) => AvailableSpace::MaxContent,
}
}
fn maybe_clamp(self, min: Option<f32>, max: Option<f32>) -> AvailableSpace {
match (self, min, max) {
(AvailableSpace::Definite(val), Some(min), Some(max)) => AvailableSpace::Definite(val.min(max).max(min)),
(AvailableSpace::Definite(val), None, Some(max)) => AvailableSpace::Definite(val.min(max)),
(AvailableSpace::Definite(val), Some(min), None) => AvailableSpace::Definite(val.max(min)),
(AvailableSpace::Definite(val), None, None) => AvailableSpace::Definite(val),
(AvailableSpace::MinContent, _, _) => AvailableSpace::MinContent,
(AvailableSpace::MaxContent, _, _) => AvailableSpace::MaxContent,
}
}
fn maybe_add(self, rhs: Option<f32>) -> AvailableSpace {
match (self, rhs) {
(AvailableSpace::Definite(val), Some(rhs)) => AvailableSpace::Definite(val + rhs),
(AvailableSpace::Definite(val), None) => AvailableSpace::Definite(val),
(AvailableSpace::MinContent, _) => AvailableSpace::MinContent,
(AvailableSpace::MaxContent, _) => AvailableSpace::MaxContent,
}
}
fn maybe_sub(self, rhs: Option<f32>) -> AvailableSpace {
match (self, rhs) {
(AvailableSpace::Definite(val), Some(rhs)) => AvailableSpace::Definite(val - rhs),
(AvailableSpace::Definite(val), None) => AvailableSpace::Definite(val),
(AvailableSpace::MinContent, _) => AvailableSpace::MinContent,
(AvailableSpace::MaxContent, _) => AvailableSpace::MaxContent,
}
}
}
impl<In, Out, T: MaybeMath<In, Out>> MaybeMath<Size<In>, Size<Out>> for Size<T> {
fn maybe_min(self, rhs: Size<In>) -> Size<Out> {
Size { width: self.width.maybe_min(rhs.width), height: self.height.maybe_min(rhs.height) }
}
fn maybe_max(self, rhs: Size<In>) -> Size<Out> {
Size { width: self.width.maybe_max(rhs.width), height: self.height.maybe_max(rhs.height) }
}
fn maybe_clamp(self, min: Size<In>, max: Size<In>) -> Size<Out> {
Size {
width: self.width.maybe_clamp(min.width, max.width),
height: self.height.maybe_clamp(min.height, max.height),
}
}
fn maybe_add(self, rhs: Size<In>) -> Size<Out> {
Size { width: self.width.maybe_add(rhs.width), height: self.height.maybe_add(rhs.height) }
}
fn maybe_sub(self, rhs: Size<In>) -> Size<Out> {
Size { width: self.width.maybe_sub(rhs.width), height: self.height.maybe_sub(rhs.height) }
}
}
#[cfg(test)]
mod tests {
mod lhs_option_f32_rhs_option_f32 {
use crate::util::MaybeMath;
#[test]
fn test_maybe_min() {
assert_eq!(Some(3.0).maybe_min(Some(5.0)), Some(3.0));
assert_eq!(Some(5.0).maybe_min(Some(3.0)), Some(3.0));
assert_eq!(Some(3.0).maybe_min(None), Some(3.0));
assert_eq!(None.maybe_min(Some(3.0)), None);
assert_eq!(None.maybe_min(None), None);
}
#[test]
fn test_maybe_max() {
assert_eq!(Some(3.0).maybe_max(Some(5.0)), Some(5.0));
assert_eq!(Some(5.0).maybe_max(Some(3.0)), Some(5.0));
assert_eq!(Some(3.0).maybe_max(None), Some(3.0));
assert_eq!(None.maybe_max(Some(3.0)), None);
assert_eq!(None.maybe_max(None), None);
}
#[test]
fn test_maybe_add() {
assert_eq!(Some(3.0).maybe_add(Some(5.0)), Some(8.0));
assert_eq!(Some(5.0).maybe_add(Some(3.0)), Some(8.0));
assert_eq!(Some(3.0).maybe_add(None), Some(3.0));
assert_eq!(None.maybe_add(Some(3.0)), None);
assert_eq!(None.maybe_add(None), None);
}
#[test]
fn test_maybe_sub() {
assert_eq!(Some(3.0).maybe_sub(Some(5.0)), Some(-2.0));
assert_eq!(Some(5.0).maybe_sub(Some(3.0)), Some(2.0));
assert_eq!(Some(3.0).maybe_sub(None), Some(3.0));
assert_eq!(None.maybe_sub(Some(3.0)), None);
assert_eq!(None.maybe_sub(None), None);
}
}
mod lhs_option_f32_rhs_f32 {
use crate::util::MaybeMath;
#[test]
fn test_maybe_min() {
assert_eq!(Some(3.0).maybe_min(5.0), Some(3.0));
assert_eq!(Some(5.0).maybe_min(3.0), Some(3.0));
assert_eq!(None.maybe_min(3.0), None);
}
#[test]
fn test_maybe_max() {
assert_eq!(Some(3.0).maybe_max(5.0), Some(5.0));
assert_eq!(Some(5.0).maybe_max(3.0), Some(5.0));
assert_eq!(None.maybe_max(3.0), None);
}
#[test]
fn test_maybe_add() {
assert_eq!(Some(3.0).maybe_add(5.0), Some(8.0));
assert_eq!(Some(5.0).maybe_add(3.0), Some(8.0));
assert_eq!(None.maybe_add(3.0), None);
}
#[test]
fn test_maybe_sub() {
assert_eq!(Some(3.0).maybe_sub(5.0), Some(-2.0));
assert_eq!(Some(5.0).maybe_sub(3.0), Some(2.0));
assert_eq!(None.maybe_sub(3.0), None);
}
}
mod lhs_f32_rhs_option_f32 {
use crate::util::MaybeMath;
#[test]
fn test_maybe_min() {
assert_eq!(3.0.maybe_min(Some(5.0)), 3.0);
assert_eq!(5.0.maybe_min(Some(3.0)), 3.0);
assert_eq!(3.0.maybe_min(None), 3.0);
}
#[test]
fn test_maybe_max() {
assert_eq!(3.0.maybe_max(Some(5.0)), 5.0);
assert_eq!(5.0.maybe_max(Some(3.0)), 5.0);
assert_eq!(3.0.maybe_max(None), 3.0);
}
#[test]
fn test_maybe_add() {
assert_eq!(3.0.maybe_add(Some(5.0)), 8.0);
assert_eq!(5.0.maybe_add(Some(3.0)), 8.0);
assert_eq!(3.0.maybe_add(None), 3.0);
}
#[test]
fn test_maybe_sub() {
assert_eq!(3.0.maybe_sub(Some(5.0)), -2.0);
assert_eq!(5.0.maybe_sub(Some(3.0)), 2.0);
assert_eq!(3.0.maybe_sub(None), 3.0);
}
}
}

16
vendor/taffy/src/util/mod.rs vendored Normal file
View File

@@ -0,0 +1,16 @@
//! Helpful misc. utilities such as a function to debug print a tree
mod math;
mod resolve;
pub(crate) mod sys;
pub use math::MaybeMath;
pub use resolve::{MaybeResolve, ResolveOrZero};
#[doc(hidden)]
#[macro_use]
pub(crate) mod debug;
#[cfg(feature = "std")]
mod print;
#[cfg(feature = "std")]
pub use print::print_tree;

59
vendor/taffy/src/util/print.rs vendored Normal file
View File

@@ -0,0 +1,59 @@
//! Contains the print_tree function for printing a debug representation of the tree
use crate::tree::{NodeId, PrintTree};
/// Prints a debug representation of the computed layout for a tree of nodes, starting with the passed root node.
pub fn print_tree(tree: &impl PrintTree, root: NodeId) {
println!("TREE");
print_node(tree, root, false, String::new());
/// Recursive function that prints each node in the tree
fn print_node(tree: &impl PrintTree, node_id: NodeId, has_sibling: bool, lines_string: String) {
let layout = &tree.get_final_layout(node_id);
let display = tree.get_debug_label(node_id);
let num_children = tree.child_count(node_id);
let fork_string = if has_sibling { "├── " } else { "└── " };
#[cfg(feature = "content_size")]
println!(
"{lines}{fork} {display} [x: {x:<4} y: {y:<4} w: {width:<4} h: {height:<4} content_w: {content_width:<4} content_h: {content_height:<4} border: l:{bl} r:{br} t:{bt} b:{bb}, padding: l:{pl} r:{pr} t:{pt} b:{pb}] ({key:?})",
lines = lines_string,
fork = fork_string,
display = display,
x = layout.location.x,
y = layout.location.y,
width = layout.size.width,
height = layout.size.height,
content_width = layout.content_size.width,
content_height = layout.content_size.height,
bl = layout.border.left,
br = layout.border.right,
bt = layout.border.top,
bb = layout.border.bottom,
pl = layout.padding.left,
pr = layout.padding.right,
pt = layout.padding.top,
pb = layout.padding.bottom,
key = node_id,
);
#[cfg(not(feature = "content_size"))]
println!(
"{lines}{fork} {display} [x: {x:<4} y: {y:<4} width: {width:<4} height: {height:<4}] ({key:?})",
lines = lines_string,
fork = fork_string,
display = display,
x = layout.location.x,
y = layout.location.y,
width = layout.size.width,
height = layout.size.height,
key = node_id,
);
let bar = if has_sibling { "" } else { " " };
let new_string = lines_string + bar;
// Recurse into children
for (index, child) in tree.child_ids(node_id).enumerate() {
let has_sibling = index < num_children - 1;
print_node(tree, child, has_sibling, new_string.clone());
}
}
}

333
vendor/taffy/src/util/resolve.rs vendored Normal file
View File

@@ -0,0 +1,333 @@
//! Helper trait to calculate dimensions during layout resolution
use crate::geometry::{Rect, Size};
use crate::style::{Dimension, LengthPercentage, LengthPercentageAuto};
use crate::style_helpers::TaffyZero;
/// Trait to encapsulate behaviour where we need to resolve from a
/// potentially context-dependent size or dimension into
/// a context-independent size or dimension.
///
/// Will return a `None` if it unable to resolve.
pub trait MaybeResolve<In, Out> {
/// Resolve a dimension that might be dependent on a context, with `None` as fallback value
fn maybe_resolve(self, context: In) -> Out;
}
/// Trait to encapsulate behaviour where we need to resolve from a
/// potentially context-dependent size or dimension into
/// a context-independent size or dimension.
///
/// Will return a default value if it unable to resolve.
pub trait ResolveOrZero<TContext, TOutput: TaffyZero> {
/// Resolve a dimension that might be dependent on a context, with a default fallback value
fn resolve_or_zero(self, context: TContext) -> TOutput;
}
impl MaybeResolve<Option<f32>, Option<f32>> for LengthPercentage {
/// Converts the given [`LengthPercentage`] into an absolute length
/// Can return `None`
fn maybe_resolve(self, context: Option<f32>) -> Option<f32> {
match self {
LengthPercentage::Length(length) => Some(length),
LengthPercentage::Percent(percent) => context.map(|dim| dim * percent),
}
}
}
impl MaybeResolve<Option<f32>, Option<f32>> for LengthPercentageAuto {
/// Converts the given [`LengthPercentageAuto`] into an absolute length
/// Can return `None`
fn maybe_resolve(self, context: Option<f32>) -> Option<f32> {
match self {
LengthPercentageAuto::Length(length) => Some(length),
LengthPercentageAuto::Percent(percent) => context.map(|dim| dim * percent),
LengthPercentageAuto::Auto => None,
}
}
}
impl MaybeResolve<Option<f32>, Option<f32>> for Dimension {
/// Converts the given [`Dimension`] into an absolute length
///
/// Can return `None`
fn maybe_resolve(self, context: Option<f32>) -> Option<f32> {
match self {
Dimension::Length(length) => Some(length),
Dimension::Percent(percent) => context.map(|dim| dim * percent),
Dimension::Auto => None,
}
}
}
// Generic implementation of MaybeResolve for f32 context where MaybeResolve is implemented
// for Option<f32> context
impl<T: MaybeResolve<Option<f32>, Option<f32>>> MaybeResolve<f32, Option<f32>> for T {
/// Converts the given MaybeResolve value into an absolute length
/// Can return `None`
fn maybe_resolve(self, context: f32) -> Option<f32> {
self.maybe_resolve(Some(context))
}
}
// Generic MaybeResolve for Size
impl<In, Out, T: MaybeResolve<In, Out>> MaybeResolve<Size<In>, Size<Out>> for Size<T> {
/// Converts any `parent`-relative values for size into an absolute size
fn maybe_resolve(self, context: Size<In>) -> Size<Out> {
Size { width: self.width.maybe_resolve(context.width), height: self.height.maybe_resolve(context.height) }
}
}
impl ResolveOrZero<Option<f32>, f32> for LengthPercentage {
/// Will return a default value of result is evaluated to `None`
fn resolve_or_zero(self, context: Option<f32>) -> f32 {
self.maybe_resolve(context).unwrap_or(0.0)
}
}
impl ResolveOrZero<Option<f32>, f32> for LengthPercentageAuto {
/// Will return a default value of result is evaluated to `None`
fn resolve_or_zero(self, context: Option<f32>) -> f32 {
self.maybe_resolve(context).unwrap_or(0.0)
}
}
impl ResolveOrZero<Option<f32>, f32> for Dimension {
/// Will return a default value of result is evaluated to `None`
fn resolve_or_zero(self, context: Option<f32>) -> f32 {
self.maybe_resolve(context).unwrap_or(0.0)
}
}
// Generic ResolveOrZero for Size
impl<In, Out: TaffyZero, T: ResolveOrZero<In, Out>> ResolveOrZero<Size<In>, Size<Out>> for Size<T> {
/// Converts any `parent`-relative values for size into an absolute size
fn resolve_or_zero(self, context: Size<In>) -> Size<Out> {
Size { width: self.width.resolve_or_zero(context.width), height: self.height.resolve_or_zero(context.height) }
}
}
// Generic ResolveOrZero for resolving Rect against Size
impl<In: Copy, Out: TaffyZero, T: ResolveOrZero<In, Out>> ResolveOrZero<Size<In>, Rect<Out>> for Rect<T> {
/// Converts any `parent`-relative values for Rect into an absolute Rect
fn resolve_or_zero(self, context: Size<In>) -> Rect<Out> {
Rect {
left: self.left.resolve_or_zero(context.width),
right: self.right.resolve_or_zero(context.width),
top: self.top.resolve_or_zero(context.height),
bottom: self.bottom.resolve_or_zero(context.height),
}
}
}
// Generic ResolveOrZero for resolving Rect against Option
impl<Out: TaffyZero, T: ResolveOrZero<Option<f32>, Out>> ResolveOrZero<Option<f32>, Rect<Out>> for Rect<T> {
/// Converts any `parent`-relative values for Rect into an absolute Rect
fn resolve_or_zero(self, context: Option<f32>) -> Rect<Out> {
Rect {
left: self.left.resolve_or_zero(context),
right: self.right.resolve_or_zero(context),
top: self.top.resolve_or_zero(context),
bottom: self.bottom.resolve_or_zero(context),
}
}
}
#[cfg(test)]
mod tests {
use super::{MaybeResolve, ResolveOrZero};
use crate::style_helpers::TaffyZero;
use core::fmt::Debug;
// MaybeResolve test runner
fn mr_case<Lhs, Rhs, Out>(input: Lhs, context: Rhs, expected: Out)
where
Lhs: MaybeResolve<Rhs, Out>,
Out: PartialEq + Debug,
{
assert_eq!(input.maybe_resolve(context), expected);
}
// ResolveOrZero test runner
fn roz_case<Lhs, Rhs, Out>(input: Lhs, context: Rhs, expected: Out)
where
Lhs: ResolveOrZero<Rhs, Out>,
Out: PartialEq + Debug + TaffyZero,
{
assert_eq!(input.resolve_or_zero(context), expected);
}
mod maybe_resolve_dimension {
use super::mr_case;
use crate::style::Dimension;
/// `Dimension::Auto` should always return `None`
///
/// The parent / context should not affect the outcome.
#[test]
fn resolve_auto() {
mr_case(Dimension::Auto, None, None);
mr_case(Dimension::Auto, Some(5.0), None);
mr_case(Dimension::Auto, Some(-5.0), None);
mr_case(Dimension::Auto, Some(0.), None);
}
/// `Dimension::Length` should always return `Some(f32)`
/// where the f32 value is the inner absolute length.
///
/// The parent / context should not affect the outcome.
#[test]
fn resolve_length() {
mr_case(Dimension::Length(1.0), None, Some(1.0));
mr_case(Dimension::Length(1.0), Some(5.0), Some(1.0));
mr_case(Dimension::Length(1.0), Some(-5.0), Some(1.0));
mr_case(Dimension::Length(1.0), Some(0.), Some(1.0));
}
/// `Dimension::Percent` should return `None` if context is `None`.
/// Otherwise it should return `Some(f32)`
/// where the f32 value is the inner value of the percent * context value.
///
/// The parent / context __should__ affect the outcome.
#[test]
fn resolve_percent() {
mr_case(Dimension::Percent(1.0), None, None);
mr_case(Dimension::Percent(1.0), Some(5.0), Some(5.0));
mr_case(Dimension::Percent(1.0), Some(-5.0), Some(-5.0));
mr_case(Dimension::Percent(1.0), Some(50.0), Some(50.0));
}
}
mod maybe_resolve_size_dimension {
use super::mr_case;
use crate::geometry::Size;
use crate::style::Dimension;
/// Size<Dimension::Auto> should always return Size<None>
///
/// The parent / context should not affect the outcome.
#[test]
fn maybe_resolve_auto() {
mr_case(Size::<Dimension>::auto(), Size::NONE, Size::NONE);
mr_case(Size::<Dimension>::auto(), Size::new(5.0, 5.0), Size::NONE);
mr_case(Size::<Dimension>::auto(), Size::new(-5.0, -5.0), Size::NONE);
mr_case(Size::<Dimension>::auto(), Size::new(0.0, 0.0), Size::NONE);
}
/// Size<Dimension::Length> should always return a Size<Some(f32)>
/// where the f32 values are the absolute length.
///
/// The parent / context should not affect the outcome.
#[test]
fn maybe_resolve_length() {
mr_case(Size::from_lengths(5.0, 5.0), Size::NONE, Size::new(5.0, 5.0));
mr_case(Size::from_lengths(5.0, 5.0), Size::new(5.0, 5.0), Size::new(5.0, 5.0));
mr_case(Size::from_lengths(5.0, 5.0), Size::new(-5.0, -5.0), Size::new(5.0, 5.0));
mr_case(Size::from_lengths(5.0, 5.0), Size::new(0.0, 0.0), Size::new(5.0, 5.0));
}
/// `Size<Dimension::Percent>` should return `Size<None>` if context is `Size<None>`.
/// Otherwise it should return `Size<Some(f32)>`
/// where the f32 value is the inner value of the percent * context value.
///
/// The context __should__ affect the outcome.
#[test]
fn maybe_resolve_percent() {
mr_case(Size::from_percent(5.0, 5.0), Size::NONE, Size::NONE);
mr_case(Size::from_percent(5.0, 5.0), Size::new(5.0, 5.0), Size::new(25.0, 25.0));
mr_case(Size::from_percent(5.0, 5.0), Size::new(-5.0, -5.0), Size::new(-25.0, -25.0));
mr_case(Size::from_percent(5.0, 5.0), Size::new(0.0, 0.0), Size::new(0.0, 0.0));
}
}
mod resolve_or_zero_dimension_to_option_f32 {
use super::roz_case;
use crate::style::Dimension;
#[test]
fn resolve_or_zero_auto() {
roz_case(Dimension::Auto, None, 0.0);
roz_case(Dimension::Auto, Some(5.0), 0.0);
roz_case(Dimension::Auto, Some(-5.0), 0.0);
roz_case(Dimension::Auto, Some(0.0), 0.0);
}
#[test]
fn resolve_or_zero_length() {
roz_case(Dimension::Length(5.0), None, 5.0);
roz_case(Dimension::Length(5.0), Some(5.0), 5.0);
roz_case(Dimension::Length(5.0), Some(-5.0), 5.0);
roz_case(Dimension::Length(5.0), Some(0.0), 5.0);
}
#[test]
fn resolve_or_zero_percent() {
roz_case(Dimension::Percent(5.0), None, 0.0);
roz_case(Dimension::Percent(5.0), Some(5.0), 25.0);
roz_case(Dimension::Percent(5.0), Some(-5.0), -25.0);
roz_case(Dimension::Percent(5.0), Some(0.0), 0.0);
}
}
mod resolve_or_zero_rect_dimension_to_rect {
use super::roz_case;
use crate::geometry::{Rect, Size};
use crate::style::Dimension;
#[test]
fn resolve_or_zero_auto() {
roz_case(Rect::<Dimension>::auto(), Size::NONE, Rect::zero());
roz_case(Rect::<Dimension>::auto(), Size::new(5.0, 5.0), Rect::zero());
roz_case(Rect::<Dimension>::auto(), Size::new(-5.0, -5.0), Rect::zero());
roz_case(Rect::<Dimension>::auto(), Size::new(0.0, 0.0), Rect::zero());
}
#[test]
fn resolve_or_zero_length() {
roz_case(Rect::from_length(5.0, 5.0, 5.0, 5.0), Size::NONE, Rect::new(5.0, 5.0, 5.0, 5.0));
roz_case(Rect::from_length(5.0, 5.0, 5.0, 5.0), Size::new(5.0, 5.0), Rect::new(5.0, 5.0, 5.0, 5.0));
roz_case(Rect::from_length(5.0, 5.0, 5.0, 5.0), Size::new(-5.0, -5.0), Rect::new(5.0, 5.0, 5.0, 5.0));
roz_case(Rect::from_length(5.0, 5.0, 5.0, 5.0), Size::new(0.0, 0.0), Rect::new(5.0, 5.0, 5.0, 5.0));
}
#[test]
fn resolve_or_zero_percent() {
roz_case(Rect::from_percent(5.0, 5.0, 5.0, 5.0), Size::NONE, Rect::zero());
roz_case(Rect::from_percent(5.0, 5.0, 5.0, 5.0), Size::new(5.0, 5.0), Rect::new(25.0, 25.0, 25.0, 25.0));
roz_case(
Rect::from_percent(5.0, 5.0, 5.0, 5.0),
Size::new(-5.0, -5.0),
Rect::new(-25.0, -25.0, -25.0, -25.0),
);
roz_case(Rect::from_percent(5.0, 5.0, 5.0, 5.0), Size::new(0.0, 0.0), Rect::zero());
}
}
mod resolve_or_zero_rect_dimension_to_rect_f32_via_option {
use super::roz_case;
use crate::geometry::Rect;
use crate::style::Dimension;
#[test]
fn resolve_or_zero_auto() {
roz_case(Rect::<Dimension>::auto(), None, Rect::zero());
roz_case(Rect::<Dimension>::auto(), Some(5.0), Rect::zero());
roz_case(Rect::<Dimension>::auto(), Some(-5.0), Rect::zero());
roz_case(Rect::<Dimension>::auto(), Some(0.0), Rect::zero());
}
#[test]
fn resolve_or_zero_length() {
roz_case(Rect::from_length(5.0, 5.0, 5.0, 5.0), None, Rect::new(5.0, 5.0, 5.0, 5.0));
roz_case(Rect::from_length(5.0, 5.0, 5.0, 5.0), Some(5.0), Rect::new(5.0, 5.0, 5.0, 5.0));
roz_case(Rect::from_length(5.0, 5.0, 5.0, 5.0), Some(-5.0), Rect::new(5.0, 5.0, 5.0, 5.0));
roz_case(Rect::from_length(5.0, 5.0, 5.0, 5.0), Some(0.0), Rect::new(5.0, 5.0, 5.0, 5.0));
}
#[test]
fn resolve_or_zero_percent() {
roz_case(Rect::from_percent(5.0, 5.0, 5.0, 5.0), None, Rect::zero());
roz_case(Rect::from_percent(5.0, 5.0, 5.0, 5.0), Some(5.0), Rect::new(25.0, 25.0, 25.0, 25.0));
roz_case(Rect::from_percent(5.0, 5.0, 5.0, 5.0), Some(-5.0), Rect::new(-25.0, -25.0, -25.0, -25.0));
roz_case(Rect::from_percent(5.0, 5.0, 5.0, 5.0), Some(0.0), Rect::zero());
}
}
}

237
vendor/taffy/src/util/sys.rs vendored Normal file
View File

@@ -0,0 +1,237 @@
//! Allocator-flexible data types
// When std is enabled, prefer those types
#[cfg(feature = "std")]
pub(crate) use self::std::*;
// When alloc but not std is enabled, use those types
#[cfg(all(feature = "alloc", not(feature = "std")))]
pub(crate) use self::alloc::*;
// When neither alloc or std is enabled, use a heapless fallback
#[cfg(all(not(feature = "alloc"), not(feature = "std")))]
pub(crate) use self::core::*;
/// For when `std` is enabled
#[cfg(feature = "std")]
mod std {
/// An allocation-backend agnostic vector type
pub(crate) type Vec<A> = std::vec::Vec<A>;
/// A vector of child nodes
pub(crate) type ChildrenVec<A> = std::vec::Vec<A>;
#[cfg(feature = "grid")]
/// A vector of grid tracks
pub(crate) type GridTrackVec<A> = std::vec::Vec<A>;
/// Creates a new vector with the capacity for the specified number of items before it must be resized
#[must_use]
pub(crate) fn new_vec_with_capacity<A>(capacity: usize) -> Vec<A> {
Vec::with_capacity(capacity)
}
/// Rounds to the nearest whole number
#[must_use]
#[inline(always)]
pub(crate) fn round(value: f32) -> f32 {
value.round()
}
/// Rounds up to the nearest whole number
#[must_use]
#[inline(always)]
pub(crate) fn ceil(value: f32) -> f32 {
value.ceil()
}
/// Rounds down to the nearest whole number
#[must_use]
#[inline(always)]
pub(crate) fn floor(value: f32) -> f32 {
value.floor()
}
/// Computes the absolute value
#[must_use]
#[inline(always)]
pub(crate) fn abs(value: f32) -> f32 {
value.abs()
}
/// Returns the largest of two f32 values
#[inline(always)]
pub(crate) fn f32_max(a: f32, b: f32) -> f32 {
a.max(b)
}
/// Returns the smallest of two f32 values
#[inline(always)]
pub(crate) fn f32_min(a: f32, b: f32) -> f32 {
a.min(b)
}
}
/// For when `alloc` but not `std` is enabled
#[cfg(all(feature = "alloc", not(feature = "std")))]
mod alloc {
extern crate alloc;
use core::cmp::Ordering;
/// An allocation-backend agnostic vector type
pub(crate) type Vec<A> = alloc::vec::Vec<A>;
/// A vector of child nodes
pub(crate) type ChildrenVec<A> = alloc::vec::Vec<A>;
#[cfg(feature = "grid")]
/// A vector of grid tracks
pub(crate) type GridTrackVec<A> = alloc::vec::Vec<A>;
/// Creates a new vector with the capacity for the specified number of items before it must be resized
#[must_use]
pub(crate) fn new_vec_with_capacity<A>(capacity: usize) -> Vec<A> {
Vec::with_capacity(capacity)
}
/// Rounds to the nearest whole number
pub(crate) use super::polyfill::round;
/// Rounds up to the nearest whole number
pub(crate) use super::polyfill::ceil;
/// Rounds down to the nearest whole number
pub(crate) use super::polyfill::floor;
/// Computes the absolute value
pub(crate) use super::polyfill::abs;
/// Returns the largest of two f32 values
#[inline(always)]
pub(crate) fn f32_max(a: f32, b: f32) -> f32 {
a.max(b)
}
/// Returns the smallest of two f32 values
#[inline(always)]
pub(crate) fn f32_min(a: f32, b: f32) -> f32 {
a.min(b)
}
}
/// For when neither `alloc` nor `std` is enabled
#[cfg(all(not(feature = "alloc"), not(feature = "std")))]
mod core {
use core::cmp::Ordering;
/// The maximum number of nodes in the tree
pub const MAX_NODE_COUNT: usize = 256;
/// The maximum number of children of any given node
pub const MAX_CHILD_COUNT: usize = 16;
#[cfg(feature = "grid")]
/// The maximum number of children of any given node
pub const MAX_GRID_TRACKS: usize = 16;
/// An allocation-backend agnostic vector type
pub(crate) type Vec<A> = arrayvec::ArrayVec<A, MAX_NODE_COUNT>;
/// A vector of child nodes, whose length cannot exceed [`MAX_CHILD_COUNT`]
pub(crate) type ChildrenVec<A> = arrayvec::ArrayVec<A, MAX_CHILD_COUNT>;
#[cfg(feature = "grid")]
/// A vector of grid tracks
pub(crate) type GridTrackVec<A> = arrayvec::ArrayVec<A, MAX_GRID_TRACKS>;
/// Creates a new map with the capacity for the specified number of items before it must be resized
///
/// This vector cannot be resized.
#[must_use]
pub(crate) fn new_vec_with_capacity<A, const CAP: usize>(_capacity: usize) -> arrayvec::ArrayVec<A, CAP> {
arrayvec::ArrayVec::new()
}
/// Rounds to the nearest whole number
pub(crate) use super::polyfill::round;
/// Computes the absolute value
pub(crate) use super::polyfill::abs;
/// Returns the largest of two f32 values
#[inline(always)]
pub(crate) fn f32_max(a: f32, b: f32) -> f32 {
a.max(b)
}
/// Returns the smallest of two f32 values
#[inline(always)]
pub(crate) fn f32_min(a: f32, b: f32) -> f32 {
a.min(b)
}
}
/// Implementations of float functions for no_std and alloc builds
/// Copied from `num-traits` crate
#[cfg(not(feature = "std"))]
mod polyfill {
#[must_use]
#[inline(always)]
fn fract(value: f32) -> f32 {
if value == 0.0 {
0.0
} else {
value % 1.0
}
}
#[must_use]
#[inline(always)]
pub(crate) fn round(value: f32) -> f32 {
let f = fract(value);
if f.is_nan() || f == 0.0 {
value
} else if value > 0.0 {
if f < 0.5 {
value - f
} else {
value - f + 1.0
}
} else if -f < 0.5 {
value - f
} else {
value - f - 1.0
}
}
#[must_use]
#[inline(always)]
pub(crate) fn floor(value: f32) -> f32 {
let f = fract(value);
if f.is_nan() || f == 0.0 {
value
} else if value < 0.0 {
value - f - 1.0
} else {
value - f
}
}
#[must_use]
#[inline(always)]
pub(crate) fn ceil(value: f32) -> f32 {
let f = fract(value);
if f.is_nan() || f == 0.0 {
value
} else if value > 0.0 {
value - f + 1.0
} else {
value - f
}
}
/// Computes the absolute value
#[must_use]
#[inline(always)]
pub(crate) fn abs(value: f32) -> f32 {
if value.is_sign_positive() {
return value;
} else if value.is_sign_negative() {
return -value;
} else {
f32::NAN
}
}
}