Skip to content

Commit

Permalink
Replace per-node measure functions with a single measure function + p…
Browse files Browse the repository at this point in the history
…er-node context (#490)

* Add Context to Measurable trait

* Add context type parameter to Taffy struct

* Remove Send+Sync bound from Measurable and create separate SyncMeasureFunc type

* Fix clippy lints

* Make Taffy constructors generic again

* Merge RawWithContext MeasureFunc variant into Raw variant

* Fix typo

Co-authored-by: TimJentzsch <TimJentzsch.github.com@timjen.net>

* Fix typo

Co-authored-by: TimJentzsch <TimJentzsch.github.com@timjen.net>

* Make Measurable::measure take &mut self

* Convert compute code to work with mutable measurables

* Update docs + add  constructor to MeasureFunc

* Convert gentests to use custom Measurable

* Implement TaffyView

* Standardise on &mut context

* Fix clippy lints

* Fixup gap_column_gap_start_index test

* Replace per-node measure functions with a single measure function and per-node context

* Fix print_tree function when taffy_tree feature is not enabled

* Fix print_tree function

* Fix formatting and clippy lints

* Feature gate print_tree method on std feature

* Fix doc references

* Remove measure_func module

* Add example of using measure to integrate leaf layout

* Re-forbid unsafe code

* Regenerate tests after rebase

* Update release notes for measure function changes

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: TimJentzsch <TimJentzsch.github.com@timjen.net>
  • Loading branch information
3 people authored Oct 9, 2023
1 parent 9b26cfa commit be627e7
Show file tree
Hide file tree
Showing 1,008 changed files with 5,614 additions and 6,317 deletions.
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

0 comments on commit be627e7

Please sign in to comment.