Skip to main content

frust_core/
view.rs

1//! Layer 1: the declarative [`View`] trait.
2//!
3//! Views are cheap, short-lived descriptors produced by a root component's `Component::build`
4//! closure, a pure function of application state (`fn build(&mut State) -> impl View<State>`).
5//! They are *not* the retained tree — re-running the build closure on every state
6//! mutation must stay cheap by construction.
7//!
8//! The lifecycle mirrors `xilem_core`'s proven `View` design
9//! (`build`/`rebuild`/`teardown`/`message`) but owns its implementation: no
10//! xilem/masonry dependency. The `Action` generic is intentionally omitted for
11//! v0 — messages route directly against `State`.
12
13use std::any::Any;
14
15use crate::widget::Widget;
16
17/// Bitflags describing what work a [`View::rebuild`] pass invalidated.
18///
19/// Combine with `|`. `LAYOUT` implies a subsequent paint, but the flags are
20/// stored orthogonally so a caller can distinguish "geometry changed" from
21/// "only pixels changed"; use [`ChangeFlags::needs_paint`] for the common
22/// "does anything need repainting?" query.
23#[derive(Clone, Copy, PartialEq, Eq, Debug)]
24pub struct ChangeFlags(u8);
25
26impl ChangeFlags {
27    /// Nothing changed; no downstream work required.
28    pub const NONE: Self = Self(0);
29    /// The widget must be re-painted.
30    pub const PAINT: Self = Self(0b0000_0001);
31    /// The widget must be re-laid-out (and therefore re-painted).
32    pub const LAYOUT: Self = Self(0b0000_0010);
33
34    /// Whether `self` contains every bit set in `other`.
35    pub const fn contains(self, other: Self) -> bool {
36        (self.0 & other.0) == other.0
37    }
38
39    /// The union of two flag sets.
40    pub const fn union(self, other: Self) -> Self {
41        Self(self.0 | other.0)
42    }
43
44    /// Whether no flags are set.
45    pub const fn is_empty(self) -> bool {
46        self.0 == 0
47    }
48
49    /// Whether this change requires a repaint (either `PAINT` or `LAYOUT`).
50    pub const fn needs_paint(self) -> bool {
51        !self.is_empty()
52    }
53
54    /// Whether this change requires a relayout.
55    pub const fn needs_layout(self) -> bool {
56        self.contains(Self::LAYOUT)
57    }
58}
59
60impl core::ops::BitOr for ChangeFlags {
61    type Output = Self;
62    fn bitor(self, rhs: Self) -> Self {
63        self.union(rhs)
64    }
65}
66
67impl core::ops::BitOrAssign for ChangeFlags {
68    fn bitor_assign(&mut self, rhs: Self) {
69        self.0 |= rhs.0;
70    }
71}
72
73impl Default for ChangeFlags {
74    fn default() -> Self {
75        Self::NONE
76    }
77}
78
79/// A stable identity for a node in the widget tree.
80///
81/// Wraps the `u64` node id used by `tree_arena`. Allocated by [`BuildCtx`].
82#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
83pub struct WidgetId(pub u64);
84
85impl From<WidgetId> for u64 {
86    fn from(id: WidgetId) -> Self {
87        id.0
88    }
89}
90
91/// Context threaded through [`View::build`] / [`View::rebuild`].
92///
93/// Three responsibilities: allocating unique [`WidgetId`]s (it borrows the id
94/// counter owned by the render root so ids stay monotonic across passes),
95/// carrying the **effective focus chain** — whether every link from the root down
96/// to the node being built/rebuilt/torn down is focused (see
97/// [`BuildCtx::has_focus`]) — and carrying the **element-replaced signal** a
98/// same-typed element swap raises for its enclosing reconciler (see
99/// [`BuildCtx::mark_element_replaced`]).
100pub struct BuildCtx<'a> {
101    next_id: &'a mut u64,
102    /// The effective focus chain: `true` only while every recorded focus link
103    /// from the root down to the current node is set. See
104    /// [`BuildCtx::has_focus`].
105    has_focus: bool,
106    /// Set by a view whose `rebuild` replaced its element's widget without
107    /// changing the element *type*; consumed by the nearest enclosing
108    /// [`BuildCtx::track_element_replaced`] scope. See
109    /// [`BuildCtx::mark_element_replaced`].
110    element_replaced: bool,
111}
112
113impl<'a> BuildCtx<'a> {
114    /// Create a context borrowing the render root's id counter.
115    ///
116    /// The focus chain seeds **`true`** — "unknown, assume live". A caller that
117    /// does not thread the chain therefore keeps the pre-chain behavior (every
118    /// reconciler under it treats a `focused` pod as the live one) instead of
119    /// silently suppressing a release it owed: over-releasing costs the user one
120    /// tap, while under-releasing strands the shell's IME surface over a widget
121    /// that no longer exists and nothing on an idle screen ever corrects it (the
122    /// same default-to-must-run rule the frame gate follows). The seams that
123    /// *know* the real value — [`RenderRoot::rebuild`](crate::app::RenderRoot)'s
124    /// view diff and [`ComponentWidget`](crate::component::ComponentWidget)'s
125    /// inner context — set it explicitly with [`BuildCtx::set_has_focus`].
126    pub fn new(next_id: &'a mut u64) -> Self {
127        Self {
128            next_id,
129            has_focus: true,
130            element_replaced: false,
131        }
132    }
133
134    /// Allocate a fresh, unique widget id.
135    pub fn alloc_id(&mut self) -> WidgetId {
136        *self.next_id += 1;
137        WidgetId(*self.next_id)
138    }
139
140    /// Whether the recorded focus path is live all the way from the root to the
141    /// node currently being built/rebuilt/torn down — the rebuild-pass mirror of
142    /// [`PaintCtx::has_focus`](crate::widget::PaintCtx::has_focus), composed the
143    /// same way (`self.focused && ctx.has_focus()`).
144    ///
145    /// This is what makes a `focused` [`ChildPod`](crate::widget::ChildPod) flag
146    /// *deep inside a blurred branch* harmless. A container-routed blur clears the
147    /// focus link at the nearest common ancestor only, so flags below it
148    /// legitimately go stale until focus next enters that subtree; a pod under a
149    /// cleared link sees `has_focus() == false` here, exactly as it sees `false`
150    /// in paint and exactly as focus-routed events never reach it. A reconciler
151    /// therefore raises [`mark_focus_orphaned`](crate::event::mark_focus_orphaned)
152    /// only when `ctx.has_focus() && pod.is_focused()` — only when the pod losing
153    /// its identity is the one whose session is actually live.
154    pub fn has_focus(&self) -> bool {
155        self.has_focus
156    }
157
158    /// Seed the effective focus chain (see [`BuildCtx::has_focus`]).
159    ///
160    /// For the two kinds of seam that *start* a chain rather than descend one:
161    /// [`RenderRoot`](crate::app::RenderRoot)'s view diff, which seeds it from the
162    /// root's own session mirror, and a component-style widget that builds an
163    /// inner `BuildCtx` over its own id counter
164    /// ([`ComponentWidget`](crate::component::ComponentWidget) is the in-crate
165    /// one) and must carry its outer chain across that boundary. A container
166    /// descending into a child pod uses [`BuildCtx::with_focus_link`] instead — it
167    /// can only narrow, which is what keeps this an AND-chain.
168    pub fn set_has_focus(&mut self, has_focus: bool) {
169        self.has_focus = has_focus;
170    }
171
172    /// Run `f` with the focus chain extended by one link, restoring the caller's
173    /// chain when it returns.
174    ///
175    /// `link_focused` is the descended-into pod's own
176    /// [`ChildPod::is_focused`](crate::widget::ChildPod::is_focused) flag, so the
177    /// closure sees `self.has_focus() && link_focused`: a cleared link anywhere
178    /// above forces `false` for the whole subtree below it, and no descent can
179    /// ever widen the chain. The rebuild-pass counterpart of
180    /// [`ChildPod::paint_child`](crate::widget::ChildPod::paint_child)'s
181    /// `set_has_focus(self.focused && ctx.has_focus())`, in the scoped-closure
182    /// shape [`SemanticsCtx::descend_into_pod`](crate::semantics::SemanticsCtx)
183    /// already uses for the semantics pass.
184    pub fn with_focus_link<R>(&mut self, link_focused: bool, f: impl FnOnce(&mut Self) -> R) -> R {
185        let outer = self.has_focus;
186        self.has_focus = outer && link_focused;
187        let result = f(self);
188        self.has_focus = outer;
189        result
190    }
191
192    /// Report that this view's `rebuild` **replaced its element's widget while
193    /// keeping the element type** — an enum view switching arms (`Either`,
194    /// `EitherOf3` .. `EitherOf8` in `frust-widgets`) is the shape this exists
195    /// for.
196    ///
197    /// A reconciler detects an [`AnyView`] concrete-type swap by comparing the
198    /// boxed widget's type id across the rebuild; on a swap it drops the child
199    /// pod's capture (`active`) and focus (`focused`) links, because the fresh
200    /// widget never saw the `Down` and never claimed focus. An enum element keeps
201    /// one static type across an arm swap, so that comparison cannot see it.
202    /// Calling this makes the enclosing reconciler treat the rebuild exactly like
203    /// a concrete-type swap.
204    ///
205    /// **Scope rule.** The nearest enclosing
206    /// [`track_element_replaced`](BuildCtx::track_element_replaced) scope consumes
207    /// the signal. An enum nested *inline* inside another enum (no pod between
208    /// them) therefore propagates to the outer one's scope — the same pod lost
209    /// its widget — while a pod anywhere inside the swapped subtree runs its own
210    /// scope and consumes its own signal, leaving the outer pod's flags intact.
211    /// A signal raised where no scope encloses it (a root-level enum, or a
212    /// container that rebuilds a child without a pod) is inert or, at worst,
213    /// reaches the next scope up: an over-release, never an under-release.
214    pub fn mark_element_replaced(&mut self) {
215        self.element_replaced = true;
216    }
217
218    /// Run `f` as a **tracking scope** for
219    /// [`mark_element_replaced`](BuildCtx::mark_element_replaced), returning
220    /// `f`'s result and whether anything inside it raised the signal.
221    ///
222    /// The caller's pending value is saved and cleared before `f` runs and
223    /// restored afterwards, so a scope sees only signals raised inside it and a
224    /// signal raised by an earlier sibling is neither consumed nor lost. A
225    /// reconciler wraps each child's nested `rebuild` in this and ORs the result
226    /// into its own type-swap detection.
227    pub fn track_element_replaced<R>(&mut self, f: impl FnOnce(&mut Self) -> R) -> (R, bool) {
228        let outer = std::mem::replace(&mut self.element_replaced, false);
229        let result = f(self);
230        let replaced = std::mem::replace(&mut self.element_replaced, outer);
231        (result, replaced)
232    }
233}
234
235/// A declarative description of a piece of UI.
236///
237/// Each `View` knows how to materialise itself into a retained [`Widget`]
238/// ([`View::build`]) and how to reconcile a previous version of itself against
239/// the live widget ([`View::rebuild`]). `State` is `'static` so views never
240/// capture borrowed data — they are values, re-created every frame.
241pub trait View<State: 'static>: 'static {
242    /// The retained widget this view produces.
243    type Element: Widget;
244
245    /// Materialise a fresh widget for this view.
246    fn build(&self, ctx: &mut BuildCtx<'_>) -> Self::Element;
247
248    /// Reconcile `prev` (the previous view of the same type) against the live
249    /// `element`, mutating it in place and reporting what changed.
250    fn rebuild(
251        &self,
252        prev: &Self,
253        element: &mut Self::Element,
254        ctx: &mut BuildCtx<'_>,
255    ) -> ChangeFlags;
256
257    /// Tear down `element` when this view is being removed.
258    ///
259    /// A no-op for v0 leaf views; kept in the trait so the lifecycle is
260    /// complete and container/removal logic (later phases) has a hook.
261    fn teardown(&self, _element: &mut Self::Element, _ctx: &mut BuildCtx<'_>) {}
262
263    /// Deliver an event message to this view, mutating application state.
264    ///
265    /// Stubbed for v0 (no event routing yet); present so the trait shape is
266    /// stable as event routing is added later.
267    fn message(&self, _element: &mut Self::Element, _state: &mut State) {}
268}
269
270/// Object-safe mirror of [`View`], used only as the erased backing of
271/// [`AnyView`].
272///
273/// The methods mirror `build`/`rebuild`/`teardown` but drop the associated
274/// `Element` type in favour of a `Box<dyn Widget>`, and add [`ErasedView::as_any`]
275/// so a rebuild can downcast the *previous* erased view to detect a
276/// concrete-type change (the xilem `AnyView` trick).
277trait ErasedView<State: 'static>: 'static {
278    /// Materialise a fresh boxed widget for this view.
279    fn dyn_build(&self, ctx: &mut BuildCtx<'_>) -> Box<dyn Widget>;
280
281    /// Reconcile against `prev` (the previous erased view). If `prev` is the same
282    /// concrete type, do a typed in-place rebuild; otherwise tear the old widget
283    /// down and build a fresh one, replacing `element`.
284    fn dyn_rebuild(
285        &self,
286        prev: &dyn ErasedView<State>,
287        element: &mut Box<dyn Widget>,
288        ctx: &mut BuildCtx<'_>,
289    ) -> ChangeFlags;
290
291    /// Tear down `element` (dispatched to the concrete view's `teardown`).
292    fn dyn_teardown(&self, element: &mut Box<dyn Widget>, ctx: &mut BuildCtx<'_>);
293
294    /// Upcast to `&dyn Any` so a rebuild can downcast the previous view.
295    fn as_any(&self) -> &dyn Any;
296}
297
298impl<State: 'static, V: View<State>> ErasedView<State> for V {
299    fn dyn_build(&self, ctx: &mut BuildCtx<'_>) -> Box<dyn Widget> {
300        Box::new(self.build(ctx))
301    }
302
303    fn dyn_rebuild(
304        &self,
305        prev: &dyn ErasedView<State>,
306        element: &mut Box<dyn Widget>,
307        ctx: &mut BuildCtx<'_>,
308    ) -> ChangeFlags {
309        if let Some(prev) = prev.as_any().downcast_ref::<V>() {
310            // Same concrete view type: recover the typed element and rebuild in
311            // place. The element's erased type is `V::Element` because it was
312            // produced by this view's `build` (via `dyn_build`).
313            let element = (**element)
314                .downcast_mut::<V::Element>()
315                .expect("erased element type matches its originating view");
316            self.rebuild(prev, element, ctx)
317        } else {
318            // Concrete type changed: tear the old widget down through the *old*
319            // view, then build a fresh one and swap it in.
320            prev.dyn_teardown(element, ctx);
321            *element = self.dyn_build(ctx);
322            ChangeFlags::LAYOUT | ChangeFlags::PAINT
323        }
324    }
325
326    fn dyn_teardown(&self, element: &mut Box<dyn Widget>, ctx: &mut BuildCtx<'_>) {
327        if let Some(element) = (**element).downcast_mut::<V::Element>() {
328            self.teardown(element, ctx);
329        }
330    }
331
332    fn as_any(&self) -> &dyn Any {
333        self
334    }
335}
336
337/// A type-erased [`View`]: lets a piece of UI change its concrete view type
338/// between frames (e.g. a conditional `if cond { text(..) } else { button(..) }`)
339/// while still fitting the statically-typed rebuild machinery.
340///
341/// Rebuild follows the xilem `AnyView` pattern: the previous view is downcast to
342/// detect whether the concrete type is unchanged. Same type → a typed in-place
343/// rebuild; different type → the old widget is torn down and a fresh one built
344/// and swapped in (signalling `LAYOUT | PAINT`). Its `Element` is a
345/// `Box<dyn Widget>`, which implements [`Widget`] through the blanket impl so it
346/// satisfies `View::Element: Widget`.
347///
348/// Erasure is idempotent, and the generic builders (`child`, tuples such as
349/// `Column((a, b))`, ..) accept any [`View`] and erase internally, so app code
350/// needs `AnyView` only for stored views, branches of more than eight arms, and
351/// genuinely heterogeneous dynamic lists (see [`any`]). Branches are written
352/// with `either(..)` (two arms) or `EitherOf3`..`EitherOf8` (three to eight),
353/// which keep every arm statically typed and do not erase.
354pub struct AnyView<State: 'static> {
355    inner: Box<dyn ErasedView<State>>,
356}
357
358impl<State: 'static> AnyView<State> {
359    /// Erase `view` into an `AnyView`.
360    ///
361    /// Erasure is idempotent: if `view` is already an `AnyView<State>`, it is
362    /// returned unchanged — `any(any(v))` and `AnyView::new(any_view)` do not box
363    /// twice.
364    pub fn new<V: View<State>>(view: V) -> Self {
365        // Slot holds the value so it can be moved out through `&mut dyn Any`'s borrow.
366        // `take()` extracts it after the downcast_mut succeeds, or the original is boxed.
367        let mut slot = Some(view);
368        if let Some(erased) =
369            (&mut slot as &mut dyn core::any::Any).downcast_mut::<Option<AnyView<State>>>()
370        {
371            return erased.take().expect("slot is Some");
372        }
373        Self {
374            inner: Box::new(slot.expect("slot is Some")),
375        }
376    }
377}
378
379/// Erase `view` into an [`AnyView`] — the free-function spelling of
380/// [`AnyView::new`], mirroring the `text(..)`/`button(..)` view-fn vocabulary.
381///
382/// Erasure is idempotent: `any(any(v))` returns the inner `AnyView` unchanged,
383/// not a doubly-boxed wrapper. This makes double erasure safe for swap detection
384/// in containers like `inflexible(any(x))`.
385///
386/// Because of that, writing `any(..)` around an argument is redundant in
387/// 0.6: the generic builders (`child`, tuples such as `Column((a, b))`, ..)
388/// accept any [`View`] and erase internally. `either(..)` and
389/// `EitherOf3`..`EitherOf8` are the no-erasure way to write a branch of two to
390/// eight arms: they keep every arm statically typed. Reach for `any` only to
391/// *store* a view (a struct field, a `Vec` of builders), for a branch with more
392/// than eight arms, or for a genuinely heterogeneous dynamic list.
393pub fn any<State: 'static, V: View<State>>(view: V) -> AnyView<State> {
394    AnyView::new(view)
395}
396
397impl<State: 'static> View<State> for AnyView<State> {
398    type Element = Box<dyn Widget>;
399
400    fn build(&self, ctx: &mut BuildCtx<'_>) -> Self::Element {
401        self.inner.dyn_build(ctx)
402    }
403
404    fn rebuild(
405        &self,
406        prev: &Self,
407        element: &mut Self::Element,
408        ctx: &mut BuildCtx<'_>,
409    ) -> ChangeFlags {
410        self.inner.dyn_rebuild(prev.inner.as_ref(), element, ctx)
411    }
412
413    fn teardown(&self, element: &mut Self::Element, ctx: &mut BuildCtx<'_>) {
414        self.inner.dyn_teardown(element, ctx);
415    }
416}
417
418/// A child sequence: what a container's child-list parameter accepts, so app
419/// code passes heterogeneous children without writing [`any`] at the call site.
420///
421/// A container takes `children: impl ViewSeq<State, M>` (generic over the
422/// inference marker `M`, which callers never name) and calls
423/// [`ViewSeq::extend_views`] once to push every element into the erased child
424/// vector it already stores (`Vec<AnyView<State>>`). That push is the **one place
425/// erasure happens**: each element goes through [`AnyView::new`], which is
426/// idempotent, so an element that is already an [`AnyView`] is moved in as-is —
427/// never boxed twice — and no intermediate collection is built.
428///
429/// Implemented for:
430///
431/// - any single [`View`] (one element; [`AnyView`] included),
432/// - tuples `(S1, .., Sn)` for `n = 1..=12`, where every element is itself a
433///   `ViewSeq`, so elements of *different* concrete types mix freely and tuples
434///   nest — a nested tuple flattens in order,
435/// - `Vec<S>` and `[S; N]` (homogeneous; the element is any `ViewSeq`),
436/// - `Option<S>` (`None` contributes no element — see below),
437/// - [`views(iter)`](views) for any iterator of views (there is deliberately no
438///   blanket impl over `IntoIterator`: it would overlap the tuple impls).
439///
440/// **Identity is positional.** The reconciler matches children by position (plus
441/// the concrete element type), so a tuple's elements keep their identity only
442/// while their positions do. An `Option` that is `None` contributes nothing, so
443/// every *later* sibling moves up one slot when it flips — exactly like a
444/// conditional child added through a builder's `.when_some(..)`/`.when(..)`.
445/// Put optional children last, or use the container's keyed-child API (still a
446/// `Vec`-based affair, not a tuple one) when sibling identity must survive.
447///
448/// **Arity.** A single tuple holds at most 12 elements; beyond that, nest tuples
449/// (`((a, b, .., l), (m, n))`), which flattens to the same order.
450///
451/// **The marker parameter.** `M` exists only so the single-view impl
452/// (`impl<V: View<State>> ViewSeq<State, _> for V`) can coexist with the
453/// container impls — without it the compiler must assume some downstream crate
454/// could implement `View` for a `Vec` or a tuple and rejects the overlap. Each
455/// impl picks a distinct marker from [`seq_marker`], so inference always resolves
456/// it; a function accepting a sequence just declares a generic `M`.
457///
458/// ```
459/// use frust_core::{AnyView, ViewSeq};
460///
461/// // A container-style API: generic over the marker, erasing into one Vec.
462/// fn children<M>(seq: impl ViewSeq<(), M>) -> Vec<AnyView<()>> {
463///     let mut out = Vec::new();
464///     seq.extend_views(&mut out);
465///     out
466/// }
467///
468/// let none = || None::<AnyView<()>>;
469/// let empty = Vec::<AnyView<()>>::new;
470/// // Twelve elements is the widest flat tuple.
471/// let twelve = (
472///     none(), none(), none(), none(), none(), none(),
473///     none(), none(), none(), none(), none(), empty(),
474/// );
475/// assert!(children(twelve).is_empty());
476/// ```
477///
478/// A thirteenth flat element is not a sequence — nest instead:
479///
480/// ```compile_fail,E0277
481/// use frust_core::{AnyView, ViewSeq};
482///
483/// fn children<M>(seq: impl ViewSeq<(), M>) -> Vec<AnyView<()>> {
484///     let mut out = Vec::new();
485///     seq.extend_views(&mut out);
486///     out
487/// }
488///
489/// let none = || None::<AnyView<()>>;
490/// let thirteen = (
491///     none(), none(), none(), none(), none(), none(), none(),
492///     none(), none(), none(), none(), none(), none(),
493/// );
494/// let _ = children(thirteen);
495/// ```
496#[diagnostic::on_unimplemented(
497    message = "this is not a child sequence",
498    note = "a child list is a View, a tuple of up to 12 views (nest tuples beyond 12), an array, a Vec, an Option, or views(iter) for an iterator"
499)]
500pub trait ViewSeq<State: 'static, M> {
501    /// Push every element of this sequence, in order, onto `out` — erasing each
502    /// with [`AnyView::new`] (a no-op for an element already an [`AnyView`]).
503    fn extend_views(self, out: &mut Vec<AnyView<State>>);
504}
505
506/// Inference markers for [`ViewSeq`]'s second parameter.
507///
508/// One marker per impl keeps the single-[`View`] impl from overlapping the
509/// container impls. They are never constructed and callers never name them: an
510/// API accepting a child sequence declares a generic `M` and lets inference pick.
511pub mod seq_marker {
512    use core::marker::PhantomData;
513
514    /// Marks the impl for a single [`View`](super::View).
515    pub enum One {}
516
517    /// Marks the impl for `Vec<S>`, where `S`'s own marker is `M`.
518    pub struct VecOf<M>(PhantomData<fn() -> M>);
519
520    /// Marks the impl for `[S; N]`, where `S`'s own marker is `M`.
521    pub struct ArrayOf<M>(PhantomData<fn() -> M>);
522
523    /// Marks the impl for `Option<S>`, where `S`'s own marker is `M`.
524    pub struct OptionOf<M>(PhantomData<fn() -> M>);
525
526    /// Marks the impl for [`Views`](super::Views), the [`views`](super::views)
527    /// iterator adapter.
528    pub enum Iter {}
529}
530
531impl<State: 'static, V: View<State>> ViewSeq<State, seq_marker::One> for V {
532    fn extend_views(self, out: &mut Vec<AnyView<State>>) {
533        out.push(AnyView::new(self));
534    }
535}
536
537impl<State: 'static, M, S: ViewSeq<State, M>> ViewSeq<State, seq_marker::VecOf<M>> for Vec<S> {
538    fn extend_views(self, out: &mut Vec<AnyView<State>>) {
539        out.reserve(self.len());
540        for seq in self {
541            seq.extend_views(out);
542        }
543    }
544}
545
546impl<State: 'static, M, S: ViewSeq<State, M>, const N: usize> ViewSeq<State, seq_marker::ArrayOf<M>>
547    for [S; N]
548{
549    fn extend_views(self, out: &mut Vec<AnyView<State>>) {
550        out.reserve(N);
551        for seq in self {
552            seq.extend_views(out);
553        }
554    }
555}
556
557impl<State: 'static, M, S: ViewSeq<State, M>> ViewSeq<State, seq_marker::OptionOf<M>>
558    for Option<S>
559{
560    fn extend_views(self, out: &mut Vec<AnyView<State>>) {
561        if let Some(seq) = self {
562            seq.extend_views(out);
563        }
564    }
565}
566
567/// Implements [`ViewSeq`] for one tuple arity per `(Element Marker index, ..)`
568/// group; the tuple's marker is the tuple of its elements' markers.
569macro_rules! impl_view_seq_for_tuples {
570    ($( ($($S:ident $M:ident $idx:tt),+) )+) => {$(
571        impl<State: 'static, $($M, $S: ViewSeq<State, $M>),+> ViewSeq<State, ($($M,)+)>
572            for ($($S,)+)
573        {
574            fn extend_views(self, out: &mut Vec<AnyView<State>>) {
575                $(self.$idx.extend_views(out);)+
576            }
577        }
578    )+};
579}
580
581impl_view_seq_for_tuples! {
582    (S0 M0 0)
583    (S0 M0 0, S1 M1 1)
584    (S0 M0 0, S1 M1 1, S2 M2 2)
585    (S0 M0 0, S1 M1 1, S2 M2 2, S3 M3 3)
586    (S0 M0 0, S1 M1 1, S2 M2 2, S3 M3 3, S4 M4 4)
587    (S0 M0 0, S1 M1 1, S2 M2 2, S3 M3 3, S4 M4 4, S5 M5 5)
588    (S0 M0 0, S1 M1 1, S2 M2 2, S3 M3 3, S4 M4 4, S5 M5 5, S6 M6 6)
589    (S0 M0 0, S1 M1 1, S2 M2 2, S3 M3 3, S4 M4 4, S5 M5 5, S6 M6 6, S7 M7 7)
590    (S0 M0 0, S1 M1 1, S2 M2 2, S3 M3 3, S4 M4 4, S5 M5 5, S6 M6 6, S7 M7 7, S8 M8 8)
591    (S0 M0 0, S1 M1 1, S2 M2 2, S3 M3 3, S4 M4 4, S5 M5 5, S6 M6 6, S7 M7 7, S8 M8 8,
592        S9 M9 9)
593    (S0 M0 0, S1 M1 1, S2 M2 2, S3 M3 3, S4 M4 4, S5 M5 5, S6 M6 6, S7 M7 7, S8 M8 8,
594        S9 M9 9, S10 M10 10)
595    (S0 M0 0, S1 M1 1, S2 M2 2, S3 M3 3, S4 M4 4, S5 M5 5, S6 M6 6, S7 M7 7, S8 M8 8,
596        S9 M9 9, S10 M10 10, S11 M11 11)
597}
598
599/// The [`ViewSeq`] adapter for an iterator of views, built by [`views`].
600pub struct Views<I>(I);
601
602impl<State: 'static, I> ViewSeq<State, seq_marker::Iter> for Views<I>
603where
604    I: IntoIterator,
605    I::Item: View<State>,
606{
607    fn extend_views(self, out: &mut Vec<AnyView<State>>) {
608        out.extend(self.0.into_iter().map(AnyView::new));
609    }
610}
611
612/// Wrap an iterator of views as a [`ViewSeq`] — the child-sequence spelling of
613/// "one child per item", e.g. `views(items.iter().map(|i| text(&i.name)))`.
614///
615/// `ViewSeq` has no blanket impl over `IntoIterator` (it would overlap the tuple
616/// impls), so an iterator goes through this adapter; a `Vec` or array needs no
617/// wrapping. Each item is erased once, as it is pushed.
618pub fn views<State: 'static, I>(iter: I) -> Views<I>
619where
620    I: IntoIterator,
621    I::Item: View<State>,
622{
623    Views(iter)
624}
625
626#[cfg(test)]
627mod tests {
628    use super::*;
629    use crate::layout::BoxConstraints;
630    use crate::widget::{LayoutCtx, PaintCtx, PaintScene, Widget};
631    use kurbo::Size;
632    use std::cell::Cell;
633    use std::rc::Rc;
634
635    // --- AnyView fixtures: two distinct view/widget type pairs. ---
636
637    struct WidgetA {
638        n: u32,
639    }
640    impl Widget for WidgetA {
641        fn layout(&mut self, _ctx: &mut LayoutCtx, _bc: &BoxConstraints) -> Size {
642            Size::new(self.n as f64, 1.0)
643        }
644        fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
645    }
646
647    struct WidgetB;
648    impl Widget for WidgetB {
649        fn layout(&mut self, _ctx: &mut LayoutCtx, _bc: &BoxConstraints) -> Size {
650            Size::new(99.0, 99.0)
651        }
652        fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
653    }
654
655    struct ViewA {
656        n: u32,
657        torn: Rc<Cell<u32>>,
658    }
659    impl View<()> for ViewA {
660        type Element = WidgetA;
661        fn build(&self, _ctx: &mut BuildCtx<'_>) -> WidgetA {
662            WidgetA { n: self.n }
663        }
664        fn rebuild(
665            &self,
666            prev: &Self,
667            element: &mut WidgetA,
668            _ctx: &mut BuildCtx<'_>,
669        ) -> ChangeFlags {
670            if prev.n != self.n {
671                element.n = self.n;
672                ChangeFlags::PAINT
673            } else {
674                ChangeFlags::NONE
675            }
676        }
677        fn teardown(&self, _element: &mut WidgetA, _ctx: &mut BuildCtx<'_>) {
678            self.torn.set(self.torn.get() + 1);
679        }
680    }
681
682    struct ViewB;
683    impl View<()> for ViewB {
684        type Element = WidgetB;
685        fn build(&self, _ctx: &mut BuildCtx<'_>) -> WidgetB {
686            WidgetB
687        }
688        fn rebuild(
689            &self,
690            _prev: &Self,
691            _element: &mut WidgetB,
692            _ctx: &mut BuildCtx<'_>,
693        ) -> ChangeFlags {
694            ChangeFlags::NONE
695        }
696    }
697
698    #[test]
699    fn any_view_same_type_rebuilds_in_place() {
700        let torn = Rc::new(Cell::new(0));
701        let mut counter = 0u64;
702        let mut ctx = BuildCtx::new(&mut counter);
703
704        let prev = any(ViewA {
705            n: 1,
706            torn: torn.clone(),
707        });
708        let mut element = prev.build(&mut ctx);
709
710        let next = any(ViewA {
711            n: 2,
712            torn: torn.clone(),
713        });
714        let flags = next.rebuild(&prev, &mut element, &mut ctx);
715
716        // Same concrete type → typed in-place rebuild, no teardown.
717        assert_eq!(flags, ChangeFlags::PAINT);
718        assert_eq!(torn.get(), 0);
719        let a = (*element)
720            .downcast_mut::<WidgetA>()
721            .expect("still a WidgetA");
722        assert_eq!(a.n, 2);
723    }
724
725    #[test]
726    fn any_view_type_swap_tears_down_and_replaces() {
727        let torn = Rc::new(Cell::new(0));
728        let mut counter = 0u64;
729        let mut ctx = BuildCtx::new(&mut counter);
730
731        let prev = any(ViewA {
732            n: 7,
733            torn: torn.clone(),
734        });
735        let mut element = prev.build(&mut ctx);
736
737        let next = any(ViewB);
738        let flags = next.rebuild(&prev, &mut element, &mut ctx);
739
740        // Concrete type changed → the old view's teardown ran and the widget was
741        // replaced with the new type.
742        assert_eq!(flags, ChangeFlags::LAYOUT | ChangeFlags::PAINT);
743        assert_eq!(torn.get(), 1, "old view should be torn down exactly once");
744        assert!((*element).downcast_mut::<WidgetB>().is_some());
745        assert!((*element).downcast_mut::<WidgetA>().is_none());
746    }
747
748    #[test]
749    fn any_view_element_works_inside_a_child_pod() {
750        // Criterion 3: `Box<dyn Widget>` implements `Widget` — an AnyView's boxed
751        // element drives layout when nested in a container's ChildPod.
752        use crate::widget::ChildPod;
753
754        let torn = Rc::new(Cell::new(0));
755        let mut counter = 0u64;
756        let mut ctx = BuildCtx::new(&mut counter);
757
758        let view = any(ViewA { n: 5, torn });
759        let element: Box<dyn Widget> = view.build(&mut ctx); // Box<dyn Widget>
760
761        // Nest the boxed widget inside a ChildPod (double-boxed): the blanket
762        // `Widget for Box<dyn Widget>` impl forwards layout through both layers.
763        let mut pod = ChildPod::new(Box::new(element));
764        let mut lctx = LayoutCtx::new();
765        let size = pod.layout_child(&mut lctx, &BoxConstraints::loose(Size::new(100.0, 100.0)));
766        assert_eq!(size, Size::new(5.0, 1.0));
767    }
768
769    #[test]
770    fn change_flags_union_and_contains() {
771        let both = ChangeFlags::PAINT | ChangeFlags::LAYOUT;
772        assert!(both.contains(ChangeFlags::PAINT));
773        assert!(both.contains(ChangeFlags::LAYOUT));
774        assert!(both.needs_layout());
775        assert!(both.needs_paint());
776
777        assert!(ChangeFlags::NONE.is_empty());
778        assert!(!ChangeFlags::NONE.needs_paint());
779
780        let paint_only = ChangeFlags::PAINT;
781        assert!(paint_only.needs_paint());
782        assert!(!paint_only.needs_layout());
783    }
784
785    #[test]
786    fn change_flags_bitor_assign() {
787        let mut f = ChangeFlags::NONE;
788        f |= ChangeFlags::PAINT;
789        assert!(f.contains(ChangeFlags::PAINT));
790        assert!(!f.contains(ChangeFlags::LAYOUT));
791    }
792
793    #[test]
794    fn double_erasure_is_idempotent() {
795        // Criterion: `any(any(view))` returns the inner `AnyView` unchanged,
796        // not a doubly-boxed wrapper. The element's TypeId matches the inner
797        // view's concrete type, not `Box<dyn Widget>`.
798        let torn = Rc::new(Cell::new(0));
799        let mut counter = 0u64;
800        let mut ctx = BuildCtx::new(&mut counter);
801
802        let double_erased = any(any(ViewA {
803            n: 5,
804            torn: torn.clone(),
805        }));
806        let mut element = double_erased.build(&mut ctx);
807
808        // The element should downcast directly to WidgetA, not to Box<dyn Widget>
809        // as it would if double-erased.
810        let a = (*element)
811            .downcast_mut::<WidgetA>()
812            .expect("double-erased any(any(ViewA)) should have WidgetA element directly");
813        assert_eq!(a.n, 5);
814
815        // Single-erasure behavior unchanged: rebuild with same type reports no swap
816        let double_erased_2 = any(any(ViewA {
817            n: 10,
818            torn: torn.clone(),
819        }));
820        let flags = double_erased_2.rebuild(&double_erased, &mut element, &mut ctx);
821
822        // Same concrete type → typed in-place rebuild, no teardown.
823        assert_eq!(flags, ChangeFlags::PAINT);
824        assert_eq!(torn.get(), 0, "no teardown on same-type rebuild");
825        let a = (*element)
826            .downcast_mut::<WidgetA>()
827            .expect("still a WidgetA after rebuild");
828        assert_eq!(a.n, 10);
829    }
830
831    #[test]
832    fn build_ctx_allocates_unique_ids() {
833        let mut counter = 0;
834        let mut ctx = BuildCtx::new(&mut counter);
835        let a = ctx.alloc_id();
836        let b = ctx.alloc_id();
837        assert_ne!(a, b);
838        assert_eq!(u64::from(a), 1);
839        assert_eq!(u64::from(b), 2);
840    }
841
842    #[test]
843    fn build_ctx_focus_chain_only_narrows_and_restores() {
844        let mut counter = 0;
845        let mut ctx = BuildCtx::new(&mut counter);
846        assert!(
847            ctx.has_focus(),
848            "an unseeded context assumes a live chain (see BuildCtx::new)"
849        );
850
851        // A focused link keeps a live chain live...
852        ctx.with_focus_link(true, |ctx| assert!(ctx.has_focus()));
853        // ...and the descent is scoped: the caller's chain comes back.
854        assert!(ctx.has_focus());
855
856        // A cleared link closes the chain for the whole subtree below it —
857        // including a *focused* link nested under the cleared one, which is the
858        // stale-flag-below-a-blurred-ancestor case in one line.
859        ctx.with_focus_link(false, |ctx| {
860            assert!(!ctx.has_focus());
861            ctx.with_focus_link(true, |ctx| {
862                assert!(
863                    !ctx.has_focus(),
864                    "no descent may widen the chain a cleared ancestor closed"
865                );
866            });
867            assert!(!ctx.has_focus());
868        });
869        assert!(
870            ctx.has_focus(),
871            "the outer chain is restored, not clobbered"
872        );
873
874        // Seeding is the one absolute write (the root / a component boundary).
875        ctx.set_has_focus(false);
876        assert!(!ctx.has_focus());
877        ctx.with_focus_link(true, |ctx| assert!(!ctx.has_focus()));
878    }
879
880    #[test]
881    fn build_ctx_element_replaced_is_consumed_by_the_nearest_scope() {
882        let mut counter = 0;
883        let mut ctx = BuildCtx::new(&mut counter);
884
885        // A scope with nothing raised inside reports nothing.
886        let ((), replaced) = ctx.track_element_replaced(|_| {});
887        assert!(!replaced);
888
889        // A signal raised inside is reported, and the result passes through.
890        let (n, replaced) = ctx.track_element_replaced(|ctx| {
891            ctx.mark_element_replaced();
892            7
893        });
894        assert_eq!(n, 7);
895        assert!(replaced);
896
897        // The nearest scope consumes it: an inner scope (a pod inside the
898        // subtree) takes its own signal, and the outer scope sees none.
899        let (inner, outer) = ctx.track_element_replaced(|ctx| {
900            let ((), inner) = ctx.track_element_replaced(|ctx| ctx.mark_element_replaced());
901            inner
902        });
903        assert!(inner, "the inner scope consumes its own signal");
904        assert!(!outer, "a consumed signal does not reach the outer scope");
905
906        // A pending signal raised outside any scope is neither consumed nor lost
907        // by a sibling's scope: it is restored when that scope returns.
908        ctx.mark_element_replaced();
909        let ((), sibling) = ctx.track_element_replaced(|_| {});
910        assert!(!sibling, "a scope sees only signals raised inside it");
911        let ((), outer) = ctx.track_element_replaced(|ctx| {
912            ctx.mark_element_replaced();
913        });
914        assert!(outer);
915        // The originally pending value came back after both scopes.
916        let ((), _) = ctx.track_element_replaced(|_| {});
917        assert!(
918            std::mem::take(&mut ctx.element_replaced),
919            "the caller's pending signal is restored, not clobbered"
920        );
921    }
922
923    // --- ViewSeq: child sequences erase once, in order. ---
924
925    fn seq<M>(children: impl ViewSeq<(), M>) -> Vec<AnyView<()>> {
926        let mut out = Vec::new();
927        children.extend_views(&mut out);
928        out
929    }
930
931    fn a(n: u32) -> ViewA {
932        ViewA {
933            n,
934            torn: Rc::new(Cell::new(0)),
935        }
936    }
937
938    /// Build each erased child and report it: `WidgetA`'s `n`, or 99 for a
939    /// `WidgetB`. The element is downcast *directly*, so a doubly-boxed child
940    /// (whose element would be a `Box<dyn Widget>`) fails the expect.
941    fn tags(children: &[AnyView<()>]) -> Vec<u32> {
942        let mut counter = 0u64;
943        let mut ctx = BuildCtx::new(&mut counter);
944        children
945            .iter()
946            .map(|child| {
947                let mut element = child.build(&mut ctx);
948                if let Some(a) = (*element).downcast_mut::<WidgetA>() {
949                    a.n
950                } else {
951                    (*element)
952                        .downcast_mut::<WidgetB>()
953                        .expect("element is the concrete widget, not a re-boxed one");
954                    99
955                }
956            })
957            .collect()
958    }
959
960    #[test]
961    fn view_seq_single_view_is_one_element() {
962        assert_eq!(tags(&seq(a(4))), vec![4]);
963    }
964
965    #[test]
966    fn view_seq_tuple_of_mixed_views_keeps_order() {
967        let children = seq((a(1), ViewB, a(3)));
968        assert_eq!(children.len(), 3);
969        assert_eq!(tags(&children), vec![1, 99, 3]);
970    }
971
972    #[test]
973    fn view_seq_nested_tuples_flatten_in_order() {
974        let children = seq(((a(1), (ViewB, a(2))), a(3), (a(4),)));
975        assert_eq!(tags(&children), vec![1, 99, 2, 3, 4]);
976    }
977
978    #[test]
979    fn view_seq_vec_array_and_option_counts() {
980        assert_eq!(tags(&seq(vec![a(1), a(2)])), vec![1, 2]);
981        assert!(seq(Vec::<ViewB>::new()).is_empty());
982        assert_eq!(tags(&seq([a(5), a(6), a(7)])), vec![5, 6, 7]);
983        assert_eq!(tags(&seq(Some(a(8)))), vec![8]);
984        assert!(seq(None::<ViewA>).is_empty());
985        // `None` contributes nothing, so a later sibling moves up a slot.
986        assert_eq!(tags(&seq((None::<ViewA>, ViewB))), vec![99]);
987        // Container elements may themselves be sequences.
988        assert_eq!(
989            tags(&seq(vec![(a(1), ViewB), (a(2), ViewB)])),
990            vec![1, 99, 2, 99]
991        );
992    }
993
994    #[test]
995    fn view_seq_does_not_rebox_an_any_view_element() {
996        // Erasure is idempotent: an element already an `AnyView` is moved into
997        // the child vector as-is, so its element is still the concrete widget.
998        let children = seq((any(a(1)), any(any(ViewB)), vec![any(a(2))]));
999        assert_eq!(tags(&children), vec![1, 99, 2]);
1000    }
1001
1002    #[test]
1003    fn view_seq_views_adapter_maps_an_iterator() {
1004        let children = seq(views((1..=3).map(a)));
1005        assert_eq!(tags(&children), vec![1, 2, 3]);
1006        // The adapter is itself a sequence element.
1007        let children = seq((ViewB, views([4, 5].into_iter().map(a))));
1008        assert_eq!(tags(&children), vec![99, 4, 5]);
1009    }
1010
1011    #[test]
1012    fn view_seq_twelve_tuple_compiles_and_nesting_goes_beyond() {
1013        let twelve = (
1014            a(1),
1015            a(2),
1016            a(3),
1017            a(4),
1018            a(5),
1019            a(6),
1020            a(7),
1021            a(8),
1022            a(9),
1023            a(10),
1024            a(11),
1025            a(12),
1026        );
1027        assert_eq!(tags(&seq(twelve)), (1..=12).collect::<Vec<_>>());
1028        // Past 12, nest: the order is the flattened order.
1029        let thirteen = (
1030            (
1031                a(1),
1032                a(2),
1033                a(3),
1034                a(4),
1035                a(5),
1036                a(6),
1037                a(7),
1038                a(8),
1039                a(9),
1040                a(10),
1041                a(11),
1042                a(12),
1043            ),
1044            a(13),
1045        );
1046        assert_eq!(tags(&seq(thirteen)), (1..=13).collect::<Vec<_>>());
1047    }
1048}