Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Replace per-node measure functions with a single measure function + per-node context #490

Merged
merged 27 commits into from
Oct 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
1bd6839
Add Context to Measurable trait
nicoburns May 21, 2023
09d8f2e
Add context type parameter to Taffy struct
nicoburns May 21, 2023
169f31f
Remove Send+Sync bound from Measurable and create separate SyncMeasur…
nicoburns Jun 1, 2023
8d5b177
Fix clippy lints
nicoburns Jun 1, 2023
ab7c459
Make Taffy constructors generic again
nicoburns Jun 1, 2023
8ddd3b9
Merge RawWithContext MeasureFunc variant into Raw variant
nicoburns Jun 1, 2023
0305f07
Fix typo
alice-i-cecile Jun 2, 2023
e078c4c
Fix typo
alice-i-cecile Jun 2, 2023
ef0b196
Make Measurable::measure take &mut self
nicoburns Jun 2, 2023
b1c320e
Convert compute code to work with mutable measurables
nicoburns Jun 3, 2023
9799fd4
Update docs + add constructor to MeasureFunc
nicoburns Jun 3, 2023
8883810
Convert gentests to use custom Measurable
nicoburns Jun 3, 2023
532d09a
Implement TaffyView
nicoburns Aug 24, 2023
1109829
Standardise on &mut context
nicoburns Aug 24, 2023
6f27f2f
Fix clippy lints
nicoburns Aug 24, 2023
9fdff66
Fixup gap_column_gap_start_index test
nicoburns Oct 1, 2023
ccd0167
Replace per-node measure functions with a single measure function and…
nicoburns Oct 1, 2023
bd5c4fa
Fix print_tree function when taffy_tree feature is not enabled
nicoburns Oct 1, 2023
292f757
Fix print_tree function
nicoburns Oct 1, 2023
7e115ca
Fix formatting and clippy lints
nicoburns Oct 1, 2023
58aaec8
Feature gate print_tree method on std feature
nicoburns Oct 1, 2023
fd78533
Fix doc references
nicoburns Oct 1, 2023
79fd2de
Remove measure_func module
nicoburns Oct 1, 2023
640b1e4
Add example of using measure to integrate leaf layout
nicoburns Oct 1, 2023
6330be5
Re-forbid unsafe code
nicoburns Oct 3, 2023
dde4431
Regenerate tests after rebase
nicoburns Oct 5, 2023
41c04c7
Update release notes for measure function changes
nicoburns Oct 9, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Right now, it powers:
use taffy::prelude::*;

// First create an instance of Taffy
let mut taffy = Taffy::new();
let mut taffy : Taffy<()> = Taffy::new();

// Create a tree of nodes using `taffy.new_leaf` and `taffy.new_with_children`.
// These functions both return a node id which can be used to refer to that node
Expand Down
58 changes: 57 additions & 1 deletion RELEASES.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,63 @@

### Breaking

Many APIs have been renamed to replace `points` or `Points` with `length` or `Length`.
#### Measure function changes

The "measure function" API for integrating Taffy with other measurement systems (such as text layout) has been changed to be more flexible,
and to interact better with borrow checking (you can now borrow external data in your measure function!).

- There are no longer per-node measure functions.
- There is now a single "global" measure function, and a per-node "context" of a user-defined type
- The `Taffy` tree is now a generic `Taffy<T>` where `T` is the "context" type.

If you are not using measure functions, then the only change you will need to make is from:

```rust
let mut tree = Taffy::new();
```

to

```rust
let mut tree : Taffy<()> = Taffy::new();
```

And generally update any uses of `Taffy` in your codebase to `Taffy<()>`.

If you are using measure functions then you will need to make some bigger (but straightforward) changes. The following Taffy 0.3 code:

```rust
let mut tree = Taffy::new();
let leaf = tree.new_leaf_with_measure(
Style::DEFAULT,
|known_dimensions: Size<Option<f32>>, available_space: Size<AvailableSpace>| Size { width: 100.0, height: 200.0 }
);
tree.compute_layout(leaf, Size::MAX_CONTENT);
```

Should become something like the following with Taffy 0.4:

```rust
let mut tree : Taffy<Size> = Taffy::new();
let leaf = tree.new_leaf_with_context(Style::DEFAULT, Size { width: 100.0, height: 200.0 });
tree.compute_layout_with_measure(
leaf,
Size::MAX_CONTENT,
|known_dimensions: Size<Option<f32>>, available_space: Size<AvailableSpace>, node_id: NodeId, node_context: Option<Size>| {
node_context.unwrap_or(Size::ZERO)
}
);
```

Note that:

- You can choose any type instead of `Size` in the above example. This includes your own custom type (which can be an enum or a trait object).
- If you don't need a context then you can use `()` for the context type
- As the single "global" measure function passed to `compute_layout_with_measure` only needs to exist for the duration of a single layout run,
it can (mutably) borrow data from it's environment

#### Many APIs have been renamed to replace `points` or `Points` with `length` or `Length`

This new name better describes one-dimentional measure of space in some unspecified unit
which is often unrelated to the PostScript point or the CSS `pt` unit.

Expand Down
2 changes: 1 addition & 1 deletion examples/basic.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use taffy::prelude::*;

fn main() -> Result<(), taffy::TaffyError> {
let mut taffy = Taffy::new();
let mut taffy: Taffy<()> = Taffy::new();

let child = taffy.new_leaf(Style {
size: Size { width: Dimension::Percent(0.5), height: Dimension::Auto },
Expand Down
4 changes: 2 additions & 2 deletions examples/flexbox_gap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use taffy::prelude::*;
// Thus the container is 80px x 20px.

fn main() -> Result<(), taffy::TaffyError> {
let mut taffy = Taffy::new();
let mut taffy: Taffy<()> = Taffy::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())?;
Expand All @@ -18,7 +18,7 @@ fn main() -> Result<(), taffy::TaffyError> {

// Compute layout and print result
taffy.compute_layout(root, Size::MAX_CONTENT)?;
taffy::util::print_tree(&taffy, root);
taffy.print_tree(root);

Ok(())
}
4 changes: 2 additions & 2 deletions examples/grid_holy_grail.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ fn default<T: Default>() -> T {
fn main() -> Result<(), taffy::TaffyError> {
use taffy::prelude::*;

let mut taffy = Taffy::new();
let mut taffy: Taffy<()> = Taffy::new();

// Setup the grid
let root_style = Style {
Expand All @@ -42,7 +42,7 @@ fn main() -> Result<(), taffy::TaffyError> {

// Compute layout and print result
taffy.compute_layout(root, Size { width: length(800.0), height: length(600.0) })?;
taffy::util::print_tree(&taffy, root);
taffy.print_tree(root);

Ok(())
}
146 changes: 146 additions & 0 deletions examples/measure.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
use taffy::prelude::*;

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.";

struct FontMetrics {
char_width: f32,
char_height: f32,
}
enum WritingMode {
Horizontal,
Vertical,
}
struct TextContext {
text_content: String,
writing_mode: WritingMode,
}
struct ImageContext {
width: f32,
height: f32,
}
enum NodeContext {
Text(TextContext),
Image(ImageContext),
}

fn main() -> Result<(), taffy::TaffyError> {
let mut taffy: Taffy<NodeContext> = Taffy::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| {
measure_function(known_dimensions, available_space, node_context, &font_metrics)
},
)?;
taffy.print_tree(root);

Ok(())
}

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 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 },
}
}

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 + word.len() > inline_line_length {
if current_line_length > 0 {
line_count += 1
};
current_line_length = word.len();
} else {
current_line_length += word.len();
};
}
(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 },
}
}
2 changes: 1 addition & 1 deletion examples/nested.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use taffy::prelude::*;

fn main() -> Result<(), taffy::TaffyError> {
let mut taffy = Taffy::new();
let mut taffy: Taffy<()> = Taffy::new();

// left
let child_t1 = taffy.new_leaf(Style {
Expand Down
26 changes: 13 additions & 13 deletions scripts/gentest/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,14 +215,14 @@ fn generate_test(name: impl AsRef<str>, description: &Value) -> TokenStream {
#[test]
fn #name() {
#[allow(unused_imports)]
use taffy::{tree::Layout, prelude::*};
let mut taffy = taffy::Taffy::new();
use taffy::{tree::Layout, prelude::*, Taffy};
let mut taffy : Taffy<crate::TextMeasure> = Taffy::new();
#set_rounding_mode
#node_description
taffy.compute_layout(node, #available_space).unwrap();
taffy.compute_layout_with_measure(node, #available_space, crate::test_measure_function).unwrap();

println!("\nComputed tree:");
taffy::util::print_tree(&taffy, node);
taffy.print_tree(node);
println!();

#assertions
Expand Down Expand Up @@ -556,8 +556,7 @@ fn generate_node(ident: &str, node: &Value) -> TokenStream {
let text_content = get_string_value("text_content", node);
let writing_mode = get_string_value("writingMode", style);
let raw_aspect_ratio = get_number_value("aspect_ratio", style);
let measure_func: Option<_> =
text_content.map(|text| generate_measure_function(text, writing_mode, raw_aspect_ratio));
let node_context: Option<_> = text_content.map(|text| generate_node_context(text, writing_mode, raw_aspect_ratio));

edges_quoted!(style, margin, generate_length_percentage_auto, quote!(zero()));
edges_quoted!(style, padding, generate_length_percentage, quote!(zero()));
Expand Down Expand Up @@ -629,8 +628,8 @@ fn generate_node(ident: &str, node: &Value) -> TokenStream {
#children_body
let #ident = taffy.new_with_children(#style,#children).unwrap();
)
} else if measure_func.is_some() {
quote!(let #ident = taffy.new_leaf_with_measure(#style,#measure_func,).unwrap();)
} else if node_context.is_some() {
quote!(let #ident = taffy.new_leaf_with_context(#style,#node_context,).unwrap();)
} else {
quote!(let #ident = taffy.new_leaf(#style).unwrap();)
}
Expand Down Expand Up @@ -867,7 +866,7 @@ fn generate_scalar_definition(track_definition: &serde_json::Map<String, Value>)
}
}

fn generate_measure_function(text_content: &str, writing_mode: Option<&str>, aspect_ratio: Option<f32>) -> TokenStream {
fn generate_node_context(text_content: &str, writing_mode: Option<&str>, aspect_ratio: Option<f32>) -> TokenStream {
let writing_mode_token = match writing_mode {
Some("vertical-rl" | "vertical-lr") => quote!(crate::WritingMode::Vertical),
_ => quote!(crate::WritingMode::Horizontal),
Expand All @@ -879,9 +878,10 @@ fn generate_measure_function(text_content: &str, writing_mode: Option<&str>, asp
};

quote!(
taffy::tree::MeasureFunc::Raw(|known_dimensions, available_space| {
const TEXT : &str = #text_content;
crate::measure_standard_text(known_dimensions, available_space, TEXT, #writing_mode_token, #aspect_ratio_token)
})
crate::TextMeasure {
text_content: #text_content,
writing_mode: #writing_mode_token,
_aspect_ratio: #aspect_ratio_token,
}
)
}
2 changes: 1 addition & 1 deletion src/compute/flexbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2132,7 +2132,7 @@ mod tests {
// Make sure we get correct constants
#[test]
fn correct_constants() {
let mut tree = Taffy::with_capacity(16);
let mut tree: Taffy<()> = Taffy::with_capacity(16);

let style = Style::default();
let node_id = tree.new_leaf(style.clone()).unwrap();
Expand Down
Loading