use std::sync::{Arc, Mutex}; use dioxus_native_core::exports::shipyard::Component; use dioxus_native_core::layout_attributes::{ apply_layout_attributes_cfg, BorderWidths, LayoutConfigeration, }; use dioxus_native_core::node::OwnedAttributeView; use dioxus_native_core::node_ref::{AttributeMaskBuilder, NodeMaskBuilder, NodeView}; use dioxus_native_core::prelude::*; use dioxus_native_core_macro::partial_derive_state; use taffy::prelude::*; use crate::{screen_to_layout_space, unit_to_layout_space}; #[derive(Debug, Clone, Copy, PartialEq)] pub(crate) enum PossiblyUninitalized { Uninitalized, Initialized(T), } impl PossiblyUninitalized { pub fn unwrap(self) -> T { match self { Self::Initialized(i) => i, _ => panic!("uninitalized"), } } } impl Default for PossiblyUninitalized { fn default() -> Self { Self::Uninitalized } } #[derive(Clone, PartialEq, Default, Debug, Component)] pub(crate) struct TaffyLayout { pub style: Style, pub node: PossiblyUninitalized, } #[partial_derive_state] impl State for TaffyLayout { type ChildDependencies = (Self,); type ParentDependencies = (); type NodeDependencies = (); const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new() .with_attrs(AttributeMaskBuilder::Some(SORTED_LAYOUT_ATTRS)) .with_text(); // The layout state should be effected by the shadow dom const TRAVERSE_SHADOW_DOM: bool = true; fn update<'a>( &mut self, node_view: NodeView, _: ::ElementBorrowed<'a>, _: Option<::ElementBorrowed<'a>>, children: Vec<::ElementBorrowed<'a>>, ctx: &SendAnyMap, ) -> bool { let mut changed = false; let taffy: &Arc> = ctx.get().unwrap(); let mut taffy = taffy.lock().expect("poisoned taffy"); let mut style = Style::default(); if let Some(text) = node_view.text() { let char_len = text.chars().count(); style = Style { size: Size { // characters are 1 point tall height: Dimension::Points(screen_to_layout_space(1)), // text is as long as it is declared width: Dimension::Points(screen_to_layout_space(char_len as u16)), }, ..Default::default() }; if let PossiblyUninitalized::Initialized(n) = self.node { if self.style != style { taffy.set_style(n, style.clone()).unwrap(); } } else { self.node = PossiblyUninitalized::Initialized(taffy.new_leaf(style.clone()).unwrap()); changed = true; } } else { // gather up all the styles from the attribute list if let Some(attributes) = node_view.attributes() { for OwnedAttributeView { attribute, value, .. } in attributes { if value.as_custom().is_none() { apply_layout_attributes_cfg( &attribute.name, &value.to_string(), &mut style, &LayoutConfigeration { border_widths: BorderWidths { thin: 1.0, medium: 1.0, thick: 1.0, }, }, ); } } } // Set all direct nodes as our children let mut child_layout = vec![]; for (l,) in children { child_layout.push(l.node.unwrap()); } fn scale_dimension(d: Dimension) -> Dimension { match d { Dimension::Points(p) => Dimension::Points(unit_to_layout_space(p)), Dimension::Percent(p) => Dimension::Percent(p), Dimension::Auto => Dimension::Auto, } } fn scale_length_percentage_auto(d: LengthPercentageAuto) -> LengthPercentageAuto { match d { LengthPercentageAuto::Points(p) => { LengthPercentageAuto::Points(unit_to_layout_space(p)) } LengthPercentageAuto::Percent(p) => LengthPercentageAuto::Percent(p), LengthPercentageAuto::Auto => LengthPercentageAuto::Auto, } } fn scale_length_percentage(d: LengthPercentage) -> LengthPercentage { match d { LengthPercentage::Points(p) => { LengthPercentage::Points(unit_to_layout_space(p)) } LengthPercentage::Percent(p) => LengthPercentage::Percent(p), } } let scaled_style = Style { inset: Rect { left: scale_length_percentage_auto(style.inset.left), right: scale_length_percentage_auto(style.inset.right), top: scale_length_percentage_auto(style.inset.top), bottom: scale_length_percentage_auto(style.inset.bottom), }, margin: Rect { left: scale_length_percentage_auto(style.margin.left), right: scale_length_percentage_auto(style.margin.right), top: scale_length_percentage_auto(style.margin.top), bottom: scale_length_percentage_auto(style.margin.bottom), }, padding: Rect { left: scale_length_percentage(style.padding.left), right: scale_length_percentage(style.padding.right), top: scale_length_percentage(style.padding.top), bottom: scale_length_percentage(style.padding.bottom), }, border: Rect { left: scale_length_percentage(style.border.left), right: scale_length_percentage(style.border.right), top: scale_length_percentage(style.border.top), bottom: scale_length_percentage(style.border.bottom), }, gap: Size { width: scale_length_percentage(style.gap.width), height: scale_length_percentage(style.gap.height), }, flex_basis: scale_dimension(style.flex_basis), size: Size { width: scale_dimension(style.size.width), height: scale_dimension(style.size.height), }, min_size: Size { width: scale_dimension(style.min_size.width), height: scale_dimension(style.min_size.height), }, max_size: Size { width: scale_dimension(style.max_size.width), height: scale_dimension(style.max_size.height), }, ..style.clone() }; if let PossiblyUninitalized::Initialized(n) = self.node { if self.style != style { taffy.set_style(n, scaled_style).unwrap(); } if taffy.children(n).unwrap() != child_layout { taffy.set_children(n, &child_layout).unwrap(); } } else { self.node = PossiblyUninitalized::Initialized( taffy .new_with_children(scaled_style, &child_layout) .unwrap(), ); changed = true; } } if self.style != style { changed = true; self.style = style; } changed } fn create<'a>( node_view: NodeView<()>, node: ::ElementBorrowed<'a>, parent: Option<::ElementBorrowed<'a>>, children: Vec<::ElementBorrowed<'a>>, context: &SendAnyMap, ) -> Self { let mut myself = Self::default(); myself.update(node_view, node, parent, children, context); myself } } // these are the attributes in layout_attiributes in native-core const SORTED_LAYOUT_ATTRS: &[&str] = &[ "align-content", "align-items", "align-self", "animation", "animation-delay", "animation-direction", "animation-duration", "animation-fill-mode", "animation-iteration-count", "animation-name", "animation-play-state", "animation-timing-function", "backface-visibility", "border", "border-bottom", "border-bottom-color", "border-bottom-left-radius", "border-bottom-right-radius", "border-bottom-style", "border-bottom-width", "border-collapse", "border-color", "border-image", "border-image-outset", "border-image-repeat", "border-image-slice", "border-image-source", "border-image-width", "border-left", "border-left-color", "border-left-style", "border-left-width", "border-radius", "border-right", "border-right-color", "border-right-style", "border-right-width", "border-spacing", "border-style", "border-top", "border-top-color", "border-top-left-radius", "border-top-right-radius", "border-top-style", "border-top-width", "border-width", "bottom", "box-shadow", "box-sizing", "caption-side", "clear", "clip", "column-count", "column-fill", "column-gap", "column-rule", "column-rule-color", "column-rule-style", "column-rule-width", "column-span", "column-width", "columns", "content", "counter-increment", "counter-reset", "cursor", "direction", "ltr", "rtl", "display", "empty-cells", "flex", "flex-basis", "flex-direction", "flex-flow", "flex-grow", "flex-shrink", "flex-wrap", "float", "height", "justify-content", "flex-start", "flex-end", "center", "space-between", "space-around", "space-evenly", "left", "letter-spacing", "line-height", "list-style", "list-style-image", "list-style-position", "list-style-type", "margin", "margin-bottom", "margin-left", "margin-right", "margin-top", "max-height", "max-width", "min-height", "min-width", "opacity", "order", "outline", "outline-color", "outline-offset", "outline-style", "outline-width", "overflow", "overflow-x", "overflow-y", "padding", "padding-bottom", "padding-left", "padding-right", "padding-top", "page-break-after", "page-break-before", "page-break-inside", "perspective", "perspective-origin", "position", "static", "relative", "fixed", "absolute", "sticky", "pointer-events", "quotes", "resize", "right", "tab-size", "table-layout", "top", "transform", "transform-origin", "transform-style", "transition", "transition-delay", "transition-duration", "transition-property", "transition-timing-function", "vertical-align", "visibility", "white-space", "width", "word-break", "word-spacing", "word-wrap", "z-index", ];