Skip to main content

takia/tree/
takia_tree.rs

1//! Contains [TakiaTree](crate::tree::TakiaTree): the default implementation of [LayoutTree](crate::tree::LayoutTree), and the error type for Takia.
2#[cfg(not(feature = "std"))]
3use slotmap::SecondaryMap;
4#[cfg(feature = "std")]
5use slotmap::SparseSecondaryMap as SecondaryMap;
6use slotmap::{DefaultKey, SlotMap};
7
8#[cfg(feature = "block_layout")]
9use crate::block::BlockContext;
10use crate::geometry::Size;
11use crate::style::{AvailableSpace, Display, Style};
12use crate::sys::DefaultCheapStr;
13use crate::tree::{
14    Cache, ClearState, Layout, LayoutContainingBlock, LayoutInput, LayoutOutput, LayoutPartialTree, NodeId, PrintTree,
15    RoundTree, RunMode, TraversePartialTree, TraverseTree,
16};
17use crate::util::debug::{debug_log, debug_log_node};
18use crate::util::sys::{new_const_children_vec, new_vec_with_capacity, Box, ChildrenVec, Vec};
19
20use crate::compute::{
21    compute_cached_layout, compute_hidden_layout, compute_leaf_layout, compute_oof_layout, compute_root_layout,
22    round_layout,
23};
24use crate::CacheTree;
25
26#[cfg(feature = "block_layout")]
27use crate::{compute::compute_block_layout, LayoutBlockContainer};
28#[cfg(feature = "flexbox")]
29use crate::{compute::compute_flexbox_layout, LayoutFlexboxContainer};
30#[cfg(feature = "grid")]
31use crate::{compute::compute_grid_layout, LayoutGridContainer};
32
33#[cfg(feature = "grid")]
34use crate::compute::grid::DetailedGridInfo;
35use crate::tree::layout::DetailedLayoutInfo;
36
37/// The error Takia generates on invalid operations
38pub type TakiaResult<T> = Result<T, TakiaError>;
39
40/// An error that occurs while trying to access or modify a node's children by index.
41#[derive(Debug, Clone, PartialEq, Eq)]
42pub enum TakiaError {
43    /// The parent node does not have a child at `child_index`. It only has `child_count` children
44    ChildIndexOutOfBounds {
45        /// The parent node whose child was being looked up
46        parent: NodeId,
47        /// The index that was looked up
48        child_index: usize,
49        /// The total number of children the parent has
50        child_count: usize,
51    },
52    /// The parent node was not found in the [`TakiaTree`](crate::TakiaTree) instance.
53    InvalidParentNode(NodeId),
54    /// The child node was not found in the [`TakiaTree`](crate::TakiaTree) instance.
55    InvalidChildNode(NodeId),
56    /// The supplied node was not found in the [`TakiaTree`](crate::TakiaTree) instance.
57    InvalidInputNode(NodeId),
58}
59
60impl core::fmt::Display for TakiaError {
61    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
62        match self {
63            TakiaError::ChildIndexOutOfBounds { parent, child_index, child_count } => {
64                write!(f, "Index (is {child_index}) should be < child_count ({child_count}) for parent node {parent:?}")
65            }
66            TakiaError::InvalidParentNode(parent) => {
67                write!(f, "Parent Node {parent:?} is not in the TakiaTree instance")
68            }
69            TakiaError::InvalidChildNode(child) => write!(f, "Child Node {child:?} is not in the TakiaTree instance"),
70            TakiaError::InvalidInputNode(node) => write!(f, "Supplied Node {node:?} is not in the TakiaTree instance"),
71        }
72    }
73}
74
75#[cfg(feature = "std")]
76impl std::error::Error for TakiaError {}
77
78/// Global configuration values for a TakiaTree instance
79#[derive(Debug, Clone, Copy)]
80pub(crate) struct TakiaConfig {
81    /// Whether to round layout values
82    pub(crate) use_rounding: bool,
83}
84
85impl Default for TakiaConfig {
86    fn default() -> Self {
87        Self { use_rounding: true }
88    }
89}
90
91/// Layout information for a given [`Node`](crate::node::Node)
92///
93/// Stored in a [`TakiaTree`].
94#[derive(Debug, Clone, PartialEq)]
95struct NodeData {
96    /// The layout strategy used by this node
97    pub(crate) style: Style,
98
99    /// The always unrounded results of the layout computation. We must store this separately from the rounded
100    /// layout to avoid errors from rounding already-rounded values. See <https://github.com/DioxusLabs/takia/issues/501>.
101    pub(crate) unrounded_layout: Layout,
102
103    /// The final results of the layout computation.
104    /// These may be rounded or unrounded depending on what the `use_rounding` config setting is set to.
105    pub(crate) final_layout: Layout,
106
107    /// Whether the node has context data associated with it or not
108    pub(crate) has_context: bool,
109
110    /// Out-of-flow (absolute/fixed) boxes whose containing block is this node,
111    /// as recorded by the out-of-flow positioning pass
112    pub(crate) hoisted_children: ChildrenVec<NodeId>,
113
114    /// The cached results of the layout computation
115    pub(crate) cache: Cache,
116
117    /// The computation result from layout algorithm
118    pub(crate) detailed_layout_info: DetailedLayoutInfo,
119}
120
121impl NodeData {
122    /// Create the data for a new node
123    #[must_use]
124    pub const fn new(style: Style) -> Self {
125        Self {
126            style,
127            cache: Cache::new(),
128            unrounded_layout: Layout::new(),
129            final_layout: Layout::new(),
130            has_context: false,
131            hoisted_children: new_const_children_vec(),
132            detailed_layout_info: DetailedLayoutInfo::None,
133        }
134    }
135
136    /// Marks a node and all of its ancestors as requiring relayout
137    ///
138    /// This clears any cached data and signals that the data must be recomputed.
139    /// If the node was already marked as dirty, returns true
140    #[inline]
141    pub fn mark_dirty(&mut self) -> ClearState {
142        self.cache.clear()
143    }
144}
145
146/// An entire tree of UI nodes. The entry point to Takia's high-level API.
147///
148/// Allows you to build a tree of UI nodes, run Takia's layout algorithms over that tree, and then access the resultant layout.]
149#[derive(Debug, Clone)]
150pub struct TakiaTree<NodeContext = ()> {
151    /// The [`NodeData`] for each node stored in this tree
152    nodes: SlotMap<DefaultKey, NodeData>,
153
154    /// Functions/closures that compute the intrinsic size of leaf nodes
155    node_context_data: SecondaryMap<DefaultKey, NodeContext>,
156
157    /// The children of each node
158    ///
159    /// The indexes in the outer vector correspond to the position of the parent [`NodeData`]
160    children: SlotMap<DefaultKey, ChildrenVec<NodeId>>,
161
162    /// The parents of each node
163    ///
164    /// The indexes in the outer vector correspond to the position of the child [`NodeData`]
165    parents: SlotMap<DefaultKey, Option<NodeId>>,
166
167    /// Layout mode configuration
168    config: TakiaConfig,
169}
170
171impl Default for TakiaTree {
172    fn default() -> TakiaTree<()> {
173        TakiaTree::new()
174    }
175}
176
177/// Iterator that wraps a slice of nodes, lazily converting them to u64
178pub struct TakiaTreeChildIter<'a>(core::slice::Iter<'a, NodeId>);
179impl Iterator for TakiaTreeChildIter<'_> {
180    type Item = NodeId;
181
182    #[inline]
183    fn next(&mut self) -> Option<Self::Item> {
184        self.0.next().copied()
185    }
186}
187
188// TraversePartialTree impl for TakiaTree
189impl<NodeContext> TraversePartialTree for TakiaTree<NodeContext> {
190    type ChildIter<'a>
191        = TakiaTreeChildIter<'a>
192    where
193        Self: 'a;
194
195    #[inline(always)]
196    fn child_ids(&self, parent_node_id: NodeId) -> Self::ChildIter<'_> {
197        TakiaTreeChildIter(self.children[parent_node_id.into()].iter())
198    }
199
200    #[inline(always)]
201    fn child_count(&self, parent_node_id: NodeId) -> usize {
202        self.children[parent_node_id.into()].len()
203    }
204
205    #[inline(always)]
206    fn get_child_id(&self, parent_node_id: NodeId, id: usize) -> NodeId {
207        self.children[parent_node_id.into()][id]
208    }
209}
210
211// TraverseTree impl for TakiaTree
212impl<NodeContext> TraverseTree for TakiaTree<NodeContext> {}
213
214// CacheTree impl for TakiaTree
215impl<NodeContext> CacheTree for TakiaTree<NodeContext> {
216    fn cache_get(&mut self, node_id: NodeId, input: &LayoutInput) -> Option<LayoutOutput> {
217        self.nodes[node_id.into()].cache.get(input)
218    }
219
220    fn cache_store(&mut self, node_id: NodeId, input: &LayoutInput, layout_output: LayoutOutput) {
221        self.nodes[node_id.into()].cache.store(input, layout_output)
222    }
223
224    fn cache_clear(&mut self, node_id: NodeId) {
225        self.nodes[node_id.into()].cache.clear();
226    }
227}
228
229// PrintTree impl for TakiaTree
230impl<NodeContext> PrintTree for TakiaTree<NodeContext> {
231    #[inline(always)]
232    fn get_debug_label(&self, node_id: NodeId) -> &'static str {
233        let node = &self.nodes[node_id.into()];
234        let display = node.style.display;
235        let num_children = self.child_count(node_id);
236
237        match (num_children, display) {
238            (_, Display::None) => "NONE",
239            (0, _) => "LEAF",
240            #[cfg(feature = "block_layout")]
241            (_, Display::Block) => "BLOCK",
242            #[cfg(feature = "block_layout")]
243            (_, Display::FlowRoot) => "FLOW-ROOT",
244            #[cfg(feature = "flexbox")]
245            (_, Display::Flex) => {
246                use crate::FlexDirection;
247                match node.style.flex_direction {
248                    FlexDirection::Row | FlexDirection::RowReverse => "FLEX ROW",
249                    FlexDirection::Column | FlexDirection::ColumnReverse => "FLEX COL",
250                }
251            }
252            #[cfg(feature = "grid")]
253            (_, Display::Grid) => "GRID",
254        }
255    }
256
257    #[inline(always)]
258    fn get_final_layout(&self, node_id: NodeId) -> Layout {
259        if self.config.use_rounding {
260            self.nodes[node_id.into()].final_layout
261        } else {
262            self.nodes[node_id.into()].unrounded_layout
263        }
264    }
265}
266
267/// View over the Takia tree that holds the tree itself along with a reference to the context
268/// and implements LayoutTree. This allows the context to be stored outside of the TakiaTree struct
269/// which makes the lifetimes of the context much more flexible.
270pub(crate) struct TakiaView<'t, NodeContext, MeasureFunction>
271where
272    MeasureFunction: FnMut(LayoutInput, NodeId, Option<&mut NodeContext>, &Style) -> LayoutOutput,
273{
274    /// A reference to the TakiaTree
275    pub(crate) takia: &'t mut TakiaTree<NodeContext>,
276    /// The context provided for passing to measure functions if layout is run over this struct
277    pub(crate) measure_function: MeasureFunction,
278}
279
280impl<NodeContext, MeasureFunction> TakiaView<'_, NodeContext, MeasureFunction>
281where
282    MeasureFunction: FnMut(LayoutInput, NodeId, Option<&mut NodeContext>, &Style) -> LayoutOutput,
283{
284    #[inline(always)]
285    /// Unified implementation that both `LayoutPartialTree::compute_child_layout`
286    /// and `LayoutBlockContainer::compute_block_child_layout` delegate to.
287    fn compute_child_layout(
288        &mut self,
289        node_id: NodeId,
290        inputs: LayoutInput,
291        #[cfg(feature = "block_layout")] block_ctx: Option<&mut BlockContext<'_>>,
292    ) -> LayoutOutput {
293        // If RunMode is PerformHiddenLayout then this indicates that an ancestor node is `Display::None`
294        // and thus that we should lay out this node using hidden layout regardless of it's own display style.
295        if inputs.run_mode == RunMode::PerformHiddenLayout {
296            debug_log!("HIDDEN");
297            // `round_layout` reaches out-of-flow boxes only through their containing block's
298            // hoisted child list, and a box inside a hidden subtree is in no such list. Write the
299            // final layout here so that it does not keep the layout from an earlier visible pass.
300            self.takia.nodes[node_id.into()].final_layout = Layout::with_order(0);
301            return compute_hidden_layout(self, node_id);
302        }
303
304        // We run the following wrapped in "compute_cached_layout", which will check the cache for an entry matching the node and inputs and:
305        //   - Return that entry if exists
306        //   - Else call the passed closure (below) to compute the result
307        //
308        // If there was no cache match and a new result needs to be computed then that result will be added to the cache
309        compute_cached_layout(self, node_id, inputs, |tree, node_id, inputs| {
310            let display_mode = tree.takia.nodes[node_id.into()].style.display;
311            let has_children = tree.child_count(node_id) > 0;
312
313            debug_log!(display_mode);
314            debug_log_node!(inputs);
315
316            // Dispatch to a layout algorithm based on the node's display style and whether the node has children or not.
317            let mut output = match (display_mode, has_children) {
318                (Display::None, _) => compute_hidden_layout(tree, node_id),
319                #[cfg(feature = "block_layout")]
320                (Display::Block, true) => compute_block_layout(tree, node_id, inputs, block_ctx),
321                #[cfg(feature = "block_layout")]
322                (Display::FlowRoot, true) => compute_block_layout(tree, node_id, inputs, None),
323                #[cfg(feature = "flexbox")]
324                (Display::Flex, true) => compute_flexbox_layout(tree, node_id, inputs),
325                #[cfg(feature = "grid")]
326                (Display::Grid, true) => compute_grid_layout(tree, node_id, inputs),
327                (_, false) => {
328                    let node_key = node_id.into();
329                    let style = &tree.takia.nodes[node_key].style;
330                    let has_context = tree.takia.nodes[node_key].has_context;
331                    let node_context = has_context.then(|| tree.takia.node_context_data.get_mut(node_key)).flatten();
332                    (tree.measure_function)(inputs, node_id, node_context, style)
333                }
334            };
335
336            // Lay out any out-of-flow candidates for which this node is the containing block.
337            // The rest bubble up via `output.oof_candidates`. This runs inside the cache-miss
338            // closure so that cached outputs already contain the processed candidate list.
339            // Only full layout passes run it: measure passes must not write hoisted box layouts
340            // (which would not be rewritten if the final layout pass is a cache hit).
341            if inputs.run_mode == RunMode::PerformLayout {
342                compute_oof_layout(tree, node_id, &mut output);
343            }
344
345            output
346        })
347    }
348}
349
350// TraversePartialTree impl for TakiaView
351impl<NodeContext, MeasureFunction> TraversePartialTree for TakiaView<'_, NodeContext, MeasureFunction>
352where
353    MeasureFunction: FnMut(LayoutInput, NodeId, Option<&mut NodeContext>, &Style) -> LayoutOutput,
354{
355    type ChildIter<'a>
356        = TakiaTreeChildIter<'a>
357    where
358        Self: 'a;
359
360    #[inline(always)]
361    fn child_ids(&self, parent_node_id: NodeId) -> Self::ChildIter<'_> {
362        self.takia.child_ids(parent_node_id)
363    }
364
365    #[inline(always)]
366    fn child_count(&self, parent_node_id: NodeId) -> usize {
367        self.takia.child_count(parent_node_id)
368    }
369
370    #[inline(always)]
371    fn get_child_id(&self, parent_node_id: NodeId, child_index: usize) -> NodeId {
372        self.takia.get_child_id(parent_node_id, child_index)
373    }
374}
375
376// TraverseTree impl for TakiaView
377impl<NodeContext, MeasureFunction> TraverseTree for TakiaView<'_, NodeContext, MeasureFunction> where
378    MeasureFunction: FnMut(LayoutInput, NodeId, Option<&mut NodeContext>, &Style) -> LayoutOutput
379{
380}
381
382// LayoutPartialTree impl for TakiaView
383impl<NodeContext, MeasureFunction> LayoutPartialTree for TakiaView<'_, NodeContext, MeasureFunction>
384where
385    MeasureFunction: FnMut(LayoutInput, NodeId, Option<&mut NodeContext>, &Style) -> LayoutOutput,
386{
387    type CoreContainerStyle<'a>
388        = &'a Style
389    where
390        Self: 'a;
391
392    type CustomIdent = DefaultCheapStr;
393
394    #[inline(always)]
395    fn get_core_container_style(&self, node_id: NodeId) -> Self::CoreContainerStyle<'_> {
396        &self.takia.nodes[node_id.into()].style
397    }
398
399    #[inline(always)]
400    fn set_unrounded_layout(&mut self, node_id: NodeId, layout: &Layout) {
401        self.takia.nodes[node_id.into()].unrounded_layout = *layout;
402    }
403
404    #[inline(always)]
405    fn resolve_calc_value(&self, _val: *const (), _basis: f32) -> f32 {
406        0.0
407    }
408
409    #[inline(always)]
410    fn compute_child_layout(&mut self, node_id: NodeId, inputs: LayoutInput) -> LayoutOutput {
411        self.compute_child_layout(
412            node_id,
413            inputs,
414            #[cfg(feature = "block_layout")]
415            None,
416        )
417    }
418}
419
420impl<NodeContext, MeasureFunction> LayoutContainingBlock for TakiaView<'_, NodeContext, MeasureFunction>
421where
422    MeasureFunction: FnMut(LayoutInput, NodeId, Option<&mut NodeContext>, &Style) -> LayoutOutput,
423{
424    type OofItemStyle<'a>
425        = &'a Style
426    where
427        Self: 'a;
428
429    #[inline(always)]
430    fn get_oof_item_style(&self, node_id: NodeId) -> Self::OofItemStyle<'_> {
431        &self.takia.nodes[node_id.into()].style
432    }
433
434    #[inline(always)]
435    fn clear_hoisted_children(&mut self, node_id: NodeId) {
436        self.takia.nodes[node_id.into()].hoisted_children.clear();
437    }
438
439    #[inline(always)]
440    fn add_hoisted_children(&mut self, node_id: NodeId, hoisted: &[NodeId]) {
441        self.takia.nodes[node_id.into()].hoisted_children.extend_from_slice(hoisted);
442    }
443
444    #[inline(always)]
445    fn get_detailed_layout_info(&self, node_id: NodeId) -> &DetailedLayoutInfo {
446        &self.takia.nodes[node_id.into()].detailed_layout_info
447    }
448}
449
450impl<NodeContext, MeasureFunction> CacheTree for TakiaView<'_, NodeContext, MeasureFunction>
451where
452    MeasureFunction: FnMut(LayoutInput, NodeId, Option<&mut NodeContext>, &Style) -> LayoutOutput,
453{
454    fn cache_get(&mut self, node_id: NodeId, input: &LayoutInput) -> Option<LayoutOutput> {
455        self.takia.nodes[node_id.into()].cache.get(input)
456    }
457
458    fn cache_store(&mut self, node_id: NodeId, input: &LayoutInput, layout_output: LayoutOutput) {
459        self.takia.nodes[node_id.into()].cache.store(input, layout_output)
460    }
461
462    fn cache_clear(&mut self, node_id: NodeId) {
463        self.takia.nodes[node_id.into()].cache.clear();
464    }
465}
466
467#[cfg(feature = "block_layout")]
468impl<NodeContext, MeasureFunction> LayoutBlockContainer for TakiaView<'_, NodeContext, MeasureFunction>
469where
470    MeasureFunction: FnMut(LayoutInput, NodeId, Option<&mut NodeContext>, &Style) -> LayoutOutput,
471{
472    type BlockContainerStyle<'a>
473        = &'a Style
474    where
475        Self: 'a;
476    type BlockItemStyle<'a>
477        = &'a Style
478    where
479        Self: 'a;
480
481    #[inline(always)]
482    fn get_block_container_style(&self, node_id: NodeId) -> Self::BlockContainerStyle<'_> {
483        self.get_core_container_style(node_id)
484    }
485
486    #[inline(always)]
487    fn get_block_child_style(&self, child_node_id: NodeId) -> Self::BlockItemStyle<'_> {
488        self.get_core_container_style(child_node_id)
489    }
490
491    #[inline(always)]
492    fn compute_block_child_layout(
493        &mut self,
494        node_id: NodeId,
495        inputs: LayoutInput,
496        block_ctx: Option<&mut BlockContext<'_>>,
497    ) -> LayoutOutput {
498        self.compute_child_layout(node_id, inputs, block_ctx)
499    }
500}
501
502#[cfg(feature = "flexbox")]
503impl<NodeContext, MeasureFunction> LayoutFlexboxContainer for TakiaView<'_, NodeContext, MeasureFunction>
504where
505    MeasureFunction: FnMut(LayoutInput, NodeId, Option<&mut NodeContext>, &Style) -> LayoutOutput,
506{
507    type FlexboxContainerStyle<'a>
508        = &'a Style
509    where
510        Self: 'a;
511    type FlexboxItemStyle<'a>
512        = &'a Style
513    where
514        Self: 'a;
515
516    #[inline(always)]
517    fn get_flexbox_container_style(&self, node_id: NodeId) -> Self::FlexboxContainerStyle<'_> {
518        &self.takia.nodes[node_id.into()].style
519    }
520
521    #[inline(always)]
522    fn get_flexbox_child_style(&self, child_node_id: NodeId) -> Self::FlexboxItemStyle<'_> {
523        &self.takia.nodes[child_node_id.into()].style
524    }
525}
526
527#[cfg(feature = "grid")]
528impl<NodeContext, MeasureFunction> LayoutGridContainer for TakiaView<'_, NodeContext, MeasureFunction>
529where
530    MeasureFunction: FnMut(LayoutInput, NodeId, Option<&mut NodeContext>, &Style) -> LayoutOutput,
531{
532    type GridContainerStyle<'a>
533        = &'a Style
534    where
535        Self: 'a;
536    type GridItemStyle<'a>
537        = &'a Style
538    where
539        Self: 'a;
540
541    #[inline(always)]
542    fn get_grid_container_style(&self, node_id: NodeId) -> Self::GridContainerStyle<'_> {
543        &self.takia.nodes[node_id.into()].style
544    }
545
546    #[inline(always)]
547    fn get_grid_child_style(&self, child_node_id: NodeId) -> Self::GridItemStyle<'_> {
548        &self.takia.nodes[child_node_id.into()].style
549    }
550
551    #[inline(always)]
552    fn set_detailed_grid_info(&mut self, node_id: NodeId, detailed_grid_info: DetailedGridInfo) {
553        self.takia.nodes[node_id.into()].detailed_layout_info = DetailedLayoutInfo::Grid(Box::new(detailed_grid_info));
554    }
555}
556
557// RoundTree impl for TakiaView
558impl<NodeContext, MeasureFunction> RoundTree for TakiaView<'_, NodeContext, MeasureFunction>
559where
560    MeasureFunction: FnMut(LayoutInput, NodeId, Option<&mut NodeContext>, &Style) -> LayoutOutput,
561{
562    #[inline(always)]
563    fn get_unrounded_layout(&self, node: NodeId) -> Layout {
564        self.takia.nodes[node.into()].unrounded_layout
565    }
566
567    #[inline(always)]
568    fn set_final_layout(&mut self, node_id: NodeId, layout: &Layout) {
569        self.takia.nodes[node_id.into()].final_layout = *layout;
570    }
571
572    #[inline(always)]
573    fn is_out_of_flow(&self, node_id: NodeId) -> bool {
574        let node = &self.takia.nodes[node_id.into()];
575        node.style.position.is_out_of_flow() && node.style.display != crate::style::Display::None
576    }
577
578    #[inline(always)]
579    fn hoisted_child_count(&self, node_id: NodeId) -> usize {
580        self.takia.nodes[node_id.into()].hoisted_children.len()
581    }
582
583    #[inline(always)]
584    fn get_hoisted_child_id(&self, node_id: NodeId, index: usize) -> NodeId {
585        self.takia.nodes[node_id.into()].hoisted_children[index]
586    }
587}
588
589#[allow(clippy::iter_cloned_collect)] // due to no-std support, we need to use `iter_cloned` instead of `collect`
590impl<NodeContext> TakiaTree<NodeContext> {
591    /// Creates a new [`TakiaTree`]
592    ///
593    /// The default capacity of a [`TakiaTree`] is 16 nodes.
594    #[must_use]
595    pub fn new() -> Self {
596        Self::with_capacity(16)
597    }
598
599    /// Creates a new [`TakiaTree`] that can store `capacity` nodes before reallocation
600    #[must_use]
601    pub fn with_capacity(capacity: usize) -> Self {
602        TakiaTree {
603            // TODO: make this method const upstream,
604            // so constructors here can be const
605            nodes: SlotMap::with_capacity(capacity),
606            children: SlotMap::with_capacity(capacity),
607            parents: SlotMap::with_capacity(capacity),
608            node_context_data: SecondaryMap::with_capacity(capacity),
609            config: TakiaConfig::default(),
610        }
611    }
612
613    /// Enable rounding of layout values. Rounding is enabled by default.
614    pub fn enable_rounding(&mut self) {
615        self.config.use_rounding = true;
616    }
617
618    /// Disable rounding of layout values. Rounding is enabled by default.
619    pub fn disable_rounding(&mut self) {
620        self.config.use_rounding = false;
621    }
622
623    /// Creates and adds a new unattached leaf node to the tree, and returns the node of the new node
624    pub fn new_leaf(&mut self, layout: Style) -> TakiaResult<NodeId> {
625        let id = self.nodes.insert(NodeData::new(layout));
626        let _ = self.children.insert(new_vec_with_capacity(0));
627        let _ = self.parents.insert(None);
628
629        Ok(id.into())
630    }
631
632    /// Creates and adds a new unattached leaf node to the tree, and returns the [`NodeId`] of the new node
633    ///
634    /// Creates and adds a new leaf node with a supplied context
635    pub fn new_leaf_with_context(&mut self, layout: Style, context: NodeContext) -> TakiaResult<NodeId> {
636        let mut data = NodeData::new(layout);
637        data.has_context = true;
638
639        let id = self.nodes.insert(data);
640        self.node_context_data.insert(id, context);
641
642        let _ = self.children.insert(new_vec_with_capacity(0));
643        let _ = self.parents.insert(None);
644
645        Ok(id.into())
646    }
647
648    /// Creates and adds a new node, which may have any number of `children`
649    pub fn new_with_children(&mut self, layout: Style, children: &[NodeId]) -> TakiaResult<NodeId> {
650        let id = NodeId::from(self.nodes.insert(NodeData::new(layout)));
651
652        for child in children {
653            self.parents[(*child).into()] = Some(id);
654        }
655
656        let _ = self.children.insert(children.iter().copied().collect::<_>());
657        let _ = self.parents.insert(None);
658
659        Ok(id)
660    }
661
662    /// Drops all nodes in the tree
663    pub fn clear(&mut self) {
664        self.nodes.clear();
665        self.children.clear();
666        self.parents.clear();
667        // A context belongs to the node that owns it. Leaving it behind keeps arbitrary
668        // user data -- for a measure function, a boxed closure and everything it
669        // captures -- alive after the node it belonged to is gone.
670        self.node_context_data.clear();
671    }
672
673    /// Remove a specific node from the tree and drop it
674    ///
675    /// Returns the id of the node removed.
676    pub fn remove(&mut self, node: NodeId) -> TakiaResult<NodeId> {
677        let key = node.into();
678        if let Some(parent) = self.parents[key] {
679            if let Some(children) = self.children.get_mut(parent.into()) {
680                children.retain(|f| *f != node);
681            }
682            self.mark_dirty(parent)?;
683        }
684
685        // Remove "parent" references to a node when removing that node
686        if let Some(children) = self.children.get(key) {
687            for child in children.iter().copied() {
688                self.parents[child.into()] = None;
689            }
690        }
691
692        let _ = self.children.remove(key);
693        let _ = self.parents.remove(key);
694        let _ = self.nodes.remove(key);
695        let _ = self.node_context_data.remove(key);
696
697        Ok(node)
698    }
699
700    /// Sets the context data associated with the node
701    #[inline]
702    pub fn set_node_context(&mut self, node: NodeId, measure: Option<NodeContext>) -> TakiaResult<()> {
703        let key = node.into();
704        if let Some(measure) = measure {
705            self.nodes[key].has_context = true;
706            self.node_context_data.insert(key, measure);
707        } else {
708            self.nodes[key].has_context = false;
709            self.node_context_data.remove(key);
710        }
711
712        self.mark_dirty(node)?;
713
714        Ok(())
715    }
716
717    /// Gets a reference to the the context data associated with the node
718    #[inline]
719    pub fn get_node_context(&self, node: NodeId) -> Option<&NodeContext> {
720        self.node_context_data.get(node.into())
721    }
722
723    /// Gets a mutable reference to the the context data associated with the node
724    #[inline]
725    pub fn get_node_context_mut(&mut self, node: NodeId) -> Option<&mut NodeContext> {
726        self.node_context_data.get_mut(node.into())
727    }
728
729    /// Gets mutable references to the the context data associated with the nodes. All keys must be valid and disjoint, otherwise None is returned.
730    pub fn get_disjoint_node_context_mut<const N: usize>(
731        &mut self,
732        keys: [NodeId; N],
733    ) -> Option<[&mut NodeContext; N]> {
734        self.node_context_data.get_disjoint_mut(keys.map(|k| k.into()))
735    }
736
737    /// Adds a `child` node under the supplied `parent`
738    pub fn add_child(&mut self, parent: NodeId, child: NodeId) -> TakiaResult<()> {
739        let parent_key = parent.into();
740        let child_key = child.into();
741        self.parents[child_key] = Some(parent);
742        self.children[parent_key].push(child);
743        self.mark_dirty(parent)?;
744
745        Ok(())
746    }
747
748    /// Inserts a `child` node at the given `child_index` under the supplied `parent`, shifting all children after it to the right.
749    pub fn insert_child_at_index(&mut self, parent: NodeId, child_index: usize, child: NodeId) -> TakiaResult<()> {
750        let parent_key = parent.into();
751
752        let child_count = self.children[parent_key].len();
753        if child_index > child_count {
754            return Err(TakiaError::ChildIndexOutOfBounds { parent, child_index, child_count });
755        }
756
757        self.parents[child.into()] = Some(parent);
758        self.children[parent_key].insert(child_index, child);
759        self.mark_dirty(parent)?;
760
761        Ok(())
762    }
763
764    /// Directly sets the `children` of the supplied `parent`
765    pub fn set_children(&mut self, parent: NodeId, children: &[NodeId]) -> TakiaResult<()> {
766        let parent_key = parent.into();
767
768        // Remove node as parent from all its current children.
769        for child in &self.children[parent_key] {
770            self.parents[(*child).into()] = None;
771        }
772
773        // Build up relation node <-> child
774        for &child in children {
775            // Remove child from previous parent
776            if let Some(previous_parent) = self.parents[child.into()] {
777                self.remove_child(previous_parent, child).unwrap();
778            }
779            self.parents[child.into()] = Some(parent);
780        }
781
782        let parent_children = &mut self.children[parent_key];
783        parent_children.clear();
784        children.iter().for_each(|child| parent_children.push(*child));
785
786        self.mark_dirty(parent)?;
787
788        Ok(())
789    }
790
791    /// Removes the `child` of the parent `node`
792    ///
793    /// The child is not removed from the tree entirely, it is simply no longer attached to its previous parent.
794    pub fn remove_child(&mut self, parent: NodeId, child: NodeId) -> TakiaResult<NodeId> {
795        let index = self.children[parent.into()].iter().position(|n| *n == child).unwrap();
796        self.remove_child_at_index(parent, index)
797    }
798
799    /// Removes the child at the given `index` from the `parent`
800    ///
801    /// The child is not removed from the tree entirely, it is simply no longer attached to its previous parent.
802    pub fn remove_child_at_index(&mut self, parent: NodeId, child_index: usize) -> TakiaResult<NodeId> {
803        let parent_key = parent.into();
804        let child_count = self.children[parent_key].len();
805        if child_index >= child_count {
806            return Err(TakiaError::ChildIndexOutOfBounds { parent, child_index, child_count });
807        }
808
809        let child = self.children[parent_key].remove(child_index);
810        self.parents[child.into()] = None;
811
812        self.mark_dirty(parent)?;
813
814        Ok(child)
815    }
816
817    /// Removes children at the given range from the `parent`
818    ///
819    /// Children are not removed from the tree entirely, they are simply no longer attached to their previous parent.
820    ///
821    /// Function will panic if given range is invalid. See [`core::slice::range`]
822    pub fn remove_children_range<R>(&mut self, parent: NodeId, range: R) -> TakiaResult<()>
823    where
824        R: core::ops::RangeBounds<usize>,
825    {
826        let parent_key = parent.into();
827        for child in self.children[parent_key].drain(range) {
828            self.parents[child.into()] = None;
829        }
830
831        self.mark_dirty(parent)?;
832        Ok(())
833    }
834
835    /// Replaces the child at the given `child_index` from the `parent` node with the new `child` node
836    ///
837    /// The child is not removed from the tree entirely, it is simply no longer attached to its previous parent.
838    pub fn replace_child_at_index(
839        &mut self,
840        parent: NodeId,
841        child_index: usize,
842        new_child: NodeId,
843    ) -> TakiaResult<NodeId> {
844        let parent_key = parent.into();
845
846        let child_count = self.children[parent_key].len();
847        if child_index >= child_count {
848            return Err(TakiaError::ChildIndexOutOfBounds { parent, child_index, child_count });
849        }
850
851        self.parents[new_child.into()] = Some(parent);
852        let old_child = core::mem::replace(&mut self.children[parent_key][child_index], new_child);
853        self.parents[old_child.into()] = None;
854
855        self.mark_dirty(parent)?;
856
857        Ok(old_child)
858    }
859
860    /// Returns the child node of the parent `node` at the provided `child_index`
861    #[inline]
862    pub fn child_at_index(&self, parent: NodeId, child_index: usize) -> TakiaResult<NodeId> {
863        let parent_key = parent.into();
864        let child_count = self.children[parent_key].len();
865        if child_index >= child_count {
866            return Err(TakiaError::ChildIndexOutOfBounds { parent, child_index, child_count });
867        }
868
869        Ok(self.children[parent_key][child_index])
870    }
871
872    /// Returns the total number of nodes in the tree
873    #[inline]
874    pub fn total_node_count(&self) -> usize {
875        self.nodes.len()
876    }
877
878    /// Returns the `NodeId` of the parent node of the specified node (if it exists)
879    ///
880    /// - Return None if the specified node has no parent
881    /// - Panics if the specified node does not exist
882    #[inline]
883    pub fn parent(&self, child_id: NodeId) -> Option<NodeId> {
884        self.parents[child_id.into()]
885    }
886
887    /// Returns a list of children that belong to the parent node
888    pub fn children(&self, parent: NodeId) -> TakiaResult<Vec<NodeId>> {
889        Ok(self.children[parent.into()].clone())
890    }
891
892    /// Returns the out-of-flow (absolute/fixed) boxes whose containing block is `node`, as
893    /// recorded by the last layout. These boxes are laid out by `node` rather than by their
894    /// parent, and their [`Layout::location`] is relative to `node`.
895    pub fn hoisted_children(&self, node: NodeId) -> TakiaResult<&[NodeId]> {
896        Ok(&self.nodes[node.into()].hoisted_children)
897    }
898
899    /// Sets the [`Style`] of the provided `node`
900    #[inline]
901    pub fn set_style(&mut self, node: NodeId, style: Style) -> TakiaResult<()> {
902        self.nodes[node.into()].style = style;
903        self.mark_dirty(node)?;
904        Ok(())
905    }
906
907    /// Gets the [`Style`] of the provided `node`
908    #[inline]
909    pub fn style(&self, node: NodeId) -> TakiaResult<&Style> {
910        Ok(&self.nodes[node.into()].style)
911    }
912
913    /// Return this node layout relative to its parent
914    #[inline]
915    pub fn layout(&self, node: NodeId) -> TakiaResult<&Layout> {
916        if self.config.use_rounding {
917            Ok(&self.nodes[node.into()].final_layout)
918        } else {
919            Ok(&self.nodes[node.into()].unrounded_layout)
920        }
921    }
922
923    /// Returns this node layout with unrounded values relative to its parent.
924    #[inline]
925    pub fn unrounded_layout(&self, node: NodeId) -> &Layout {
926        &self.nodes[node.into()].unrounded_layout
927    }
928
929    /// Get the "detailed layout info" for a node.
930    ///
931    /// Currently this is only implemented for CSS Grid containers where it contains
932    /// the computed size of each grid track and the computed placement of each grid item
933    #[inline]
934    pub fn detailed_layout_info(&self, node_id: NodeId) -> &DetailedLayoutInfo {
935        &self.nodes[node_id.into()].detailed_layout_info
936    }
937
938    /// Marks the layout of this node and its ancestors as outdated
939    pub fn mark_dirty(&mut self, node: NodeId) -> TakiaResult<()> {
940        fn mark_dirty_recursive(
941            nodes: &mut SlotMap<DefaultKey, NodeData>,
942            parents: &SlotMap<DefaultKey, Option<NodeId>>,
943            node_key: DefaultKey,
944        ) {
945            match nodes[node_key].mark_dirty() {
946                ClearState::AlreadyEmpty => {
947                    // Node was already marked as dirty.
948                    // No need to visit ancestors
949                    // as they should be marked as dirty already.
950                }
951                ClearState::Cleared => {
952                    if let Some(Some(node)) = parents.get(node_key) {
953                        mark_dirty_recursive(nodes, parents, (*node).into());
954                    }
955                }
956            }
957        }
958
959        mark_dirty_recursive(&mut self.nodes, &self.parents, node.into());
960
961        Ok(())
962    }
963
964    /// Indicates whether the layout of this node needs to be recomputed
965    #[inline]
966    pub fn dirty(&self, node: NodeId) -> TakiaResult<bool> {
967        Ok(self.nodes[node.into()].cache.is_empty())
968    }
969
970    /// Updates the stored layout of the provided `node` and its children
971    pub fn compute_layout_with_measure<MeasureFunction>(
972        &mut self,
973        node_id: NodeId,
974        available_space: Size<AvailableSpace>,
975        measure_function: MeasureFunction,
976    ) -> Result<(), TakiaError>
977    where
978        MeasureFunction: FnMut(LayoutInput, NodeId, Option<&mut NodeContext>, &Style) -> LayoutOutput,
979    {
980        let use_rounding = self.config.use_rounding;
981        let mut takia_view = TakiaView { takia: self, measure_function };
982        compute_root_layout(&mut takia_view, node_id, available_space);
983        if use_rounding {
984            round_layout(&mut takia_view, node_id);
985        }
986        Ok(())
987    }
988
989    /// Updates the stored layout of the provided `node` and its children
990    pub fn compute_layout(&mut self, node: NodeId, available_space: Size<AvailableSpace>) -> Result<(), TakiaError> {
991        self.compute_layout_with_measure(node, available_space, |inputs, _, _, style| {
992            compute_leaf_layout(inputs, style, |_, _| 0.0, |_, _| Size::ZERO)
993        })
994    }
995
996    /// Prints a debug representation of the tree's layout
997    #[cfg(feature = "std")]
998    pub fn print_tree(&mut self, root: NodeId) {
999        crate::util::print_tree(self, root)
1000    }
1001
1002    /// Returns an instance of LayoutTree representing the TakiaTree
1003    #[cfg(test)]
1004    pub(crate) fn as_layout_tree(&mut self) -> impl LayoutPartialTree + CacheTree + '_ {
1005        TakiaView {
1006            takia: self,
1007            measure_function: |inputs, _, _, style| compute_leaf_layout(inputs, style, |_, _| 0.0, |_, _| Size::ZERO),
1008        }
1009    }
1010}
1011
1012#[cfg(test)]
1013mod tests {
1014
1015    use super::*;
1016    use crate::style::{Dimension, Display, FlexDirection};
1017    use crate::style_helpers::*;
1018    use crate::util::sys;
1019
1020    fn size_measure_function(
1021        inputs: LayoutInput,
1022        _node_id: NodeId,
1023        node_context: Option<&mut Size<f32>>,
1024        style: &Style,
1025    ) -> LayoutOutput {
1026        compute_leaf_layout(
1027            inputs,
1028            style,
1029            |_, _| 0.0,
1030            |known_dimensions, _available_space| {
1031                known_dimensions.unwrap_or(node_context.cloned().unwrap_or(Size::ZERO))
1032            },
1033        )
1034    }
1035
1036    #[test]
1037    fn new_should_allocate_default_capacity() {
1038        const DEFAULT_CAPACITY: usize = 16; // This is the capacity defined in the `impl Default`
1039        let takia: TakiaTree<()> = TakiaTree::new();
1040
1041        assert!(takia.children.capacity() >= DEFAULT_CAPACITY);
1042        assert!(takia.parents.capacity() >= DEFAULT_CAPACITY);
1043        assert!(takia.nodes.capacity() >= DEFAULT_CAPACITY);
1044    }
1045
1046    #[test]
1047    fn test_with_capacity() {
1048        const CAPACITY: usize = 8;
1049        let takia: TakiaTree<()> = TakiaTree::with_capacity(CAPACITY);
1050
1051        assert!(takia.children.capacity() >= CAPACITY);
1052        assert!(takia.parents.capacity() >= CAPACITY);
1053        assert!(takia.nodes.capacity() >= CAPACITY);
1054    }
1055
1056    #[test]
1057    fn test_new_leaf() {
1058        let mut takia: TakiaTree<()> = TakiaTree::new();
1059
1060        let res = takia.new_leaf(Style::default());
1061        assert!(res.is_ok());
1062        let node = res.unwrap();
1063
1064        // node should be in the takia tree and have no children
1065        assert!(takia.child_count(node) == 0);
1066    }
1067
1068    #[test]
1069    fn new_leaf_with_context() {
1070        let mut takia: TakiaTree<Size<f32>> = TakiaTree::new();
1071
1072        let res = takia.new_leaf_with_context(Style::default(), Size::ZERO);
1073        assert!(res.is_ok());
1074        let node = res.unwrap();
1075
1076        // node should be in the takia tree and have no children
1077        assert!(takia.child_count(node) == 0);
1078    }
1079
1080    /// Test that new_with_children works as expected
1081    #[test]
1082    fn test_new_with_children() {
1083        let mut takia: TakiaTree<()> = TakiaTree::new();
1084        let child0 = takia.new_leaf(Style::default()).unwrap();
1085        let child1 = takia.new_leaf(Style::default()).unwrap();
1086        let node = takia.new_with_children(Style::default(), &[child0, child1]).unwrap();
1087
1088        // node should have two children
1089        assert_eq!(takia.child_count(node), 2);
1090        assert_eq!(takia.children(node).unwrap()[0], child0);
1091        assert_eq!(takia.children(node).unwrap()[1], child1);
1092    }
1093
1094    #[test]
1095    fn remove_node_should_remove() {
1096        let mut takia: TakiaTree<()> = TakiaTree::new();
1097
1098        let node = takia.new_leaf(Style::default()).unwrap();
1099
1100        let _ = takia.remove(node).unwrap();
1101    }
1102
1103    #[test]
1104    fn remove_node_should_detach_hierarchy() {
1105        let mut takia: TakiaTree<()> = TakiaTree::new();
1106
1107        // Build a linear tree layout: <0> <- <1> <- <2>
1108        let node2 = takia.new_leaf(Style::default()).unwrap();
1109        let node1 = takia.new_with_children(Style::default(), &[node2]).unwrap();
1110        let node0 = takia.new_with_children(Style::default(), &[node1]).unwrap();
1111
1112        // Both node0 and node1 should have 1 child nodes
1113        assert_eq!(takia.children(node0).unwrap().as_slice(), &[node1]);
1114        assert_eq!(takia.children(node1).unwrap().as_slice(), &[node2]);
1115
1116        // Disconnect the tree: <0> <2>
1117        let _ = takia.remove(node1).unwrap();
1118
1119        // Both remaining nodes should have no child nodes
1120        assert!(takia.children(node0).unwrap().is_empty());
1121        assert!(takia.children(node2).unwrap().is_empty());
1122    }
1123
1124    #[test]
1125    fn remove_last_node() {
1126        let mut takia: TakiaTree<()> = TakiaTree::new();
1127
1128        let parent = takia.new_leaf(Style::default()).unwrap();
1129        let child = takia.new_leaf(Style::default()).unwrap();
1130        takia.add_child(parent, child).unwrap();
1131
1132        takia.remove(child).unwrap();
1133        takia.remove(parent).unwrap();
1134    }
1135
1136    // Related to: https://github.com/DioxusLabs/takia/issues/998
1137    #[test]
1138    fn remove_node_marks_parent_dirty() {
1139        use crate::prelude::*;
1140
1141        let mut takia: TakiaTree<()> = TakiaTree::new();
1142
1143        // The root's height comes entirely from its child's 1px bottom border
1144        let child = takia
1145            .new_leaf(Style { border: Rect { bottom: length(1f32), ..Rect::zero() }, ..Default::default() })
1146            .unwrap();
1147        let root = takia.new_with_children(Style::default(), &[child]).unwrap();
1148
1149        takia.compute_layout(root, Size::MIN_CONTENT).unwrap();
1150        assert_eq!(takia.layout(root).unwrap().size.height, 1f32);
1151
1152        takia.remove(child).unwrap();
1153        assert_eq!(takia.dirty(root), Ok(true));
1154
1155        // The root no longer has any children, so its height should shrink back to zero
1156        takia.compute_layout(root, Size::MIN_CONTENT).unwrap();
1157        assert_eq!(takia.layout(root).unwrap().size.height, 0f32);
1158    }
1159
1160    #[test]
1161    fn set_measure() {
1162        let mut takia: TakiaTree<Size<f32>> = TakiaTree::new();
1163        let node = takia.new_leaf_with_context(Style::default(), Size { width: 200.0, height: 200.0 }).unwrap();
1164        takia.compute_layout_with_measure(node, Size::MAX_CONTENT, size_measure_function).unwrap();
1165        assert_eq!(takia.layout(node).unwrap().size.width, 200.0);
1166
1167        takia.set_node_context(node, Some(Size { width: 100.0, height: 100.0 })).unwrap();
1168        takia.compute_layout_with_measure(node, Size::MAX_CONTENT, size_measure_function).unwrap();
1169        assert_eq!(takia.layout(node).unwrap().size.width, 100.0);
1170    }
1171
1172    #[test]
1173    fn set_measure_of_previously_unmeasured_node() {
1174        let mut takia: TakiaTree<Size<f32>> = TakiaTree::new();
1175        let node = takia.new_leaf(Style::default()).unwrap();
1176        takia.compute_layout_with_measure(node, Size::MAX_CONTENT, size_measure_function).unwrap();
1177        assert_eq!(takia.layout(node).unwrap().size.width, 0.0);
1178
1179        takia.set_node_context(node, Some(Size { width: 100.0, height: 100.0 })).unwrap();
1180        takia.compute_layout_with_measure(node, Size::MAX_CONTENT, size_measure_function).unwrap();
1181        assert_eq!(takia.layout(node).unwrap().size.width, 100.0);
1182    }
1183
1184    /// Test that adding `add_child()` works
1185    #[test]
1186    fn add_child() {
1187        let mut takia: TakiaTree<()> = TakiaTree::new();
1188        let node = takia.new_leaf(Style::default()).unwrap();
1189        assert_eq!(takia.child_count(node), 0);
1190
1191        let child0 = takia.new_leaf(Style::default()).unwrap();
1192        takia.add_child(node, child0).unwrap();
1193        assert_eq!(takia.child_count(node), 1);
1194
1195        let child1 = takia.new_leaf(Style::default()).unwrap();
1196        takia.add_child(node, child1).unwrap();
1197        assert_eq!(takia.child_count(node), 2);
1198    }
1199
1200    #[test]
1201    fn insert_child_at_index() {
1202        let mut takia: TakiaTree<()> = TakiaTree::new();
1203
1204        let child0 = takia.new_leaf(Style::default()).unwrap();
1205        let child1 = takia.new_leaf(Style::default()).unwrap();
1206        let child2 = takia.new_leaf(Style::default()).unwrap();
1207
1208        let node = takia.new_leaf(Style::default()).unwrap();
1209        assert_eq!(takia.child_count(node), 0);
1210
1211        takia.insert_child_at_index(node, 0, child0).unwrap();
1212        assert_eq!(takia.child_count(node), 1);
1213        assert_eq!(takia.children(node).unwrap()[0], child0);
1214
1215        takia.insert_child_at_index(node, 0, child1).unwrap();
1216        assert_eq!(takia.child_count(node), 2);
1217        assert_eq!(takia.children(node).unwrap()[0], child1);
1218        assert_eq!(takia.children(node).unwrap()[1], child0);
1219
1220        takia.insert_child_at_index(node, 1, child2).unwrap();
1221        assert_eq!(takia.child_count(node), 3);
1222        assert_eq!(takia.children(node).unwrap()[0], child1);
1223        assert_eq!(takia.children(node).unwrap()[1], child2);
1224        assert_eq!(takia.children(node).unwrap()[2], child0);
1225    }
1226
1227    #[test]
1228    fn set_children() {
1229        let mut takia: TakiaTree<()> = TakiaTree::new();
1230
1231        let child0 = takia.new_leaf(Style::default()).unwrap();
1232        let child1 = takia.new_leaf(Style::default()).unwrap();
1233        let node = takia.new_with_children(Style::default(), &[child0, child1]).unwrap();
1234
1235        assert_eq!(takia.child_count(node), 2);
1236        assert_eq!(takia.children(node).unwrap()[0], child0);
1237        assert_eq!(takia.children(node).unwrap()[1], child1);
1238
1239        let child2 = takia.new_leaf(Style::default()).unwrap();
1240        let child3 = takia.new_leaf(Style::default()).unwrap();
1241        takia.set_children(node, &[child2, child3]).unwrap();
1242
1243        assert_eq!(takia.child_count(node), 2);
1244        assert_eq!(takia.children(node).unwrap()[0], child2);
1245        assert_eq!(takia.children(node).unwrap()[1], child3);
1246    }
1247
1248    /// Test that removing a child works
1249    #[test]
1250    fn remove_child() {
1251        let mut takia: TakiaTree<()> = TakiaTree::new();
1252        let child0 = takia.new_leaf(Style::default()).unwrap();
1253        let child1 = takia.new_leaf(Style::default()).unwrap();
1254        let node = takia.new_with_children(Style::default(), &[child0, child1]).unwrap();
1255
1256        assert_eq!(takia.child_count(node), 2);
1257
1258        takia.remove_child(node, child0).unwrap();
1259        assert_eq!(takia.child_count(node), 1);
1260        assert_eq!(takia.children(node).unwrap()[0], child1);
1261
1262        takia.remove_child(node, child1).unwrap();
1263        assert_eq!(takia.child_count(node), 0);
1264    }
1265
1266    #[test]
1267    fn remove_child_at_index() {
1268        let mut takia: TakiaTree<()> = TakiaTree::new();
1269        let child0 = takia.new_leaf(Style::default()).unwrap();
1270        let child1 = takia.new_leaf(Style::default()).unwrap();
1271        let node = takia.new_with_children(Style::default(), &[child0, child1]).unwrap();
1272
1273        assert_eq!(takia.child_count(node), 2);
1274
1275        takia.remove_child_at_index(node, 0).unwrap();
1276        assert_eq!(takia.child_count(node), 1);
1277        assert_eq!(takia.children(node).unwrap()[0], child1);
1278
1279        takia.remove_child_at_index(node, 0).unwrap();
1280        assert_eq!(takia.child_count(node), 0);
1281    }
1282
1283    #[test]
1284    fn remove_children_range() {
1285        let mut takia: TakiaTree<()> = TakiaTree::new();
1286        let child0 = takia.new_leaf(Style::default()).unwrap();
1287        let child1 = takia.new_leaf(Style::default()).unwrap();
1288        let child2 = takia.new_leaf(Style::default()).unwrap();
1289        let child3 = takia.new_leaf(Style::default()).unwrap();
1290        let node = takia.new_with_children(Style::default(), &[child0, child1, child2, child3]).unwrap();
1291
1292        assert_eq!(takia.child_count(node), 4);
1293
1294        takia.remove_children_range(node, 1..=2).unwrap();
1295        assert_eq!(takia.child_count(node), 2);
1296        assert_eq!(takia.children(node).unwrap(), [child0, child3]);
1297        for child in [child0, child3] {
1298            assert_eq!(takia.parent(child), Some(node));
1299        }
1300        for child in [child1, child2] {
1301            assert_eq!(takia.parent(child), None);
1302        }
1303    }
1304
1305    // Related to: https://github.com/DioxusLabs/takia/issues/510
1306    #[test]
1307    fn remove_child_updates_parents() {
1308        let mut takia: TakiaTree<()> = TakiaTree::new();
1309
1310        let parent = takia.new_leaf(Style::default()).unwrap();
1311        let child = takia.new_leaf(Style::default()).unwrap();
1312
1313        takia.add_child(parent, child).unwrap();
1314
1315        takia.remove(parent).unwrap();
1316
1317        // Once the parent is removed this shouldn't panic.
1318        assert!(takia.set_children(child, &[]).is_ok());
1319    }
1320
1321    #[test]
1322    fn replace_child_at_index() {
1323        let mut takia: TakiaTree<()> = TakiaTree::new();
1324
1325        let child0 = takia.new_leaf(Style::default()).unwrap();
1326        let child1 = takia.new_leaf(Style::default()).unwrap();
1327
1328        let node = takia.new_with_children(Style::default(), &[child0]).unwrap();
1329        assert_eq!(takia.child_count(node), 1);
1330        assert_eq!(takia.children(node).unwrap()[0], child0);
1331
1332        takia.replace_child_at_index(node, 0, child1).unwrap();
1333        assert_eq!(takia.child_count(node), 1);
1334        assert_eq!(takia.children(node).unwrap()[0], child1);
1335    }
1336    #[test]
1337    fn test_child_at_index() {
1338        let mut takia: TakiaTree<()> = TakiaTree::new();
1339        let child0 = takia.new_leaf(Style::default()).unwrap();
1340        let child1 = takia.new_leaf(Style::default()).unwrap();
1341        let child2 = takia.new_leaf(Style::default()).unwrap();
1342        let node = takia.new_with_children(Style::default(), &[child0, child1, child2]).unwrap();
1343
1344        assert!(if let Ok(result) = takia.child_at_index(node, 0) { result == child0 } else { false });
1345        assert!(if let Ok(result) = takia.child_at_index(node, 1) { result == child1 } else { false });
1346        assert!(if let Ok(result) = takia.child_at_index(node, 2) { result == child2 } else { false });
1347    }
1348    #[test]
1349    fn test_child_count() {
1350        let mut takia: TakiaTree<()> = TakiaTree::new();
1351        let child0 = takia.new_leaf(Style::default()).unwrap();
1352        let child1 = takia.new_leaf(Style::default()).unwrap();
1353        let node = takia.new_with_children(Style::default(), &[child0, child1]).unwrap();
1354
1355        assert!(takia.child_count(node) == 2);
1356        assert!(takia.child_count(child0) == 0);
1357        assert!(takia.child_count(child1) == 0);
1358    }
1359
1360    #[allow(clippy::vec_init_then_push)]
1361    #[test]
1362    fn test_children() {
1363        let mut takia: TakiaTree<()> = TakiaTree::new();
1364        let child0 = takia.new_leaf(Style::default()).unwrap();
1365        let child1 = takia.new_leaf(Style::default()).unwrap();
1366        let node = takia.new_with_children(Style::default(), &[child0, child1]).unwrap();
1367
1368        let mut children = sys::Vec::new();
1369        children.push(child0);
1370        children.push(child1);
1371
1372        let children_result = takia.children(node).unwrap();
1373        assert_eq!(children_result, children);
1374
1375        assert!(takia.children(child0).unwrap().is_empty());
1376    }
1377    #[test]
1378    fn test_set_style() {
1379        let mut takia: TakiaTree<()> = TakiaTree::new();
1380
1381        let node = takia.new_leaf(Style::default()).unwrap();
1382        assert_eq!(takia.style(node).unwrap().display, Display::Flex);
1383
1384        takia.set_style(node, Style { display: Display::None, ..Style::default() }).unwrap();
1385        assert_eq!(takia.style(node).unwrap().display, Display::None);
1386    }
1387    #[test]
1388    fn test_style() {
1389        let mut takia: TakiaTree<()> = TakiaTree::new();
1390
1391        let style = Style { display: Display::None, flex_direction: FlexDirection::RowReverse, ..Default::default() };
1392
1393        let node = takia.new_leaf(style.clone()).unwrap();
1394
1395        let res = takia.style(node);
1396        assert!(res.is_ok());
1397        assert!(res.unwrap() == &style);
1398    }
1399    #[test]
1400    fn test_layout() {
1401        let mut takia: TakiaTree<()> = TakiaTree::new();
1402        let node = takia.new_leaf(Style::default()).unwrap();
1403
1404        // TODO: Improve this test?
1405        let res = takia.layout(node);
1406        assert!(res.is_ok());
1407    }
1408
1409    #[test]
1410    fn test_mark_dirty() {
1411        let mut takia: TakiaTree<()> = TakiaTree::new();
1412        let child0 = takia.new_leaf(Style::default()).unwrap();
1413        let child1 = takia.new_leaf(Style::default()).unwrap();
1414        let node = takia.new_with_children(Style::default(), &[child0, child1]).unwrap();
1415
1416        takia.compute_layout(node, Size::MAX_CONTENT).unwrap();
1417
1418        assert_eq!(takia.dirty(child0), Ok(false));
1419        assert_eq!(takia.dirty(child1), Ok(false));
1420        assert_eq!(takia.dirty(node), Ok(false));
1421
1422        takia.mark_dirty(node).unwrap();
1423        assert_eq!(takia.dirty(child0), Ok(false));
1424        assert_eq!(takia.dirty(child1), Ok(false));
1425        assert_eq!(takia.dirty(node), Ok(true));
1426
1427        takia.compute_layout(node, Size::MAX_CONTENT).unwrap();
1428        takia.mark_dirty(child0).unwrap();
1429        assert_eq!(takia.dirty(child0), Ok(true));
1430        assert_eq!(takia.dirty(child1), Ok(false));
1431        assert_eq!(takia.dirty(node), Ok(true));
1432    }
1433
1434    #[test]
1435    fn compute_layout_should_produce_valid_result() {
1436        let mut takia: TakiaTree<()> = TakiaTree::new();
1437        let node_result = takia.new_leaf(Style {
1438            size: Size { width: Dimension::from_length(10f32), height: Dimension::from_length(10f32) },
1439            ..Default::default()
1440        });
1441        assert!(node_result.is_ok());
1442        let node = node_result.unwrap();
1443        let layout_result = takia.compute_layout(
1444            node,
1445            Size { width: AvailableSpace::Definite(100.), height: AvailableSpace::Definite(100.) },
1446        );
1447        assert!(layout_result.is_ok());
1448    }
1449
1450    #[test]
1451    fn make_sure_layout_location_is_top_left() {
1452        use crate::prelude::*;
1453
1454        let mut takia: TakiaTree<()> = TakiaTree::new();
1455
1456        let node = takia
1457            .new_leaf(Style {
1458                size: Size { width: Dimension::from_percent(1f32), height: Dimension::from_percent(1f32) },
1459                ..Default::default()
1460            })
1461            .unwrap();
1462
1463        let root = takia
1464            .new_with_children(
1465                Style {
1466                    size: Size { width: Dimension::from_length(100f32), height: Dimension::from_length(100f32) },
1467                    padding: Rect {
1468                        left: length(10f32),
1469                        right: length(20f32),
1470                        top: length(30f32),
1471                        bottom: length(40f32),
1472                    },
1473                    ..Default::default()
1474                },
1475                &[node],
1476            )
1477            .unwrap();
1478
1479        takia.compute_layout(root, Size::MAX_CONTENT).unwrap();
1480
1481        // If Layout::location represents top-left coord, 'node' location
1482        // must be (due applied 'root' padding): {x: 10, y: 30}.
1483        //
1484        // It's important, since result will be different for each other
1485        // coordinate space:
1486        // - bottom-left:  {x: 10, y: 40}
1487        // - top-right:    {x: 20, y: 30}
1488        // - bottom-right: {x: 20, y: 40}
1489        let layout = takia.layout(node).unwrap();
1490        assert_eq!(layout.location.x, 10f32);
1491        assert_eq!(layout.location.y, 30f32);
1492    }
1493
1494    #[test]
1495    fn set_children_reparents() {
1496        let mut takia: TakiaTree<()> = TakiaTree::new();
1497        let child = takia.new_leaf(Style::default()).unwrap();
1498        let old_parent = takia.new_with_children(Style::default(), &[child]).unwrap();
1499
1500        let new_parent = takia.new_leaf(Style::default()).unwrap();
1501        takia.set_children(new_parent, &[child]).unwrap();
1502
1503        assert!(takia.children(old_parent).unwrap().is_empty());
1504    }
1505
1506    /// A context that increments a counter when dropped. Each test owns its own static
1507    /// counter so the two can run in parallel.
1508    struct DropCounted(&'static core::sync::atomic::AtomicUsize);
1509
1510    impl Drop for DropCounted {
1511        fn drop(&mut self) {
1512            self.0.fetch_add(1, core::sync::atomic::Ordering::SeqCst);
1513        }
1514    }
1515
1516    #[test]
1517    fn remove_drops_the_nodes_context() {
1518        static DROPS: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(0);
1519        let mut takia: TakiaTree<DropCounted> = TakiaTree::new();
1520        let node = takia.new_leaf_with_context(Style::DEFAULT, DropCounted(&DROPS)).unwrap();
1521
1522        takia.remove(node).unwrap();
1523
1524        assert_eq!(
1525            DROPS.load(core::sync::atomic::Ordering::SeqCst),
1526            1,
1527            "removing a node must drop its context rather than strand it in node_context_data"
1528        );
1529    }
1530
1531    #[test]
1532    fn clear_drops_every_nodes_context() {
1533        static DROPS: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(0);
1534        let mut takia: TakiaTree<DropCounted> = TakiaTree::new();
1535        for _ in 0..8 {
1536            takia.new_leaf_with_context(Style::DEFAULT, DropCounted(&DROPS)).unwrap();
1537        }
1538
1539        takia.clear();
1540
1541        assert_eq!(
1542            DROPS.load(core::sync::atomic::Ordering::SeqCst),
1543            8,
1544            "clear() drops all nodes, so it must drop their contexts too"
1545        );
1546    }
1547}