Skip to main content

tui_lipan/core/
element.rs

1use std::any::{Any, TypeId};
2use std::cell::Cell;
3use std::sync::Arc;
4
5use smallvec::SmallVec;
6
7use crate::callback::ScopeId;
8use crate::core::memo::MemoElement;
9use crate::overlay::Portal;
10use crate::style::{LayoutConstraints, Length, ShrinkPriority, Theme};
11#[cfg(feature = "big-text")]
12use crate::widgets::BigText;
13#[cfg(feature = "image")]
14use crate::widgets::Image;
15#[cfg(feature = "terminal")]
16use crate::widgets::Terminal;
17use crate::widgets::{
18    Animated, AsciiCanvas, Button, Canvas, Center, CenterPin, Chart, Checkbox, ClassDiagram,
19    Divider, DocumentView, DragSource, DraggableTabBar, DropTarget, EffectScope, ErDiagram, Flow,
20    Flowchart, Frame, GanttDiagram, Graph, Grid, HStack, Heatmap, HexArea, Input, List,
21    MouseRegion, PanView, ProgressBar, ScrollView, SequenceDiagram, Spacer, Spinner, Splitter,
22    StateDiagram, StatusBarLayout, Tabs, Text, VStack, ZStack,
23};
24
25#[derive(Clone, Copy, Debug, PartialEq, Eq)]
26pub(crate) struct MeasureCacheEntry {
27    pub max_w: Option<u16>,
28    pub max_h: Option<u16>,
29    pub size: (u16, u16),
30}
31
32/// A stable identity used for reconciliation and focus.
33#[derive(Clone, Debug, PartialEq, Eq, Hash)]
34pub struct Key(Arc<str>);
35
36impl From<&'static str> for Key {
37    fn from(s: &'static str) -> Self {
38        Self(Arc::from(s))
39    }
40}
41
42impl From<String> for Key {
43    fn from(s: String) -> Self {
44        Self(Arc::from(s))
45    }
46}
47
48impl From<Arc<str>> for Key {
49    fn from(s: Arc<str>) -> Self {
50        Self(s)
51    }
52}
53
54impl AsRef<str> for Key {
55    fn as_ref(&self) -> &str {
56        &self.0
57    }
58}
59
60impl std::fmt::Display for Key {
61    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62        write!(f, "{}", self.0)
63    }
64}
65
66/// A declarative UI node (the Virtual DOM).
67#[derive(Clone)]
68pub struct Element {
69    /// Optional key for stable identity.
70    pub(crate) key: Option<Key>,
71    /// Concrete element kind.
72    pub(crate) kind: ElementKind,
73    /// Layout constraints for stack sizing.
74    pub(crate) layout: LayoutConstraints,
75    /// Cached layout hash for repeated layout-only reconciles.
76    pub(crate) layout_hash_cache: Cell<Option<u64>>,
77    /// Small MRU cache for repeated min-size measurements.
78    pub(crate) measure_cache: Cell<[Option<MeasureCacheEntry>; 2]>,
79    /// Memoized [`crate::widgets::element_subtree_has_split_wrap_sync`] result for this subtree.
80    pub(crate) split_wrap_probe_cache: Cell<Option<bool>>,
81}
82
83impl Element {
84    /// Create a new element.
85    pub(crate) fn new(kind: ElementKind) -> Self {
86        Self {
87            key: None,
88            kind,
89            layout: LayoutConstraints::default(),
90            layout_hash_cache: Cell::new(None),
91            measure_cache: Cell::new([None, None]),
92            split_wrap_probe_cache: Cell::new(None),
93        }
94    }
95
96    pub(crate) fn clear_caches(&self) {
97        self.layout_hash_cache.set(None);
98        self.measure_cache.set([None, None]);
99        self.split_wrap_probe_cache.set(None);
100    }
101
102    /// Assign a stable sibling key used during reconciliation.
103    ///
104    /// For all multi-child containers, keyed children are matched by key first,
105    /// so identity is preserved across reorders and insertions when tags are
106    /// compatible.
107    pub fn key(mut self, key: impl Into<Key>) -> Self {
108        self.key = Some(key.into());
109        self
110    }
111
112    /// Assign a path-independent persistence key for a nested component.
113    ///
114    /// Unlike [`key`](Self::key), which only disambiguates siblings at the same
115    /// resolved container path, `component_state_key` makes the component's
116    /// instance (and therefore its state) survive *ancestor* reshaping: wrapping
117    /// or unwrapping parent containers, context providers, portals, or other
118    /// restructuring that would normally invalidate the path-based reuse.
119    ///
120    /// Only meaningful on elements produced from [`crate::child`]; a no-op on
121    /// other element kinds.
122    pub fn component_state_key(mut self, key: impl Into<Key>) -> Self {
123        if let ElementKind::Component(component) = &mut self.kind {
124            component.state_key = Some(key.into());
125        }
126        self
127    }
128
129    /// Set minimum width constraint. See [`LayoutConstraints::min_w`] for semantics.
130    pub fn min_width(mut self, w: Length) -> Self {
131        self.layout.min_w = w;
132        self.clear_caches();
133        self
134    }
135
136    /// Set minimum height constraint. See [`LayoutConstraints::min_h`] for semantics.
137    pub fn min_height(mut self, h: Length) -> Self {
138        self.layout.min_h = h;
139        self.clear_caches();
140        self
141    }
142
143    /// Set maximum width constraint. See [`LayoutConstraints::max_w`] for semantics.
144    ///
145    /// When the new max is a concrete `Px` value smaller than the current `min_w`,
146    /// `min_w` is capped so that `clamp_width` does not override the max.
147    pub fn max_width(mut self, w: Length) -> Self {
148        self.layout.max_w = Some(w);
149        if let (Length::Px(max_px), Length::Px(min_px)) = (w, self.layout.min_w)
150            && min_px > max_px
151        {
152            self.layout.min_w = Length::Px(max_px);
153        }
154        self.clear_caches();
155        self
156    }
157
158    /// Set maximum height constraint. See [`LayoutConstraints::max_h`] for semantics.
159    ///
160    /// When the new max is a concrete `Px` value smaller than the current `min_h`,
161    /// `min_h` is capped so that `clamp_height` does not override the max.
162    pub fn max_height(mut self, h: Length) -> Self {
163        self.layout.max_h = Some(h);
164        if let (Length::Px(max_px), Length::Px(min_px)) = (h, self.layout.min_h)
165            && min_px > max_px
166        {
167            self.layout.min_h = Length::Px(max_px);
168        }
169        self.clear_caches();
170        self
171    }
172
173    /// Mark whether this element's cross-axis size can change when its
174    /// main-axis allocation changes. See [`LayoutConstraints::reflows`].
175    pub fn reflows(mut self, reflows: bool) -> Self {
176        self.layout.reflows = reflows;
177        self.clear_caches();
178        self
179    }
180
181    /// Set this element's stack shrink priority. See
182    /// [`LayoutConstraints::shrink_priority`].
183    pub fn shrink_priority(mut self, priority: ShrinkPriority) -> Self {
184        self.layout.shrink_priority = priority;
185        self.clear_caches();
186        self
187    }
188
189    pub(crate) fn with_layout(mut self, layout: LayoutConstraints) -> Self {
190        self.layout = layout;
191        self.clear_caches();
192        self
193    }
194
195    pub(crate) fn layout_constraints(&self) -> LayoutConstraints {
196        match &self.kind {
197            ElementKind::Group(group) => group.child.layout_constraints(),
198            ElementKind::EffectScope(scope) => scope
199                .child
200                .as_deref()
201                .map(Element::layout_constraints)
202                .unwrap_or(self.layout),
203            ElementKind::Animated(animated) => {
204                if animated.height.is_some() {
205                    let mut layout = animated.child.layout_constraints();
206                    layout.min_h = self.layout.min_h;
207                    layout.max_h = self.layout.max_h;
208                    layout.focus_min_h = self.layout.focus_min_h;
209                    layout.collapse_h = self.layout.collapse_h;
210                    layout.force_compact = false;
211                    layout
212                } else {
213                    animated.child.layout_constraints()
214                }
215            }
216            ElementKind::DragSource(source) => source
217                .child
218                .as_deref()
219                .map(Element::layout_constraints)
220                .unwrap_or(self.layout),
221            ElementKind::DropTarget(target) => target
222                .child
223                .as_deref()
224                .map(Element::layout_constraints)
225                .unwrap_or(self.layout),
226            ElementKind::ThemeProvider(tp) => tp.child.layout_constraints(),
227            ElementKind::ContextProvider(cp) => cp.child.layout_constraints(),
228            ElementKind::Memo(_) => self.layout,
229            _ => self.layout,
230        }
231    }
232
233    pub(crate) fn contains_unexpanded_component(&self) -> bool {
234        matches!(self.kind, ElementKind::Component(_))
235            || self
236                .kind
237                .children()
238                .into_iter()
239                .any(Self::contains_unexpanded_component)
240    }
241
242    /// Replace the child of the `Group` with the matching scope, clearing all
243    /// cached layout/measurement state on the replacement path.
244    pub(crate) fn replace_group_child_by_scope(
245        &mut self,
246        scope: ScopeId,
247        replacement: &mut Option<Element>,
248    ) -> bool {
249        if let ElementKind::Group(group) = &mut self.kind
250            && group.scope == scope
251        {
252            if let Some(replacement) = replacement.take() {
253                *group.child = replacement;
254                self.clear_caches();
255                return true;
256            }
257            return false;
258        }
259
260        let replaced = self
261            .kind
262            .children_mut()
263            .iter_mut()
264            .any(|child| child.replace_group_child_by_scope(scope, replacement));
265        if replaced {
266            self.clear_caches();
267        }
268        replaced
269    }
270}
271
272/// Concrete element kinds.
273#[derive(Clone)]
274#[allow(clippy::large_enum_variant)]
275pub(crate) enum ElementKind {
276    /// Static or dynamic text.
277    Text(Text),
278    /// ASCII art text.
279    #[cfg(feature = "big-text")]
280    BigText(BigText),
281    /// ASCII canvas grid (also handles frame sequences).
282    AsciiCanvas(AsciiCanvas),
283
284    /// Clickable, focusable button.
285    Button(Box<Button>),
286    /// Single-line text input.
287    Input(Box<Input>),
288    /// Protocol-aware image widget.
289    #[cfg(feature = "image")]
290    Image(Image),
291    /// Vertically scrolling list.
292    List(Box<List>),
293    /// Multi-line text input.
294    TextArea(Box<crate::widgets::TextArea>),
295    /// Hex/ASCII binary data viewer.
296    HexArea(Box<HexArea>),
297    /// Terminal viewport.
298    #[cfg(feature = "terminal")]
299    Terminal(Terminal),
300    /// Popover overlay.
301    Popover(crate::widgets::Popover),
302    Portal(Portal),
303    /// Table with columns and rows.
304    Table(Box<crate::widgets::Table>),
305    /// Tab bar.
306    Tabs(Tabs),
307    /// Draggable tab bar.
308    DraggableTabBar(Box<DraggableTabBar>),
309    /// Nested component.
310    Component(crate::core::nested::ComponentElement),
311    /// Internal layout-transparent wrapper.
312    Group(Group),
313    /// Subtree color/effect post-processing wrapper.
314    EffectScope(EffectScope),
315    /// Animated wrapper (opacity + height transitions).
316    Animated(Animated),
317    /// Drag source wrapper.
318    DragSource(DragSource),
319    /// Drop target wrapper.
320    DropTarget(DropTarget),
321    /// Pointer-interaction region wrapper.
322    MouseRegion(MouseRegion),
323    /// Scrollable vertical container.
324    ScrollView(Box<ScrollView>),
325    /// Two-dimensional pan viewport.
326    PanView(PanView),
327    /// Vertical stack layout.
328    VStack(VStack),
329    /// Horizontal stack layout.
330    HStack(HStack),
331    /// CSS-like explicit grid layout.
332    Grid(Grid),
333    /// Horizontal wrapping flow layout.
334    Flow(Flow),
335    /// Absolute-positioned child layout.
336    Canvas(Canvas),
337    /// Mermaid-style flowchart visualization.
338    Flowchart(Box<Flowchart>),
339    /// Overlay stack layout.
340    ZStack(ZStack),
341    /// Centering helper for overlays/modals.
342    Center(Center),
343    /// Center-pinned layout with collision-aware top/bottom zones.
344    CenterPin(CenterPin),
345    /// Framed container with optional title.
346    Frame(Frame),
347    /// Divider line.
348    Divider(Divider),
349    /// Flexible empty space.
350    Spacer(Spacer),
351    /// Sparkline chart.
352    Sparkline(crate::widgets::Sparkline),
353    /// Multi-series chart.
354    Chart(Box<Chart>),
355    /// Node-edge graph visualization.
356    Graph(Box<Graph>),
357    /// UML sequence diagram visualization.
358    SequenceDiagram(Box<SequenceDiagram>),
359    /// UML class diagram visualization.
360    ClassDiagram(Box<ClassDiagram>),
361    /// UML state diagram visualization.
362    StateDiagram(Box<StateDiagram>),
363    /// Entity-relationship diagram visualization.
364    ErDiagram(Box<ErDiagram>),
365    /// Gantt timeline diagram visualization.
366    GanttDiagram(Box<GanttDiagram>),
367    /// Internal status bar layout container.
368    StatusBarLayout(StatusBarLayout),
369    /// Heatmap visualization.
370    Heatmap(Heatmap),
371    /// Checkbox toggle.
372    Checkbox(Checkbox),
373    /// Progress bar.
374    ProgressBar(ProgressBar),
375    /// Slider.
376    Slider(crate::widgets::Slider),
377    /// Loading spinner.
378    Spinner(Spinner),
379    /// Resizable splitter container.
380    Splitter(Splitter),
381    /// Read-only rich text document viewer.
382    DocumentView(Box<DocumentView>),
383    /// Deferred theme application wrapper (consumed during expansion).
384    ThemeProvider(Box<ThemeProviderElement>),
385    /// Deferred typed context provider (consumed during expansion).
386    ContextProvider(Box<ContextProviderElement>),
387    /// In-view memoized subtree wrapper.
388    Memo(MemoElement),
389}
390
391/// Generate the `dimensions()` match arms from the widget manifest.
392macro_rules! impl_element_dimensions {
393    (
394        @direct [ $($v:ident,)* ]
395        @direct_gated [ $($gv:ident => $gf:literal,)* ]
396        @direct_no_hash [ $($dnh:ident,)* ]
397        @direct_no_hash_gated [ $($dnhg:ident => $dnhgf:literal,)* ]
398        @props_dims [ $($pd:ident,)* ]
399        @const_auto_hash [ $($cah:ident,)* ]
400        @const_auto_hash_gated [ $($cahg:ident => $cahgf:literal,)* ]
401        @const_flex [ $($cf:ident,)* ]
402        @const_flex_no_hash [ $($cfnh:ident,)* ]
403        @no_dims [ $($nd:ident,)* ]
404        @element_only_const_auto [ $($eo:ident,)* ]
405    ) => {
406        impl ElementKind {
407            /// Returns the `(width, height)` dimensions for this element kind, if applicable.
408            ///
409            /// Returns `None` for variants that need recursive delegation (Group,
410            /// MouseRegion, Popover) or special per-axis handling (Frame).
411            pub(crate) fn dimensions(&self) -> Option<(Length, Length)> {
412                match self {
413                    // Standard widgets with direct width/height fields
414                    $( Self::$v(w) => Some((w.width, w.height)), )*
415                    $( Self::$dnh(w) => Some((w.width, w.height)), )*
416                    $( #[cfg(feature = $gf)] Self::$gv(w) => Some((w.width, w.height)), )*
417                    $( #[cfg(feature = $dnhgf)] Self::$dnhg(w) => Some((w.width, w.height)), )*
418
419                    // Containers with props struct
420                    $( Self::$pd(w) => Some((w.props.width, w.props.height)), )*
421
422                    // Always-constant dimensions: Auto
423                    $( Self::$cah(_) )|* $( | Self::$eo(_) )* => {
424                        Some((Length::Auto, Length::Auto))
425                    }
426                    $( #[cfg(feature = $cahgf)] Self::$cahg(_) => {
427                        Some((Length::Auto, Length::Auto))
428                    } )*
429
430                    // Always-constant dimensions: Flex(1)
431                    $( Self::$cf(_) )|* $( | Self::$cfnh(_) )* => {
432                        Some((Length::Flex(1), Length::Flex(1)))
433                    }
434
435                    // Special cases: delegation or per-axis logic - handled by caller
436                    $( Self::$nd(_) )|* => None,
437                }
438            }
439        }
440    };
441}
442
443for_all_widget_variants!(impl_element_dimensions);
444
445impl ElementKind {
446    /// Returns references to all `Element` children of this variant.
447    ///
448    /// Leaf widgets and `Component` (which is expanded before layout) return
449    /// an empty collection. Container variants return children in a consistent
450    /// order: for named slots (e.g. Frame header/child, CenterPin top/center/bottom),
451    /// only `Some` slots are included.
452    pub(crate) fn children(&self) -> SmallVec<[&Element; 4]> {
453        match self {
454            Self::Group(g) => SmallVec::from_buf_and_len([g.child.as_ref(); 4], 1),
455            Self::ThemeProvider(tp) => SmallVec::from_buf_and_len([&tp.child; 4], 1),
456            Self::ContextProvider(cp) => SmallVec::from_buf_and_len([&cp.child; 4], 1),
457            Self::Portal(p) => SmallVec::from_buf_and_len([p.content.as_ref(); 4], 1),
458            Self::Popover(p) => {
459                smallvec::smallvec![p.trigger.as_ref(), p.content.as_ref()]
460            }
461            Self::EffectScope(e) => match &e.child {
462                Some(c) => SmallVec::from_buf_and_len([c.as_ref(); 4], 1),
463                None => SmallVec::new(),
464            },
465            Self::Animated(a) => SmallVec::from_buf_and_len([a.child.as_ref(); 4], 1),
466            Self::DragSource(ds) => match &ds.child {
467                Some(c) => SmallVec::from_buf_and_len([c.as_ref(); 4], 1),
468                None => SmallVec::new(),
469            },
470            Self::DropTarget(dt) => match &dt.child {
471                Some(c) => SmallVec::from_buf_and_len([c.as_ref(); 4], 1),
472                None => SmallVec::new(),
473            },
474            Self::MouseRegion(m) => match &m.child {
475                Some(c) => SmallVec::from_buf_and_len([c.as_ref(); 4], 1),
476                None => SmallVec::new(),
477            },
478            Self::Center(c) => match &c.child {
479                Some(c) => SmallVec::from_buf_and_len([c.as_ref(); 4], 1),
480                None => SmallVec::new(),
481            },
482            Self::CenterPin(cp) => {
483                let mut out = SmallVec::new();
484                if let Some(c) = &cp.top {
485                    out.push(c.as_ref());
486                }
487                if let Some(c) = &cp.center {
488                    out.push(c.as_ref());
489                }
490                if let Some(c) = &cp.bottom {
491                    out.push(c.as_ref());
492                }
493                out
494            }
495            Self::StatusBarLayout(layout) => smallvec::smallvec![
496                layout.left.as_ref(),
497                layout.center.as_ref(),
498                layout.right.as_ref()
499            ],
500            Self::Frame(f) => {
501                let mut out = SmallVec::new();
502                if let Some(c) = &f.header {
503                    out.push(c.as_ref());
504                }
505                if let Some(c) = &f.child {
506                    out.push(c.as_ref());
507                }
508                out
509            }
510            Self::Divider(d) => match &d.label {
511                Some(c) => SmallVec::from_buf_and_len([c.as_ref(); 4], 1),
512                None => SmallVec::new(),
513            },
514            Self::ScrollView(sv) => sv.children.iter().collect(),
515            Self::PanView(pan) => match &pan.child {
516                Some(c) => SmallVec::from_buf_and_len([c.as_ref(); 4], 1),
517                None => SmallVec::new(),
518            },
519            Self::VStack(v) => v.children.iter().collect(),
520            Self::HStack(h) => h.children.iter().collect(),
521            Self::Grid(g) => g.items.iter().map(|i| &i.element).collect(),
522            Self::Flow(f) => f.children.iter().collect(),
523            Self::Canvas(c) => c.items.iter().map(|i| &i.element).collect(),
524            Self::ZStack(z) => z.children.iter().collect(),
525            Self::Splitter(sp) => sp.children.iter().collect(),
526            _ => SmallVec::new(),
527        }
528    }
529
530    /// Returns mutable references to all `Element` children of this variant.
531    ///
532    /// Same semantics as [`children`](Self::children) but with `&mut` access.
533    pub(crate) fn children_mut(&mut self) -> SmallVec<[&mut Element; 4]> {
534        match self {
535            Self::Group(g) => smallvec::smallvec![g.child.as_mut()],
536            Self::ThemeProvider(tp) => smallvec::smallvec![&mut tp.child],
537            Self::ContextProvider(cp) => smallvec::smallvec![&mut cp.child],
538            Self::Portal(p) => smallvec::smallvec![p.content.as_mut()],
539            Self::Popover(p) => {
540                smallvec::smallvec![p.trigger.as_mut(), p.content.as_mut()]
541            }
542            Self::EffectScope(e) => match &mut e.child {
543                Some(c) => smallvec::smallvec![c.as_mut()],
544                None => SmallVec::new(),
545            },
546            Self::Animated(a) => smallvec::smallvec![a.child.as_mut()],
547            Self::DragSource(ds) => match &mut ds.child {
548                Some(c) => smallvec::smallvec![c.as_mut()],
549                None => SmallVec::new(),
550            },
551            Self::DropTarget(dt) => match &mut dt.child {
552                Some(c) => smallvec::smallvec![c.as_mut()],
553                None => SmallVec::new(),
554            },
555            Self::MouseRegion(m) => match &mut m.child {
556                Some(c) => smallvec::smallvec![c.as_mut()],
557                None => SmallVec::new(),
558            },
559            Self::Center(c) => match &mut c.child {
560                Some(c) => smallvec::smallvec![c.as_mut()],
561                None => SmallVec::new(),
562            },
563            Self::CenterPin(cp) => {
564                let mut out = SmallVec::new();
565                if let Some(c) = &mut cp.top {
566                    out.push(c.as_mut());
567                }
568                if let Some(c) = &mut cp.center {
569                    out.push(c.as_mut());
570                }
571                if let Some(c) = &mut cp.bottom {
572                    out.push(c.as_mut());
573                }
574                out
575            }
576            Self::StatusBarLayout(layout) => smallvec::smallvec![
577                layout.left.as_mut(),
578                layout.center.as_mut(),
579                layout.right.as_mut()
580            ],
581            Self::Frame(f) => {
582                let mut out = SmallVec::new();
583                if let Some(c) = &mut f.header {
584                    out.push(c.as_mut());
585                }
586                if let Some(c) = &mut f.child {
587                    out.push(c.as_mut());
588                }
589                out
590            }
591            Self::Divider(d) => match &mut d.label {
592                Some(c) => smallvec::smallvec![c.as_mut()],
593                None => SmallVec::new(),
594            },
595            Self::ScrollView(sv) => sv.children.iter_mut().collect(),
596            Self::PanView(pan) => match &mut pan.child {
597                Some(c) => smallvec::smallvec![c.as_mut()],
598                None => SmallVec::new(),
599            },
600            Self::VStack(v) => v.children.iter_mut().collect(),
601            Self::HStack(h) => h.children.iter_mut().collect(),
602            Self::Grid(g) => g.items.iter_mut().map(|i| &mut i.element).collect(),
603            Self::Flow(f) => f.children.iter_mut().collect(),
604            Self::Canvas(c) => c.items.iter_mut().map(|i| &mut i.element).collect(),
605            Self::ZStack(z) => z.children.iter_mut().collect(),
606            Self::Splitter(sp) => sp.children.iter_mut().collect(),
607            _ => SmallVec::new(),
608        }
609    }
610}
611
612/// A deferred theme application wrapper, consumed during component expansion.
613#[derive(Clone)]
614pub(crate) struct ThemeProviderElement {
615    pub theme: Theme,
616    pub child: Element,
617}
618
619/// A deferred typed context provider wrapper, consumed during expansion.
620#[derive(Clone)]
621pub(crate) struct ContextProviderElement {
622    pub type_id: TypeId,
623    pub value: Arc<dyn Any>,
624    pub equals: fn(&dyn Any, &dyn Any) -> bool,
625    pub generation: u64,
626    pub child: Element,
627}
628
629impl ContextProviderElement {
630    pub(crate) fn new<T>(value: T, child: Element) -> Self
631    where
632        T: Clone + PartialEq + 'static,
633    {
634        Self {
635            type_id: TypeId::of::<T>(),
636            value: Arc::new(value),
637            equals: context_value_equals::<T>,
638            generation: 1,
639            child,
640        }
641    }
642}
643
644fn context_value_equals<T: PartialEq + 'static>(a: &dyn Any, b: &dyn Any) -> bool {
645    a.downcast_ref::<T>() == b.downcast_ref::<T>()
646}
647
648/// A layout-transparent wrapper node used to preserve scoping boundaries.
649#[derive(Clone)]
650pub(crate) struct Group {
651    pub scope: ScopeId,
652    pub child: Box<Element>,
653}
654
655impl crate::layout::hash::LayoutHash for Group {
656    fn layout_hash(
657        &self,
658        hasher: &mut impl std::hash::Hasher,
659        recurse: &dyn Fn(&Element) -> Option<u64>,
660    ) -> Option<()> {
661        use std::hash::Hash;
662        recurse(self.child.as_ref())?.hash(hasher);
663        Some(())
664    }
665}
666
667/// Extension trait for ergonomically building `Element`s.
668///
669/// These methods consume the widget and return `Element`, not the original
670/// widget type, so they must always be the **last calls** in a builder chain.
671/// Placing them before widget-specific methods causes a compile error because
672/// `Element` does not expose widget setters.
673///
674/// If a constraint is needed mid-chain, add it as a dedicated builder method
675/// on that widget (see `Toast::max_width` as an example).
676pub trait IntoElement: Into<Element> + Sized {
677    /// Convert into an element and assign a stable sibling key used during
678    /// multi-child container reconciliation.
679    fn key(self, key: impl Into<Key>) -> Element {
680        self.into().key(key)
681    }
682
683    /// Convert into an element and assign a path-independent component state
684    /// key. See [`Element::component_state_key`].
685    fn component_state_key(self, key: impl Into<Key>) -> Element {
686        self.into().component_state_key(key)
687    }
688
689    /// Set minimum width constraint. See [`LayoutConstraints::min_w`] for semantics.
690    fn min_width(self, w: Length) -> Element {
691        self.into().min_width(w)
692    }
693
694    /// Set minimum height constraint. See [`LayoutConstraints::min_h`] for semantics.
695    fn min_height(self, h: Length) -> Element {
696        self.into().min_height(h)
697    }
698
699    /// Set maximum width constraint. See [`LayoutConstraints::max_w`] for semantics.
700    fn max_width(self, w: Length) -> Element {
701        self.into().max_width(w)
702    }
703
704    /// Set maximum height constraint. See [`LayoutConstraints::max_h`] for semantics.
705    fn max_height(self, h: Length) -> Element {
706        self.into().max_height(h)
707    }
708
709    /// Mark whether this element reflows when its main-axis allocation changes.
710    fn reflows(self, reflows: bool) -> Element {
711        self.into().reflows(reflows)
712    }
713
714    /// Set this element's stack shrink priority.
715    fn shrink_priority(self, priority: ShrinkPriority) -> Element {
716        self.into().shrink_priority(priority)
717    }
718}
719
720impl<T> IntoElement for T where T: Into<Element> + Sized {}
721
722impl Default for Element {
723    /// Creates a zero-cost placeholder element (empty [`Spacer`]).
724    ///
725    /// This is primarily used by [`std::mem::take`] inside theme application
726    /// to avoid deep-cloning children.
727    fn default() -> Self {
728        Element::new(ElementKind::Spacer(Spacer::default()))
729    }
730}
731
732#[cfg(test)]
733mod tests {
734    use super::{Element, ElementKind, Group, MeasureCacheEntry};
735    use crate::callback::ScopeId;
736    use crate::core::memo::Memo;
737    use crate::overlay::{DismissPolicy, OverlayLayer, OverlayPlacement, PointerCapture, Portal};
738    use crate::style::Theme;
739    use crate::widgets::{ContextProvider, Divider, Grid, Splitter, Text, ThemeProvider};
740
741    const TARGET_SCOPE: ScopeId = ScopeId(42);
742
743    fn scoped_group(label: &str) -> Element {
744        Element::new(ElementKind::Group(Group {
745            scope: TARGET_SCOPE,
746            child: Box::new(Text::new(label).into()),
747        }))
748    }
749
750    fn replacement() -> Element {
751        Text::new("new").into()
752    }
753
754    fn mark_caches(element: &Element) {
755        element.layout_hash_cache.set(Some(99));
756        element.measure_cache.set([
757            Some(MeasureCacheEntry {
758                max_w: Some(1),
759                max_h: Some(1),
760                size: (1, 1),
761            }),
762            None,
763        ]);
764        element.split_wrap_probe_cache.set(Some(true));
765    }
766
767    fn text_contents(element: &Element, out: &mut Vec<String>) {
768        if let ElementKind::Text(text) = &element.kind {
769            out.push(text.plain_content());
770        }
771        for child in element.kind.children() {
772            text_contents(child, out);
773        }
774    }
775
776    fn group_for_scope(element: &Element, scope: ScopeId) -> Option<&Element> {
777        if let ElementKind::Group(group) = &element.kind
778            && group.scope == scope
779        {
780            return Some(element);
781        }
782        element
783            .kind
784            .children()
785            .into_iter()
786            .find_map(|child| group_for_scope(child, scope))
787    }
788
789    fn assert_replaces_through_wrapper(mut root: Element) {
790        mark_caches(&root);
791        let group = group_for_scope(&root, TARGET_SCOPE).expect("target group before replace");
792        mark_caches(group);
793
794        let mut replacement = Some(replacement());
795        assert!(
796            root.replace_group_child_by_scope(TARGET_SCOPE, &mut replacement),
797            "scope replacement should succeed"
798        );
799        assert!(replacement.is_none());
800
801        let mut texts = Vec::new();
802        text_contents(&root, &mut texts);
803        assert!(texts.contains(&"new".to_string()), "texts: {texts:?}");
804        assert!(!texts.contains(&"old".to_string()), "texts: {texts:?}");
805        assert_eq!(root.layout_hash_cache.get(), None);
806        assert_eq!(root.measure_cache.get(), [None, None]);
807        assert_eq!(root.split_wrap_probe_cache.get(), None);
808
809        let group = group_for_scope(&root, TARGET_SCOPE).expect("target group after replace");
810        assert_eq!(group.layout_hash_cache.get(), None);
811        assert_eq!(group.measure_cache.get(), [None, None]);
812        assert_eq!(group.split_wrap_probe_cache.get(), None);
813    }
814
815    #[test]
816    fn replace_group_child_traverses_children_mut_wrappers() {
817        let portal = Element::new(ElementKind::Portal(Portal {
818            layer: OverlayLayer::Modal,
819            content: Box::new(scoped_group("old")),
820            placement: OverlayPlacement::Center,
821            dismiss_policy: DismissPolicy::None,
822            on_close: None,
823            backdrop: None,
824            captures_focus: false,
825            captures_pointer: PointerCapture::None,
826        }));
827
828        let cases: Vec<(&str, Element)> = vec![
829            ("grid", Grid::new().child(scoped_group("old")).into()),
830            (
831                "splitter",
832                Splitter::vertical().child(scoped_group("old")).into(),
833            ),
834            (
835                "divider_label",
836                Divider::horizontal().label(scoped_group("old")).into(),
837            ),
838            (
839                "theme_provider",
840                ThemeProvider::new(Theme::default())
841                    .child(scoped_group("old"))
842                    .into(),
843            ),
844            (
845                "context_provider",
846                ContextProvider::new(7u32).child(scoped_group("old")).into(),
847            ),
848            ("portal", portal),
849        ];
850
851        for (_name, root) in cases {
852            assert_replaces_through_wrapper(root);
853        }
854    }
855
856    #[test]
857    fn replace_group_child_does_not_expand_unresolved_memo_builders() {
858        let mut root = Memo::with_call_site(1, 1).build(|| scoped_group("old"));
859        let mut replacement = Some(replacement());
860        assert!(!root.replace_group_child_by_scope(TARGET_SCOPE, &mut replacement));
861
862        let mut texts = Vec::new();
863        text_contents(
864            &replacement.expect("unexpanded memo keeps replacement"),
865            &mut texts,
866        );
867        assert_eq!(texts, ["new".to_string()]);
868    }
869}