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}