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