From 92116b6bc4a343fabc18bd2dd0424d69005a20d7 Mon Sep 17 00:00:00 2001 From: Nico Burns Date: Sat, 11 Mar 2023 23:57:28 +0000 Subject: [PATCH 01/41] Add wasm_bindgen annotations to enums (behind wasm feature) --- Cargo.toml | 2 ++ bindings/wasm/.gitignore | 1 + src/style/alignment.rs | 42 ++++++++++++++++++++++++++++++++++++++++ src/style/flex.rs | 32 ++++++++++++++++++++++++++++++ src/style/grid.rs | 18 +++++++++++++++++ src/style/mod.rs | 34 ++++++++++++++++++++++++++++++-- 6 files changed, 127 insertions(+), 2 deletions(-) create mode 100644 bindings/wasm/.gitignore diff --git a/Cargo.toml b/Cargo.toml index 66823ca30..d1725ea10 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,10 +20,12 @@ rand = { version = "0.8.5", optional = true } serde = { version = "1.0", optional = true, features = ["serde_derive"] } slotmap = "1.0.6" grid = { version = "0.9.0", optional = true } +wasm-bindgen = { version = "0.2.84", optional = true } [features] default = ["std", "grid"] grid = ["alloc", "dep:grid"] +wasm = ["dep:wasm-bindgen"] alloc = [] std = ["num-traits/std"] serde = ["dep:serde"] diff --git a/bindings/wasm/.gitignore b/bindings/wasm/.gitignore new file mode 100644 index 000000000..1de565933 --- /dev/null +++ b/bindings/wasm/.gitignore @@ -0,0 +1 @@ +target \ No newline at end of file diff --git a/src/style/alignment.rs b/src/style/alignment.rs index 659ec0ccd..0c696efa6 100644 --- a/src/style/alignment.rs +++ b/src/style/alignment.rs @@ -1,12 +1,17 @@ //! Style types for controlling alignment +#[cfg(feature = "wasm")] +use wasm_bindgen::prelude::*; + /// Used to control how child [`Nodes`](crate::node::Node) 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) +#[repr(u8)] #[derive(Copy, Clone, PartialEq, Eq, Debug)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "wasm", wasm_bindgen)] pub enum AlignItems { /// Items are packed toward the start of the axis Start, @@ -29,6 +34,23 @@ pub enum AlignItems { /// Stretch to fill the container Stretch, } + +impl TryFrom for AlignItems { + type Error = (); + fn try_from(n: i32) -> Result { + match n { + 0 => Ok(AlignItems::Start), + 1 => Ok(AlignItems::End), + 2 => Ok(AlignItems::FlexStart), + 3 => Ok(AlignItems::FlexEnd), + 4 => Ok(AlignItems::Center), + 5 => Ok(AlignItems::Baseline), + 6 => Ok(AlignItems::Stretch), + _ => Err(()), + } + } +} + /// Used to control how child [`Nodes`](crate::node::Node) 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 @@ -55,8 +77,10 @@ pub type JustifySelf = AlignItems; /// For Grid it controls alignment in the block axis /// /// [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/align-content) +#[repr(u8)] #[derive(Copy, Clone, PartialEq, Eq, Debug)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "wasm", wasm_bindgen)] pub enum AlignContent { /// Items are packed toward the start of the axis Start, @@ -87,6 +111,24 @@ pub enum AlignContent { SpaceAround, } +impl TryFrom for AlignContent { + type Error = (); + fn try_from(n: i32) -> Result { + match n { + 0 => Ok(AlignContent::Start), + 1 => Ok(AlignContent::End), + 2 => Ok(AlignContent::FlexStart), + 3 => Ok(AlignContent::FlexEnd), + 4 => Ok(AlignContent::Center), + 6 => Ok(AlignContent::Stretch), + 7 => Ok(AlignContent::SpaceBetween), + 8 => Ok(AlignContent::SpaceEvenly), + 9 => Ok(AlignContent::SpaceAround), + _ => Err(()), + } + } +} + /// 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 diff --git a/src/style/flex.rs b/src/style/flex.rs index 8cce6d051..057df338b 100644 --- a/src/style/flex.rs +++ b/src/style/flex.rs @@ -1,12 +1,17 @@ //! Style types for Flexbox layout +#[cfg(feature = "wasm")] +use wasm_bindgen::prelude::*; + /// 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) +#[repr(u8)] #[derive(Copy, Clone, PartialEq, Eq, Debug)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "wasm", wasm_bindgen)] pub enum FlexWrap { /// Items will not wrap and stay on a single line NoWrap, @@ -22,6 +27,18 @@ impl Default for FlexWrap { } } +impl TryFrom for FlexWrap { + type Error = (); + fn try_from(n: i32) -> Result { + match n { + 0 => Ok(FlexWrap::NoWrap), + 1 => Ok(FlexWrap::Wrap), + 2 => Ok(FlexWrap::WrapReverse), + _ => Err(()), + } + } +} + /// The direction of the flexbox layout main axis. /// /// There are always two perpendicular layout axes: main (or primary) and cross (or secondary). @@ -33,8 +50,10 @@ impl Default for FlexWrap { /// The default behavior is [`FlexDirection::Row`]. /// /// [Specification](https://www.w3.org/TR/css-flexbox-1/#flex-direction-property) +#[repr(u8)] #[derive(Copy, Clone, PartialEq, Eq, Debug)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "wasm", wasm_bindgen)] pub enum FlexDirection { /// Defines +x as the main axis /// @@ -60,6 +79,19 @@ impl Default for FlexDirection { } } +impl TryFrom for FlexDirection { + type Error = (); + fn try_from(n: i32) -> Result { + match n { + 0 => Ok(FlexDirection::Row), + 1 => Ok(FlexDirection::Column), + 2 => Ok(FlexDirection::RowReverse), + 3 => Ok(FlexDirection::ColumnReverse), + _ => Err(()), + } + } +} + impl FlexDirection { #[inline] /// Is the direction [`FlexDirection::Row`] or [`FlexDirection::RowReverse`]? diff --git a/src/style/grid.rs b/src/style/grid.rs index 1053f74ff..13526cf96 100644 --- a/src/style/grid.rs +++ b/src/style/grid.rs @@ -8,6 +8,9 @@ use crate::sys::GridTrackVec; use core::cmp::{max, min}; use core::convert::Infallible; +#[cfg(feature = "wasm")] +use wasm_bindgen::prelude::*; + /// 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. @@ -15,8 +18,10 @@ use core::convert::Infallible; /// Defaults to [`GridAutoFlow::Row`] /// /// [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/grid-auto-flow) +#[repr(u8)] #[derive(Copy, Clone, PartialEq, Eq, Debug)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "wasm", wasm_bindgen)] pub enum GridAutoFlow { /// Items are placed by filling each row in turn, adding new rows as necessary Row, @@ -34,6 +39,19 @@ impl Default for GridAutoFlow { } } +impl TryFrom for GridAutoFlow { + type Error = (); + fn try_from(n: i32) -> Result { + match n { + 0 => Ok(GridAutoFlow::Row), + 1 => Ok(GridAutoFlow::Column), + 2 => Ok(GridAutoFlow::RowDense), + 3 => Ok(GridAutoFlow::ColumnDense), + _ => Err(()), + } + } +} + impl GridAutoFlow { /// Whether grid auto placement uses the sparse placement algorithm or the dense placement algorithm /// See: diff --git a/src/style/mod.rs b/src/style/mod.rs index ee4fa9d21..272e48e56 100644 --- a/src/style/mod.rs +++ b/src/style/mod.rs @@ -7,6 +7,9 @@ pub use self::alignment::{AlignContent, AlignItems, AlignSelf, JustifyContent, J pub use self::dimension::{AvailableSpace, Dimension, LengthPercentage, LengthPercentageAuto}; pub use self::flex::{FlexDirection, FlexWrap}; +#[cfg(feature = "wasm")] +use wasm_bindgen::prelude::*; + #[cfg(feature = "grid")] mod grid; #[cfg(feature = "grid")] @@ -28,16 +31,18 @@ use crate::sys::GridTrackVec; /// Sets the layout used for the children of this node /// /// [`Display::Flex`] is the default value. +#[repr(u8)] #[derive(Copy, Clone, PartialEq, Eq, Debug)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "wasm", wasm_bindgen)] pub enum Display { + /// The children will not be laid out, and will follow absolute positioning + None, /// The children will follow the flexbox layout algorithm Flex, /// The children will follow the CSS Grid layout algorithm #[cfg(feature = "grid")] Grid, - /// The children will not be laid out, and will follow absolute positioning - None, } impl Default for Display { @@ -46,6 +51,18 @@ impl Default for Display { } } +impl TryFrom for Display { + type Error = (); + fn try_from(n: i32) -> Result { + match n { + 0 => Ok(Display::None), + 1 => Ok(Display::Flex), + 2 => Ok(Display::Grid), + _ => Err(()), + } + } +} + /// The positioning strategy for this item. /// /// This controls both how the origin is determined for the [`Style::position`] field, @@ -55,8 +72,10 @@ impl Default for Display { /// which can be unintuitive. /// /// [`Position::Relative`] is the default value, in contrast to the default behavior in CSS. +#[repr(u8)] #[derive(Copy, Clone, PartialEq, Eq, Debug)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "wasm", wasm_bindgen)] pub enum Position { /// The offset is computed relative to the final position given by the layout algorithm. /// Offsets do not affect the position of any other items; they are effectively a correction factor applied at the end. @@ -75,6 +94,17 @@ impl Default for Position { } } +impl TryFrom for Position { + type Error = (); + fn try_from(n: i32) -> Result { + match n { + 0 => Ok(Position::Relative), + 1 => Ok(Position::Absolute), + _ => Err(()), + } + } +} + /// The flexbox layout information for a single [`Node`](crate::node::Node). /// /// The most important idea in flexbox is the notion of a "main" and "cross" axis, which are always perpendicular to each other. From 53e87c1b5bab219461b8eb17c818715750fddbe2 Mon Sep 17 00:00:00 2001 From: Nico Burns Date: Sun, 12 Mar 2023 00:51:38 +0000 Subject: [PATCH 02/41] Add TryFrom impls for LengthPercentage and LengthPercentageAuto --- src/style/dimension.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/style/dimension.rs b/src/style/dimension.rs index 21e3d7f1c..fb17fd836 100644 --- a/src/style/dimension.rs +++ b/src/style/dimension.rs @@ -30,6 +30,17 @@ impl FromPercent for LengthPercentage { } } +impl TryFrom for LengthPercentage { + type Error = (); + fn try_from(input: Dimension) -> Result { + match input { + Dimension::Points(value) => Ok(Self::Points(value)), + Dimension::Percent(value) => Ok(Self::Percent(value)), + Dimension::Auto => Err(()), + } + } +} + /// A unit of linear measurement /// /// This is commonly combined with [`Rect`], [`Point`](crate::geometry::Point) and [`Size`]. @@ -70,6 +81,19 @@ impl From for LengthPercentageAuto { } } +// Currently it would be possible to implement From for LengthPercentageAuto, but we +// anticipate that this won't the case in future, so for forwards compatibility we stick to a TryFrom impl here +impl TryFrom for LengthPercentageAuto { + type Error = (); + fn try_from(input: Dimension) -> Result { + match input { + Dimension::Points(value) => Ok(Self::Points(value)), + Dimension::Percent(value) => Ok(Self::Percent(value)), + Dimension::Auto => Ok(Self::Auto), + } + } +} + impl LengthPercentageAuto { /// Returns: /// - Some(points) for Points variants From 890fbce141d603500f3ea0ff291d8339eb8ca0e1 Mon Sep 17 00:00:00 2001 From: Nico Burns Date: Sun, 12 Mar 2023 00:54:31 +0000 Subject: [PATCH 03/41] Wasm bindings WIP --- bindings/wasm/Cargo.toml | 27 +++ bindings/wasm/src/lib.rs | 376 +++++++++++++++++++++++++++++++++++++ bindings/wasm/src/utils.rs | 5 + 3 files changed, 408 insertions(+) create mode 100644 bindings/wasm/Cargo.toml create mode 100644 bindings/wasm/src/lib.rs create mode 100644 bindings/wasm/src/utils.rs diff --git a/bindings/wasm/Cargo.toml b/bindings/wasm/Cargo.toml new file mode 100644 index 000000000..34713e9e1 --- /dev/null +++ b/bindings/wasm/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "taffy-layout" +version = "0.3.7" +edition = "2021" + +# This crate is for WASM bindings: it should never be published to crates.io +publish = false + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +default = ["console_error_panic_hook"] + +[dependencies] +wasm-bindgen = "0.2.84" +js-sys = "0.3" +taffy = { path = "../..", features = ["wasm"] } +console_error_panic_hook = { version = "0.1.1", optional = true } + +[dev-dependencies] +wasm-bindgen-test = "0.3.33" + +[profile.release] +opt-level = "s" + +[workspace] \ No newline at end of file diff --git a/bindings/wasm/src/lib.rs b/bindings/wasm/src/lib.rs new file mode 100644 index 000000000..f28a474b0 --- /dev/null +++ b/bindings/wasm/src/lib.rs @@ -0,0 +1,376 @@ +#![allow(non_snake_case)] + +mod utils; + +use std::cell::RefCell; +use std::rc::Rc; + +use js_sys::Function; +use js_sys::Reflect; +use taffy::style::*; +use taffy::style_helpers::TaffyZero; +use taffy::tree::LayoutTree; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +#[derive(Clone, Debug)] +pub struct Layout { + #[wasm_bindgen(readonly)] + pub width: f32, + + #[wasm_bindgen(readonly)] + pub height: f32, + + #[wasm_bindgen(readonly)] + pub x: f32, + + #[wasm_bindgen(readonly)] + pub y: f32, + + #[wasm_bindgen(readonly)] + pub childCount: usize, + + children: Vec, +} + +#[wasm_bindgen] +impl Layout { + fn new(allocator: &Allocator, node: taffy::node::Node) -> Layout { + let taffy = allocator.taffy.borrow(); + let layout = taffy.layout(node).unwrap(); + let children = taffy.children(node).unwrap(); + + Layout { + width: layout.size.width, + height: layout.size.height, + x: layout.location.x, + y: layout.location.y, + childCount: children.len(), + children: children.into_iter().map(|child| Layout::new(allocator, child)).collect(), + } + } + + #[wasm_bindgen] + pub fn child(&self, at: usize) -> Layout { + self.children[at].clone() + } +} + +#[wasm_bindgen] +#[derive(Clone)] +pub struct Allocator { + taffy: Rc>, +} + +#[wasm_bindgen] +impl Allocator { + #[wasm_bindgen(constructor)] + pub fn new() -> Self { + Self { taffy: Rc::new(RefCell::new(taffy::Taffy::new())) } + } +} + +#[wasm_bindgen] +pub struct Node { + allocator: Allocator, + node: taffy::node::Node, + style: JsValue, + + #[wasm_bindgen(readonly)] + pub childCount: usize, +} + +#[wasm_bindgen] +impl Node { + #[wasm_bindgen(constructor)] + pub fn new(allocator: &Allocator, style: &JsValue) -> Self { + Self { + allocator: allocator.clone(), + node: allocator.taffy.borrow_mut().new_leaf(parse_style(&style)).unwrap(), + style: style.clone(), + childCount: 0, + } + } + + #[wasm_bindgen(js_name = setMeasure)] + pub fn set_measure(&mut self, measure: &JsValue) { + let _measure = Function::from(measure.clone()); + + self.allocator + .taffy + .borrow_mut() + .set_measure( + self.node, + // Some(taffy::node::MeasureFunc::Boxed(Box::new( + // move |constraints| { + // use taffy::number::OrElse; + + // let widthConstraint = + // if let taffy::number::Number::Defined(val) = constraints.width { + // val.into() + // } else { + // JsValue::UNDEFINED + // }; + + // let heightConstaint = + // if let taffy::number::Number::Defined(val) = constraints.height { + // val.into() + // } else { + // JsValue::UNDEFINED + // }; + + // if let Ok(result) = + // measure.call2(&JsValue::UNDEFINED, &widthConstraint, &heightConstaint) + // { + // let width = get_f32(&result, "width"); + // let height = get_f32(&result, "height"); + + // if width.is_some() && height.is_some() { + // return taffy::geometry::Size { + // width: width.unwrap(), + // height: height.unwrap(), + // }; + // } + // } + + // constraints.map(|v| v.or_else(0.0)) + // }, + // ))), + None, + ) + .unwrap(); + } + + #[wasm_bindgen(js_name = addChild)] + pub fn add_child(&mut self, child: &Node) { + self.allocator.taffy.borrow_mut().add_child(self.node, child.node).unwrap(); + self.childCount += 1; + } + + #[wasm_bindgen(js_name = removeChild)] + pub fn remove_child(&mut self, child: &Node) { + self.allocator.taffy.borrow_mut().remove_child(self.node, child.node).unwrap(); + self.childCount -= 1; + } + + #[wasm_bindgen(js_name = replaceChildAtIndex)] + pub fn replace_child_at_index(&mut self, index: usize, child: &Node) { + self.allocator.taffy.borrow_mut().replace_child_at_index(self.node, index, child.node).unwrap(); + } + + #[wasm_bindgen(js_name = removeChildAtIndex)] + pub fn remove_child_at_index(&mut self, index: usize) { + self.allocator.taffy.borrow_mut().remove_child_at_index(self.node, index).unwrap(); + self.childCount -= 1; + } + + #[wasm_bindgen(js_name = getStyle)] + pub fn get_style(&self) -> JsValue { + self.style.clone() + } + + #[wasm_bindgen(js_name = setStyle)] + pub fn set_style(&mut self, style: &JsValue) { + self.allocator.taffy.borrow_mut().set_style(self.node, parse_style(style)).unwrap(); + self.style = style.clone(); + } + + #[wasm_bindgen(js_name = markDirty)] + pub fn mark_dirty(&mut self) { + self.allocator.taffy.borrow_mut().mark_dirty(self.node).unwrap() + } + + #[wasm_bindgen(js_name = isDirty)] + pub fn is_dirty(&self) -> bool { + self.allocator.taffy.borrow().dirty(self.node).unwrap() + } + + #[wasm_bindgen(js_name = isChildless)] + pub fn is_childless(&mut self) -> bool { + self.allocator.taffy.borrow_mut().is_childless(self.node) + } + + #[wasm_bindgen(js_name = computeLayout)] + pub fn compute_layout(&mut self, size: &JsValue) -> Layout { + self.allocator + .taffy + .borrow_mut() + .compute_layout( + self.node, + taffy::geometry::Size { + width: get_available_space(size, "width"), + height: get_available_space(size, "height"), + }, + ) + .unwrap(); + Layout::new(&self.allocator, self.node) + } +} + +fn parse_style(style: &JsValue) -> taffy::style::Style { + taffy::style::Style { + display: try_parse_from_i32(style, "display").unwrap_or_default(), + + // Position styles + position: try_parse_from_i32(style, "position").unwrap_or_default(), + inset: taffy::geometry::Rect { + left: try_parse_length_percentage_auto(style, "insetLeft").unwrap_or(LengthPercentageAuto::Auto), + right: try_parse_length_percentage_auto(style, "insetRight").unwrap_or(LengthPercentageAuto::Auto), + top: try_parse_length_percentage_auto(style, "insetTop").unwrap_or(LengthPercentageAuto::Auto), + bottom: try_parse_length_percentage_auto(style, "insetBottom").unwrap_or(LengthPercentageAuto::Auto), + }, + + // Size styles + size: taffy::geometry::Size { + width: try_parse_dimension(style, "width").unwrap_or(Dimension::Auto), + height: try_parse_dimension(style, "height").unwrap_or(Dimension::Auto), + }, + min_size: taffy::geometry::Size { + width: try_parse_dimension(style, "minWidth").unwrap_or(Dimension::Auto), + height: try_parse_dimension(style, "minHeight").unwrap_or(Dimension::Auto), + }, + max_size: taffy::geometry::Size { + width: try_parse_dimension(style, "maxWidth").unwrap_or(Dimension::Auto), + height: try_parse_dimension(style, "maxHeight").unwrap_or(Dimension::Auto), + }, + aspect_ratio: get_f32(style, "aspectRatio"), + + // Alignment styles + align_items: try_parse_from_i32(style, "alignItems"), + align_self: try_parse_from_i32(style, "alignSelf"), + align_content: try_parse_from_i32(style, "alignContent"), + justify_content: try_parse_from_i32(style, "justifyContent"), + justify_self: try_parse_from_i32(style, "justifySelf"), + justify_items: try_parse_from_i32(style, "justifyItems"), + + // Spacing styles + margin: taffy::geometry::Rect { + left: try_parse_length_percentage_auto(style, "marginLeft").unwrap_or(LengthPercentageAuto::Points(0.0)), + right: try_parse_length_percentage_auto(style, "marginRight").unwrap_or(LengthPercentageAuto::Points(0.0)), + top: try_parse_length_percentage_auto(style, "marginTop").unwrap_or(LengthPercentageAuto::Points(0.0)), + bottom: try_parse_length_percentage_auto(style, "marginBottom") + .unwrap_or(LengthPercentageAuto::Points(0.0)), + }, + padding: taffy::geometry::Rect { + left: try_parse_length_percentage(style, "paddingLeft").unwrap_or(LengthPercentage::Points(0.0)), + right: try_parse_length_percentage(style, "paddingRight").unwrap_or(LengthPercentage::Points(0.0)), + top: try_parse_length_percentage(style, "paddingTop").unwrap_or(LengthPercentage::Points(0.0)), + bottom: try_parse_length_percentage(style, "paddingBottom").unwrap_or(LengthPercentage::Points(0.0)), + }, + border: taffy::geometry::Rect { + left: try_parse_length_percentage(style, "borderLeft").unwrap_or(LengthPercentage::Points(0.0)), + right: try_parse_length_percentage(style, "borderRight").unwrap_or(LengthPercentage::Points(0.0)), + top: try_parse_length_percentage(style, "borderTop").unwrap_or(LengthPercentage::Points(0.0)), + bottom: try_parse_length_percentage(style, "borderBottom").unwrap_or(LengthPercentage::Points(0.0)), + }, + gap: taffy::geometry::Size { + width: try_parse_length_percentage(style, "gapWidth").unwrap_or(LengthPercentage::Points(0.0)), + height: try_parse_length_percentage(style, "gapHeight").unwrap_or(LengthPercentage::Points(0.0)), + }, + + // Flexbox styles + flex_direction: try_parse_from_i32(style, "flexDirection").unwrap_or_default(), + flex_wrap: try_parse_from_i32(style, "flexWrap").unwrap_or_default(), + flex_grow: get_f32(style, "flexGrow").unwrap_or(0.0), + flex_shrink: get_f32(style, "flexShrink").unwrap_or(1.0), + flex_basis: try_parse_dimension(style, "flexBasis").unwrap_or(Dimension::Auto), + + // CSS Grid styles + // TODO parse the remaining CSS Grid styles + grid_auto_flow: try_parse_from_i32(style, "gridAutoFlow").unwrap_or_default(), + grid_template_rows: Default::default(), + grid_template_columns: Default::default(), + grid_auto_rows: Default::default(), + grid_auto_columns: Default::default(), + grid_row: Default::default(), + grid_column: Default::default(), + } +} + +fn try_parse_from_i32>(style: &JsValue, property_key: &'static str) -> Option { + get_i32(style, property_key).and_then(|i| T::try_from(i).ok()) +} + +fn try_parse_dimension(obj: &JsValue, key: &str) -> Option { + if has_key(obj, key) { + if let Ok(val) = Reflect::get(obj, &key.into()) { + if let Some(number) = val.as_f64() { + return Some(taffy::style::Dimension::Points(number as f32)); + } + if let Some(string) = val.as_string() { + let string = string.trim(); + if string == "auto" { + return Some(taffy::style::Dimension::Auto); + } + if string.ends_with('%') { + let len = string.len(); + if let Ok(number) = string[..len - 1].parse::() { + return Some(taffy::style::Dimension::Percent(number / 100.0)); + } + } + if let Ok(number) = string.parse::() { + return Some(taffy::style::Dimension::Points(number)); + } + } + } + } + None +} + +// We first parse into a Dimension then use the TryFrom impl to attempt a conversion +fn try_parse_length_percentage_auto(obj: &JsValue, key: &str) -> Option { + try_parse_dimension(obj, key).and_then(|dim| dim.try_into().ok()) +} + +// We first parse into a Dimension then use the TryFrom impl to attempt a conversion +fn try_parse_length_percentage(obj: &JsValue, key: &str) -> Option { + try_parse_dimension(obj, key).and_then(|dim| dim.try_into().ok()) +} + +fn get_available_space(obj: &JsValue, key: &str) -> taffy::style::AvailableSpace { + if has_key(obj, key) { + if let Ok(val) = Reflect::get(obj, &key.into()) { + if let Some(number) = val.as_f64() { + return taffy::style::AvailableSpace::Definite(number as f32); + } + if let Some(string) = val.as_string() { + if string == "min" || string == "minContent" { + return taffy::style::AvailableSpace::MinContent; + } + if string == "max" || string == "maxContent" { + return taffy::style::AvailableSpace::MaxContent; + } + if let Ok(number) = string.parse::() { + return taffy::style::AvailableSpace::Definite(number); + } + } + } + } + taffy::style::AvailableSpace::ZERO +} + +fn get_i32(obj: &JsValue, key: &str) -> Option { + if has_key(obj, key) { + if let Ok(val) = Reflect::get(obj, &key.into()) { + return val.as_f64().map(|v| v as i32); + } + } + None +} + +fn get_f32(obj: &JsValue, key: &str) -> Option { + if has_key(obj, key) { + if let Ok(val) = Reflect::get(obj, &key.into()) { + return val.as_f64().map(|v| v as f32); + } + } + None +} + +fn has_key(obj: &JsValue, key: &str) -> bool { + if let Ok(exists) = Reflect::has(obj, &key.into()) { + exists + } else { + false + } +} diff --git a/bindings/wasm/src/utils.rs b/bindings/wasm/src/utils.rs new file mode 100644 index 000000000..930538cbb --- /dev/null +++ b/bindings/wasm/src/utils.rs @@ -0,0 +1,5 @@ +#[allow(dead_code)] +pub fn set_panic_hook() { + #[cfg(feature = "console_error_panic_hook")] + console_error_panic_hook::set_once(); +} \ No newline at end of file From 8d7913cd0ba499bf2aa4d3c5c4b3bd0c88f86bb6 Mon Sep 17 00:00:00 2001 From: Nico Burns Date: Sun, 12 Mar 2023 01:03:13 +0000 Subject: [PATCH 04/41] Simplify JsValue accessing code --- bindings/wasm/src/lib.rs | 101 +++++++++++++++++---------------------- 1 file changed, 45 insertions(+), 56 deletions(-) diff --git a/bindings/wasm/src/lib.rs b/bindings/wasm/src/lib.rs index f28a474b0..d0632bfea 100644 --- a/bindings/wasm/src/lib.rs +++ b/bindings/wasm/src/lib.rs @@ -287,33 +287,48 @@ fn parse_style(style: &JsValue) -> taffy::style::Style { } } +#[allow(dead_code)] +fn has_key(obj: &JsValue, key: &str) -> bool { + Reflect::has(obj, &key.into()).unwrap_or(false) +} + +fn get_key(obj: &JsValue, key: &str) -> Option { + Reflect::get(obj, &key.into()).ok() +} + +fn get_i32(obj: &JsValue, key: &str) -> Option { + get_key(obj, key).and_then(|val| val.as_f64().map(|v| v as i32)) +} + +fn get_f32(obj: &JsValue, key: &str) -> Option { + get_key(obj, key).and_then(|val| val.as_f64().map(|v| v as f32)) +} + fn try_parse_from_i32>(style: &JsValue, property_key: &'static str) -> Option { get_i32(style, property_key).and_then(|i| T::try_from(i).ok()) } fn try_parse_dimension(obj: &JsValue, key: &str) -> Option { - if has_key(obj, key) { - if let Ok(val) = Reflect::get(obj, &key.into()) { - if let Some(number) = val.as_f64() { - return Some(taffy::style::Dimension::Points(number as f32)); + if let Some(val) = get_key(obj, key) { + if let Some(number) = val.as_f64() { + return Some(taffy::style::Dimension::Points(number as f32)); + } + if let Some(string) = val.as_string() { + let string = string.trim(); + if string == "auto" { + return Some(taffy::style::Dimension::Auto); } - if let Some(string) = val.as_string() { - let string = string.trim(); - if string == "auto" { - return Some(taffy::style::Dimension::Auto); - } - if string.ends_with('%') { - let len = string.len(); - if let Ok(number) = string[..len - 1].parse::() { - return Some(taffy::style::Dimension::Percent(number / 100.0)); - } - } - if let Ok(number) = string.parse::() { - return Some(taffy::style::Dimension::Points(number)); + if string.ends_with('%') { + let len = string.len(); + if let Ok(number) = string[..len - 1].parse::() { + return Some(taffy::style::Dimension::Percent(number / 100.0)); } } + if let Ok(number) = string.parse::() { + return Some(taffy::style::Dimension::Points(number)); + } } - } + }; None } @@ -328,49 +343,23 @@ fn try_parse_length_percentage(obj: &JsValue, key: &str) -> Option taffy::style::AvailableSpace { - if has_key(obj, key) { - if let Ok(val) = Reflect::get(obj, &key.into()) { - if let Some(number) = val.as_f64() { - return taffy::style::AvailableSpace::Definite(number as f32); + if let Some(val) = get_key(obj, key) { + if let Some(number) = val.as_f64() { + return taffy::style::AvailableSpace::Definite(number as f32); + } + if let Some(string) = val.as_string() { + if string == "min" || string == "minContent" { + return taffy::style::AvailableSpace::MinContent; } - if let Some(string) = val.as_string() { - if string == "min" || string == "minContent" { - return taffy::style::AvailableSpace::MinContent; - } - if string == "max" || string == "maxContent" { - return taffy::style::AvailableSpace::MaxContent; - } - if let Ok(number) = string.parse::() { - return taffy::style::AvailableSpace::Definite(number); - } + if string == "max" || string == "maxContent" { + return taffy::style::AvailableSpace::MaxContent; + } + if let Ok(number) = string.parse::() { + return taffy::style::AvailableSpace::Definite(number); } } } taffy::style::AvailableSpace::ZERO } -fn get_i32(obj: &JsValue, key: &str) -> Option { - if has_key(obj, key) { - if let Ok(val) = Reflect::get(obj, &key.into()) { - return val.as_f64().map(|v| v as i32); - } - } - None -} -fn get_f32(obj: &JsValue, key: &str) -> Option { - if has_key(obj, key) { - if let Ok(val) = Reflect::get(obj, &key.into()) { - return val.as_f64().map(|v| v as f32); - } - } - None -} - -fn has_key(obj: &JsValue, key: &str) -> bool { - if let Ok(exists) = Reflect::has(obj, &key.into()) { - exists - } else { - false - } -} From 690d0ac9470e211476e90f70864f98b0768207bf Mon Sep 17 00:00:00 2001 From: Nico Burns Date: Sun, 12 Mar 2023 01:05:37 +0000 Subject: [PATCH 05/41] Improve AvailableSpace parsing --- bindings/wasm/src/lib.rs | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/bindings/wasm/src/lib.rs b/bindings/wasm/src/lib.rs index d0632bfea..4ec3d8ee4 100644 --- a/bindings/wasm/src/lib.rs +++ b/bindings/wasm/src/lib.rs @@ -8,7 +8,6 @@ use std::rc::Rc; use js_sys::Function; use js_sys::Reflect; use taffy::style::*; -use taffy::style_helpers::TaffyZero; use taffy::tree::LayoutTree; use wasm_bindgen::prelude::*; @@ -198,8 +197,8 @@ impl Node { .compute_layout( self.node, taffy::geometry::Size { - width: get_available_space(size, "width"), - height: get_available_space(size, "height"), + width: try_parse_available_space(size, "width").unwrap_or(AvailableSpace::MaxContent), + height: try_parse_available_space(size, "height").unwrap_or(AvailableSpace::MaxContent), }, ) .unwrap(); @@ -213,7 +212,7 @@ fn parse_style(style: &JsValue) -> taffy::style::Style { // Position styles position: try_parse_from_i32(style, "position").unwrap_or_default(), - inset: taffy::geometry::Rect { + inset: taffy::geometry::Rect { left: try_parse_length_percentage_auto(style, "insetLeft").unwrap_or(LengthPercentageAuto::Auto), right: try_parse_length_percentage_auto(style, "insetRight").unwrap_or(LengthPercentageAuto::Auto), top: try_parse_length_percentage_auto(style, "insetTop").unwrap_or(LengthPercentageAuto::Auto), @@ -342,24 +341,22 @@ fn try_parse_length_percentage(obj: &JsValue, key: &str) -> Option taffy::style::AvailableSpace { +fn try_parse_available_space(obj: &JsValue, key: &str) -> Option { if let Some(val) = get_key(obj, key) { if let Some(number) = val.as_f64() { - return taffy::style::AvailableSpace::Definite(number as f32); + return Some(AvailableSpace::Definite(number as f32)); } if let Some(string) = val.as_string() { - if string == "min" || string == "minContent" { - return taffy::style::AvailableSpace::MinContent; + if string == "min-content" { + return Some(AvailableSpace::MinContent); } - if string == "max" || string == "maxContent" { - return taffy::style::AvailableSpace::MaxContent; + if string == "max-content" { + return Some(AvailableSpace::MaxContent); } if let Ok(number) = string.parse::() { - return taffy::style::AvailableSpace::Definite(number); + return Some(AvailableSpace::Definite(number)); } } } - taffy::style::AvailableSpace::ZERO + None } - - From 8bb725f41de97d74c800c109eeb6f55860874f1a Mon Sep 17 00:00:00 2001 From: Nico Burns Date: Sun, 12 Mar 2023 01:19:06 +0000 Subject: [PATCH 06/41] Add FromStr impls to Dimension and AvailableSpace --- src/style/dimension.rs | 55 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/src/style/dimension.rs b/src/style/dimension.rs index fb17fd836..038dbaf0c 100644 --- a/src/style/dimension.rs +++ b/src/style/dimension.rs @@ -1,5 +1,7 @@ //! Style types for representing lengths / sizes +use core::str::FromStr; + use crate::geometry::{Rect, Size}; use crate::style_helpers::{FromPercent, FromPoints, TaffyAuto, TaffyMaxContent, TaffyMinContent, TaffyZero}; use crate::sys::abs; @@ -122,6 +124,7 @@ pub enum Dimension { /// The dimension should be automatically computed Auto, } + impl TaffyZero for Dimension { const ZERO: Self = Self::Points(0.0); } @@ -139,6 +142,12 @@ impl FromPercent for Dimension { } } +impl From for Dimension { + fn from(value: f32) -> Self { + Dimension::Points(value) + } +} + impl From for Dimension { fn from(input: LengthPercentage) -> Self { match input { @@ -158,6 +167,31 @@ impl From for Dimension { } } +impl FromStr for Dimension { + type Err = (); + + fn from_str(s: &str) -> Result { + let s = s.trim(); + + // Auto + if s == "auto" { + return Ok(Self::Auto); + } + // px + if s.ends_with("px") { + let len = s.len(); + return s[..len - 1].trim().parse::().map(Self::Points).map_err(|_| ()); + } + // percent + if s.ends_with('%') { + let len = s.len(); + return s[..len - 1].trim().parse::().map(|number| Self::Percent(number / 100.0)).map_err(|_| ()); + } + // Bare number (px) + s.parse::().map(Self::Points).map_err(|_| ()) + } +} + impl Dimension { /// Is this value defined? pub(crate) fn is_defined(self) -> bool { @@ -324,6 +358,27 @@ impl From> for AvailableSpace { } } +impl FromStr for AvailableSpace { + type Err = (); + + fn from_str(s: &str) -> Result { + let s = s.trim(); + + if s == "min-content" { + return Ok(Self::MinContent); + } + if s == "max-content" { + return Ok(Self::MaxContent); + } + if s.ends_with("px") { + let len = s.len(); + return s[..len - 1].trim().parse::().map(AvailableSpace::Definite).map_err(|_| ()); + } + + s.parse::().map(AvailableSpace::Definite).map_err(|_| ()) + } +} + impl Size { /// Convert `Size` into `Size>` pub fn into_options(self) -> Size> { From f97761272636357408974b64bff2e828e1b68c42 Mon Sep 17 00:00:00 2001 From: Nico Burns Date: Sun, 12 Mar 2023 01:26:25 +0000 Subject: [PATCH 07/41] Use Dimension and AvailableSpace's FromStr impls in wasm bindings --- bindings/wasm/src/lib.rs | 51 +++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/bindings/wasm/src/lib.rs b/bindings/wasm/src/lib.rs index 4ec3d8ee4..013c67fc2 100644 --- a/bindings/wasm/src/lib.rs +++ b/bindings/wasm/src/lib.rs @@ -2,6 +2,7 @@ mod utils; +use core::str::FromStr; use std::cell::RefCell; use std::rc::Rc; @@ -307,56 +308,52 @@ fn try_parse_from_i32>(style: &JsValue, property_key: &'static s get_i32(style, property_key).and_then(|i| T::try_from(i).ok()) } -fn try_parse_dimension(obj: &JsValue, key: &str) -> Option { + +fn try_parse_dimension(obj: &JsValue, key: &str) -> Option { if let Some(val) = get_key(obj, key) { if let Some(number) = val.as_f64() { - return Some(taffy::style::Dimension::Points(number as f32)); + return Some(Dimension::Points(number as f32)); } if let Some(string) = val.as_string() { - let string = string.trim(); - if string == "auto" { - return Some(taffy::style::Dimension::Auto); - } - if string.ends_with('%') { - let len = string.len(); - if let Ok(number) = string[..len - 1].parse::() { - return Some(taffy::style::Dimension::Percent(number / 100.0)); - } - } - if let Ok(number) = string.parse::() { - return Some(taffy::style::Dimension::Points(number)); - } + return string.parse().ok() } }; None } // We first parse into a Dimension then use the TryFrom impl to attempt a conversion -fn try_parse_length_percentage_auto(obj: &JsValue, key: &str) -> Option { +fn try_parse_length_percentage_auto(obj: &JsValue, key: &str) -> Option { try_parse_dimension(obj, key).and_then(|dim| dim.try_into().ok()) } // We first parse into a Dimension then use the TryFrom impl to attempt a conversion -fn try_parse_length_percentage(obj: &JsValue, key: &str) -> Option { +fn try_parse_length_percentage(obj: &JsValue, key: &str) -> Option { try_parse_dimension(obj, key).and_then(|dim| dim.try_into().ok()) } -fn try_parse_available_space(obj: &JsValue, key: &str) -> Option { +fn try_parse_available_space(obj: &JsValue, key: &str) -> Option { if let Some(val) = get_key(obj, key) { if let Some(number) = val.as_f64() { return Some(AvailableSpace::Definite(number as f32)); } if let Some(string) = val.as_string() { - if string == "min-content" { - return Some(AvailableSpace::MinContent); - } - if string == "max-content" { - return Some(AvailableSpace::MaxContent); - } - if let Ok(number) = string.parse::() { - return Some(AvailableSpace::Definite(number)); - } + return string.parse().ok() } } None } + +// Generic try_parse_dimension impl +// Could in theory be used to replace the above 4 functions, but it doesn't quite work and it's +// a bit confusing +// fn try_parse_dimension + Into>(obj: &JsValue, key: &str) -> Option { +// if let Some(val) = get_key(obj, key) { +// if let Some(number) = val.as_f64() { +// return Some(T::from(number as f32).into()); +// } +// if let Some(string) = val.as_string() { +// return string.parse::().map(|val| val.into()).ok() +// } +// }; +// None +// } From 3a9e5ff061696ada142c527450c247cf5b5e0e5c Mon Sep 17 00:00:00 2001 From: Nico Burns Date: Sun, 12 Mar 2023 01:27:30 +0000 Subject: [PATCH 08/41] Add TODO for measure functions --- bindings/wasm/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/bindings/wasm/src/lib.rs b/bindings/wasm/src/lib.rs index 013c67fc2..0a99729d6 100644 --- a/bindings/wasm/src/lib.rs +++ b/bindings/wasm/src/lib.rs @@ -101,6 +101,7 @@ impl Node { .borrow_mut() .set_measure( self.node, + // TODO: fix setting measure functions // Some(taffy::node::MeasureFunc::Boxed(Box::new( // move |constraints| { // use taffy::number::OrElse; From c481ea261c36c2cede1927bfed049f2c98db0357 Mon Sep 17 00:00:00 2001 From: Nico Burns Date: Sun, 12 Mar 2023 01:54:52 +0000 Subject: [PATCH 09/41] Feature flag Display::Grid variant in From impl --- src/style/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/style/mod.rs b/src/style/mod.rs index 272e48e56..92f53e11a 100644 --- a/src/style/mod.rs +++ b/src/style/mod.rs @@ -57,6 +57,7 @@ impl TryFrom for Display { match n { 0 => Ok(Display::None), 1 => Ok(Display::Flex), + #[cfg(feature = "grid")] 2 => Ok(Display::Grid), _ => Err(()), } From 45c39b04ba0aa03401379c7148af3f24f1a6fad1 Mon Sep 17 00:00:00 2001 From: Nico Burns Date: Sun, 12 Mar 2023 15:30:23 +0000 Subject: [PATCH 10/41] Enable measure func setting --- bindings/wasm/src/lib.rs | 97 +++++++++++++++++++++------------------- 1 file changed, 51 insertions(+), 46 deletions(-) diff --git a/bindings/wasm/src/lib.rs b/bindings/wasm/src/lib.rs index 0a99729d6..dfb9180a6 100644 --- a/bindings/wasm/src/lib.rs +++ b/bindings/wasm/src/lib.rs @@ -2,13 +2,13 @@ mod utils; -use core::str::FromStr; use std::cell::RefCell; use std::rc::Rc; +use js_sys::Array; use js_sys::Function; use js_sys::Reflect; -use taffy::style::*; +use taffy::prelude::*; use taffy::tree::LayoutTree; use wasm_bindgen::prelude::*; @@ -94,51 +94,57 @@ impl Node { #[wasm_bindgen(js_name = setMeasure)] pub fn set_measure(&mut self, measure: &JsValue) { - let _measure = Function::from(measure.clone()); + // let js_measure_func = Arc::new(Mutex::new(Function::from(measure.clone()))); + + struct FuncWrap(Function); + impl FuncWrap { + fn apply(&self, this: &JsValue, args: &Array) -> Result { + self.0.apply(this, args) + } + } + // SAFETY: Wasm is single-threaded so there can't be multiple threads + unsafe impl Send for FuncWrap {} + unsafe impl Sync for FuncWrap {} + + let js_measure_func = FuncWrap(Function::from(measure.clone())); + + let measure_func = move |known_dimensions: Size>, available_space: Size| { + fn convert_available_space(val: AvailableSpace) -> JsValue { + match val { + AvailableSpace::Definite(val) => val.into(), + AvailableSpace::MaxContent => JsValue::from_str("max-content"), + AvailableSpace::MinContent => JsValue::from_str("min-content"), + } + } + + let known_width = known_dimensions.width.map(|val| val.into()).unwrap_or(JsValue::UNDEFINED); + let known_height = known_dimensions.height.map(|val| val.into()).unwrap_or(JsValue::UNDEFINED); + + let available_width = convert_available_space(available_space.width); + let available_height = convert_available_space(available_space.height); + + let args = Array::new_with_length(4); + args.set(0, known_width); + args.set(1, known_height); + args.set(2, available_width); + args.set(3, available_height); + + if let Ok(result) = js_measure_func.apply(&JsValue::UNDEFINED, &args) { + let width = get_f32(&result, "width"); + let height = get_f32(&result, "height"); + + if width.is_some() && height.is_some() { + return Size { width: width.unwrap(), height: height.unwrap() }; + } + } + + known_dimensions.unwrap_or(Size::ZERO) + }; self.allocator .taffy .borrow_mut() - .set_measure( - self.node, - // TODO: fix setting measure functions - // Some(taffy::node::MeasureFunc::Boxed(Box::new( - // move |constraints| { - // use taffy::number::OrElse; - - // let widthConstraint = - // if let taffy::number::Number::Defined(val) = constraints.width { - // val.into() - // } else { - // JsValue::UNDEFINED - // }; - - // let heightConstaint = - // if let taffy::number::Number::Defined(val) = constraints.height { - // val.into() - // } else { - // JsValue::UNDEFINED - // }; - - // if let Ok(result) = - // measure.call2(&JsValue::UNDEFINED, &widthConstraint, &heightConstaint) - // { - // let width = get_f32(&result, "width"); - // let height = get_f32(&result, "height"); - - // if width.is_some() && height.is_some() { - // return taffy::geometry::Size { - // width: width.unwrap(), - // height: height.unwrap(), - // }; - // } - // } - - // constraints.map(|v| v.or_else(0.0)) - // }, - // ))), - None, - ) + .set_measure(self.node, Some(taffy::node::MeasureFunc::Boxed(Box::new(measure_func)))) .unwrap(); } @@ -309,14 +315,13 @@ fn try_parse_from_i32>(style: &JsValue, property_key: &'static s get_i32(style, property_key).and_then(|i| T::try_from(i).ok()) } - fn try_parse_dimension(obj: &JsValue, key: &str) -> Option { if let Some(val) = get_key(obj, key) { if let Some(number) = val.as_f64() { return Some(Dimension::Points(number as f32)); } if let Some(string) = val.as_string() { - return string.parse().ok() + return string.parse().ok(); } }; None @@ -338,7 +343,7 @@ fn try_parse_available_space(obj: &JsValue, key: &str) -> Option return Some(AvailableSpace::Definite(number as f32)); } if let Some(string) = val.as_string() { - return string.parse().ok() + return string.parse().ok(); } } None From f1c62d9a3aed2d2870f1ccd53ecda14876102959 Mon Sep 17 00:00:00 2001 From: Nico Burns Date: Mon, 20 Mar 2023 19:44:05 +0000 Subject: [PATCH 11/41] Add wasm_test.html --- bindings/wasm/wasm_test.html | 61 ++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 bindings/wasm/wasm_test.html diff --git a/bindings/wasm/wasm_test.html b/bindings/wasm/wasm_test.html new file mode 100644 index 000000000..3d4e227ea --- /dev/null +++ b/bindings/wasm/wasm_test.html @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + From 94eaca2021b28a8b66f2f4793c0e67473d980155 Mon Sep 17 00:00:00 2001 From: Nico Burns Date: Mon, 20 Mar 2023 19:55:11 +0000 Subject: [PATCH 12/41] Bump package version to 0.3.9 --- bindings/wasm/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/wasm/Cargo.toml b/bindings/wasm/Cargo.toml index 34713e9e1..e56ef4514 100644 --- a/bindings/wasm/Cargo.toml +++ b/bindings/wasm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "taffy-layout" -version = "0.3.7" +version = "0.3.9" edition = "2021" # This crate is for WASM bindings: it should never be published to crates.io From 5d681b492e79f8de2165b3dbb7079feb23799e24 Mon Sep 17 00:00:00 2001 From: Nico Burns Date: Mon, 20 Mar 2023 21:14:16 +0000 Subject: [PATCH 13/41] Add set_flex_grow and set_height functions --- bindings/wasm/src/lib.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/bindings/wasm/src/lib.rs b/bindings/wasm/src/lib.rs index dfb9180a6..a2728f010 100644 --- a/bindings/wasm/src/lib.rs +++ b/bindings/wasm/src/lib.rs @@ -182,6 +182,16 @@ impl Node { self.style = style.clone(); } + #[wasm_bindgen(js_name = setFlexGrow)] + pub fn set_flex_grow(&mut self, flex_grow: f32) { + self.allocator.taffy.borrow_mut().style_mut(self.node).unwrap().flex_grow = flex_grow; + } + + #[wasm_bindgen(js_name = setHeight)] + pub fn set_height(&mut self, height: &str) { + self.allocator.taffy.borrow_mut().style_mut(self.node).unwrap().size.height = height.parse().unwrap(); + } + #[wasm_bindgen(js_name = markDirty)] pub fn mark_dirty(&mut self) { self.allocator.taffy.borrow_mut().mark_dirty(self.node).unwrap() From 6f15afded27118e9112d3929c97268d7ec8dbf58 Mon Sep 17 00:00:00 2001 From: Nico Burns Date: Mon, 20 Mar 2023 21:19:55 +0000 Subject: [PATCH 14/41] Don't parse styles when creating nodes --- bindings/wasm/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/wasm/src/lib.rs b/bindings/wasm/src/lib.rs index a2728f010..4bf85cfdc 100644 --- a/bindings/wasm/src/lib.rs +++ b/bindings/wasm/src/lib.rs @@ -86,7 +86,7 @@ impl Node { pub fn new(allocator: &Allocator, style: &JsValue) -> Self { Self { allocator: allocator.clone(), - node: allocator.taffy.borrow_mut().new_leaf(parse_style(&style)).unwrap(), + node: allocator.taffy.borrow_mut().new_leaf(Style::DEFAULT).unwrap(), style: style.clone(), childCount: 0, } From 2608814bb9a580ea2ee9515a4e6fa2c890efb18b Mon Sep 17 00:00:00 2001 From: Nico Burns Date: Mon, 20 Mar 2023 21:20:24 +0000 Subject: [PATCH 15/41] Add style_mut function to Taffy struct --- src/node.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/node.rs b/src/node.rs index 500f9d9eb..fd8b1d70b 100644 --- a/src/node.rs +++ b/src/node.rs @@ -345,11 +345,16 @@ impl Taffy { Ok(()) } - /// Gets the [`Style`] of the provided `node` + /// Gets a reference to the [`Style`] of the provided `node` pub fn style(&self, node: Node) -> TaffyResult<&Style> { Ok(&self.nodes[node].style) } + /// Gets a mutable reference to the [`Style`] of the provided `node` + pub fn style_mut(&mut self, node: Node) -> TaffyResult<&mut Style> { + Ok(&mut self.nodes[node].style) + } + /// Return this node layout relative to its parent pub fn layout(&self, node: Node) -> TaffyResult<&Layout> { Ok(&self.nodes[node].layout) From 975a6f98f12990f536eb434159fb38846b80477c Mon Sep 17 00:00:00 2001 From: Nico Burns Date: Mon, 20 Mar 2023 21:20:53 +0000 Subject: [PATCH 16/41] Improve test page --- bindings/wasm/wasm_test.html | 132 +++++++++++++++++++++++++++-------- 1 file changed, 101 insertions(+), 31 deletions(-) diff --git a/bindings/wasm/wasm_test.html b/bindings/wasm/wasm_test.html index 3d4e227ea..f3464ac68 100644 --- a/bindings/wasm/wasm_test.html +++ b/bindings/wasm/wasm_test.html @@ -1,3 +1,4 @@ + @@ -6,55 +7,124 @@ - + + // Actually run benchmarks + async function run() { + let start, time; - - From c8f54c81a4af266826157e4a527c815af186547b Mon Sep 17 00:00:00 2001 From: Nico Burns Date: Fri, 24 Mar 2023 04:03:33 +0000 Subject: [PATCH 17/41] Remove style property from Node --- bindings/wasm/src/lib.rs | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/bindings/wasm/src/lib.rs b/bindings/wasm/src/lib.rs index 4bf85cfdc..99dc1cfbd 100644 --- a/bindings/wasm/src/lib.rs +++ b/bindings/wasm/src/lib.rs @@ -74,7 +74,6 @@ impl Allocator { pub struct Node { allocator: Allocator, node: taffy::node::Node, - style: JsValue, #[wasm_bindgen(readonly)] pub childCount: usize, @@ -83,11 +82,10 @@ pub struct Node { #[wasm_bindgen] impl Node { #[wasm_bindgen(constructor)] - pub fn new(allocator: &Allocator, style: &JsValue) -> Self { + pub fn new(allocator: &Allocator) -> Self { Self { allocator: allocator.clone(), node: allocator.taffy.borrow_mut().new_leaf(Style::DEFAULT).unwrap(), - style: style.clone(), childCount: 0, } } @@ -171,17 +169,6 @@ impl Node { self.childCount -= 1; } - #[wasm_bindgen(js_name = getStyle)] - pub fn get_style(&self) -> JsValue { - self.style.clone() - } - - #[wasm_bindgen(js_name = setStyle)] - pub fn set_style(&mut self, style: &JsValue) { - self.allocator.taffy.borrow_mut().set_style(self.node, parse_style(style)).unwrap(); - self.style = style.clone(); - } - #[wasm_bindgen(js_name = setFlexGrow)] pub fn set_flex_grow(&mut self, flex_grow: f32) { self.allocator.taffy.borrow_mut().style_mut(self.node).unwrap().flex_grow = flex_grow; From 8d36740204c51afa0f34eb0e2719f10a1179dd32 Mon Sep 17 00:00:00 2001 From: Nico Burns Date: Fri, 24 Mar 2023 04:04:02 +0000 Subject: [PATCH 18/41] Add style getter/setter macros + add a few more methods --- bindings/wasm/src/lib.rs | 63 +++++++++++++++++++++++++++++++++------- 1 file changed, 53 insertions(+), 10 deletions(-) diff --git a/bindings/wasm/src/lib.rs b/bindings/wasm/src/lib.rs index 99dc1cfbd..a57060bdd 100644 --- a/bindings/wasm/src/lib.rs +++ b/bindings/wasm/src/lib.rs @@ -169,16 +169,6 @@ impl Node { self.childCount -= 1; } - #[wasm_bindgen(js_name = setFlexGrow)] - pub fn set_flex_grow(&mut self, flex_grow: f32) { - self.allocator.taffy.borrow_mut().style_mut(self.node).unwrap().flex_grow = flex_grow; - } - - #[wasm_bindgen(js_name = setHeight)] - pub fn set_height(&mut self, height: &str) { - self.allocator.taffy.borrow_mut().style_mut(self.node).unwrap().size.height = height.parse().unwrap(); - } - #[wasm_bindgen(js_name = markDirty)] pub fn mark_dirty(&mut self) { self.allocator.taffy.borrow_mut().mark_dirty(self.node).unwrap() @@ -211,6 +201,59 @@ impl Node { } } +macro_rules! get_style { + ($self:expr, $style_ident:ident, $block:expr) => {{ + let taffy = $self.allocator.taffy.borrow(); + let $style_ident = taffy.style($self.node)?; + Ok($block) + }}; +} + +macro_rules! with_style_mut { + ($self:expr, $style_ident:ident, $block:expr) => {{ + let mut taffy = $self.allocator.taffy.borrow_mut(); + let $style_ident = taffy.style_mut($self.node)?; + $block; + Ok(()) + }}; +} + + +// Style getter/setter methods +#[wasm_bindgen] +#[clippy::allow(non_snake_case)] +impl Node { + + // Display / Position + pub fn getDisplay(&mut self) -> Result { + get_style!(self, style, style.display) + } + pub fn setDisplay(&mut self, value: Display) -> Result<(), JsError> { + with_style_mut!(self, style, style.display = value) + } + pub fn getPosition(&mut self) -> Result { + get_style!(self, style, style.position) + } + pub fn setPosition(&mut self, value: Position) -> Result<(), JsError> { + with_style_mut!(self, style, style.position = value) + } + + // flex-grow + pub fn getFlexGrow(&mut self) -> Result { + get_style!(self, style, style.flex_grow) + } + pub fn setFlexGrow(&mut self, flex_grow: f32) -> Result<(), JsError> { + with_style_mut!(self, style, style.flex_grow = flex_grow) + } + + + #[wasm_bindgen(js_name = setHeight)] + pub fn set_height(&mut self, height: &str) -> Result<(), JsError> { + with_style_mut!(self, style, style.size.height = height.parse().unwrap()) + } +} + + fn parse_style(style: &JsValue) -> taffy::style::Style { taffy::style::Style { display: try_parse_from_i32(style, "display").unwrap_or_default(), From dc2dc0725624c549e53fe6a187909e48c81c2bf9 Mon Sep 17 00:00:00 2001 From: Nico Burns Date: Fri, 24 Mar 2023 04:04:27 +0000 Subject: [PATCH 19/41] wasm test: log to div rather than console + reduce warmup iterations --- bindings/wasm/wasm_test.html | 46 +++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/bindings/wasm/wasm_test.html b/bindings/wasm/wasm_test.html index f3464ac68..0a7f24386 100644 --- a/bindings/wasm/wasm_test.html +++ b/bindings/wasm/wasm_test.html @@ -5,14 +5,24 @@ +
+