ailloli_ui_runtime/component/
view.rs

1//! Retained view contract and type-erased view adapters.
2
3use crate::component::context::Context;
4use crate::input::EventCtx;
5use crate::input::{ActivationPolicy, FocusPolicy, HoverCursorRole, InputRole};
6use crate::layout::LayoutEngine;
7use crate::layout::LayoutResult;
8use crate::layout::{LayoutChild, LayoutCtx};
9use crate::scene::PaintCtx;
10use ailloli_ui_core::event::Event;
11use ailloli_ui_core::geometry::{Constraints, Point, Rect};
12use ailloli_ui_core::style::FlexItemStyle;
13use ailloli_ui_core::style::LayoutSizeHint;
14use std::any::TypeId;
15use std::cell::RefCell;
16use std::collections::HashMap;
17use std::marker::PhantomData;
18use std::rc::{Rc, Weak};
19
20/// Declarative view tree node (widget, component, or empty).
21///
22/// A view is transient input to reconciliation. Keys are optional owned UTF-8
23/// strings, children preserve insertion order, and flex/size metadata matters
24/// only when interpreted by a compatible parent widget. Cloning shares widget
25/// and component implementations through `Rc` but recursively clones metadata
26/// and the child vector.
27///
28/// # Examples
29///
30/// ```
31/// use ailloli_ui_runtime::component::{View, ViewKind};
32/// let view = View::<()>::empty().key("root");
33/// assert_eq!(view.key_ref(), Some("root"));
34/// assert!(matches!(view.kind, ViewKind::Empty));
35/// ```
36pub struct View<A> {
37    /// Stable identity for reconciliation (see `IntoViewKeyExt::key`).
38    pub key: Option<String>,
39    /// Empty, widget, or component payload.
40    pub kind: ViewKind<A>,
41    /// Direct declarative children in reconciliation and paint order.
42    pub children: Vec<View<A>>,
43    /// Flex item style when this view is a direct child of `Row` / `Column`.
44    pub flex_item: FlexItemStyle,
45    /// Declarative width/height from builders; used by parent flex for main-axis `Fill`.
46    pub size_hint: LayoutSizeHint,
47}
48
49/// Provides the operations defined for `View<A>`.
50impl<A> View<A> {
51    /// Borrows the reconciliation key as `&str` without allocating.
52    ///
53    /// `None` means positional reconciliation; `Some("")` is a real empty key.
54    ///
55    /// # Examples
56    ///
57    /// ```
58    /// use ailloli_ui_runtime::component::View;
59    /// let view = View::<()>::empty().key(String::from("item-1"));
60    /// assert_eq!(view.key_ref(), Some("item-1"));
61    /// ```
62    pub fn key_ref(&self) -> Option<&str> {
63        self.key.as_deref()
64    }
65}
66
67/// Implements the `Clone` contract for `View<A>`.
68impl<A> Clone for View<A> {
69    /// Produces the clone required by the standard cloning contract.
70    fn clone(&self) -> Self {
71        Self {
72            key: self.key.clone(),
73            kind: self.kind.clone(),
74            children: self.children.clone(),
75            flex_item: self.flex_item,
76            size_hint: self.size_hint,
77        }
78    }
79}
80
81/// Discriminant of a [`View`] node.
82///
83/// Widget and component variants share their implementation through a
84/// UI-thread-local `Rc`; cloning a kind does not clone the underlying object.
85///
86/// # Examples
87///
88/// ```
89/// use ailloli_ui_runtime::component::ViewKind;
90/// let kind: ViewKind<()> = ViewKind::Empty;
91/// assert!(matches!(kind, ViewKind::Empty));
92/// ```
93pub enum ViewKind<A> {
94    /// Placeholder with no content.
95    Empty,
96    /// Built-in or custom widget implementing [`Widget`].
97    Widget(Rc<dyn Widget<A>>),
98    /// Stateful component with a render function.
99    Component(Rc<dyn ComponentNode<A>>),
100}
101
102/// Implements the `Clone` contract for `ViewKind<A>`.
103impl<A> Clone for ViewKind<A> {
104    /// Produces the clone required by the standard cloning contract.
105    fn clone(&self) -> Self {
106        match self {
107            Self::Empty => Self::Empty,
108            Self::Widget(widget) => Self::Widget(widget.clone()),
109            Self::Component(component) => Self::Component(component.clone()),
110        }
111    }
112}
113
114/// Provides the operations defined for `View<A>`.
115impl<A> View<A> {
116    /// Creates an unkeyed empty view with no children and default metadata.
117    ///
118    /// # Examples
119    ///
120    /// ```
121    /// use ailloli_ui_runtime::component::{View, ViewKind};
122    /// let view = View::<()>::empty();
123    /// assert!(view.key.is_none() && view.children.is_empty());
124    /// assert!(matches!(view.kind, ViewKind::Empty));
125    /// ```
126    pub fn empty() -> Self {
127        Self {
128            key: None,
129            kind: ViewKind::Empty,
130            children: Vec::new(),
131            flex_item: FlexItemStyle::default(),
132            size_hint: LayoutSizeHint::default(),
133        }
134    }
135
136    /// Wraps a widget in an unkeyed view with no declarative children.
137    ///
138    /// # Examples
139    ///
140    /// ```
141    /// use ailloli_ui_core::{Constraints, Rect};
142    /// use ailloli_ui_runtime::component::{View, Widget};
143    /// use ailloli_ui_runtime::layout::{LayoutChild, LayoutCtx, LayoutEngine, LayoutResult};
144    /// use ailloli_ui_runtime::scene::PaintCtx;
145    /// struct W;
146    /// impl Widget<()> for W {
147    ///     fn debug_name(&self) -> &'static str { "W" }
148    ///     fn layout(&self, _: &mut LayoutEngine<'_, ()>, _: &mut LayoutCtx<'_>, _: &mut [LayoutChild], _: Constraints) -> LayoutResult { LayoutResult::empty() }
149    ///     fn paint(&self, _: &mut PaintCtx<'_>, _: Rect, _: &LayoutResult) {}
150    /// }
151    /// assert!(View::leaf(W).children.is_empty());
152    /// ```
153    pub fn leaf(widget: impl Widget<A> + 'static) -> Self {
154        Self {
155            key: None,
156            kind: ViewKind::Widget(track_widget(widget)),
157            children: Vec::new(),
158            flex_item: FlexItemStyle::default(),
159            size_hint: LayoutSizeHint::default(),
160        }
161    }
162
163    /// Wraps a widget and an ordered vector of declarative children.
164    ///
165    /// Children are moved without cloning; no parent pointer or key validation
166    /// occurs until reconciliation.
167    ///
168    /// # Examples
169    ///
170    /// ```
171    /// use ailloli_ui_core::{Constraints, Rect};
172    /// use ailloli_ui_runtime::component::{View, Widget};
173    /// use ailloli_ui_runtime::layout::{LayoutChild, LayoutCtx, LayoutEngine, LayoutResult};
174    /// use ailloli_ui_runtime::scene::PaintCtx;
175    /// struct W;
176    /// impl Widget<()> for W {
177    ///     fn debug_name(&self) -> &'static str { "W" }
178    ///     fn layout(&self, _: &mut LayoutEngine<'_, ()>, _: &mut LayoutCtx<'_>, _: &mut [LayoutChild], _: Constraints) -> LayoutResult { LayoutResult::empty() }
179    ///     fn paint(&self, _: &mut PaintCtx<'_>, _: Rect, _: &LayoutResult) {}
180    /// }
181    /// let view = View::node(W, vec![View::empty(), View::empty()]);
182    /// assert_eq!(view.children.len(), 2);
183    /// ```
184    pub fn node(widget: impl Widget<A> + 'static, children: Vec<View<A>>) -> Self {
185        Self {
186            key: None,
187            kind: ViewKind::Widget(track_widget(widget)),
188            children,
189            flex_item: FlexItemStyle::default(),
190            size_hint: LayoutSizeHint::default(),
191        }
192    }
193
194    /// Wraps a stateful component in an unkeyed view.
195    ///
196    /// Declarative children start empty because reconciliation obtains the
197    /// component's single built child by invoking [`ComponentNode::build`].
198    ///
199    /// # Examples
200    ///
201    /// ```
202    /// use ailloli_ui_runtime::component::{ComponentNode, Context, View, ViewKind};
203    /// struct C;
204    /// impl ComponentNode<()> for C {
205    ///     fn build(&self, _: &mut Context<()>) -> View<()> { View::empty() }
206    /// }
207    /// let view = View::component(C);
208    /// assert!(matches!(view.kind, ViewKind::Component(_)));
209    /// ```
210    pub fn component(component: impl ComponentNode<A> + 'static) -> Self {
211        Self {
212            key: None,
213            kind: ViewKind::Component(track_component(component)),
214            children: Vec::new(),
215            flex_item: FlexItemStyle::default(),
216            size_hint: LayoutSizeHint::default(),
217        }
218    }
219
220    /// Replaces this view's flex-child metadata and returns it.
221    ///
222    /// Values have already been normalized only if the caller used
223    /// `FlexItemStyle` builders; direct public-field values are preserved.
224    ///
225    /// # Examples
226    ///
227    /// ```
228    /// use ailloli_ui_core::style::FlexItemStyle;
229    /// use ailloli_ui_runtime::component::View;
230    /// let view = View::<()>::empty().with_flex_item(FlexItemStyle::new().flex_grow(2.0));
231    /// assert_eq!(view.flex_item.flex_grow, 2.0);
232    /// ```
233    pub fn with_flex_item(mut self, flex_item: FlexItemStyle) -> Self {
234        self.flex_item = flex_item;
235        self
236    }
237
238    /// Replaces declarative width/height hints and returns this view.
239    ///
240    /// # Examples
241    ///
242    /// ```
243    /// use ailloli_ui_core::style::{LayoutSizeHint, Length};
244    /// use ailloli_ui_runtime::component::View;
245    /// let hint = LayoutSizeHint::new(Length::Fill, Length::px(20.0));
246    /// let view = View::<()>::empty().with_size_hint(hint);
247    /// assert_eq!(view.size_hint, hint);
248    /// ```
249    pub fn with_size_hint(mut self, size_hint: LayoutSizeHint) -> Self {
250        self.size_hint = size_hint;
251        self
252    }
253
254    /// Sets an owned reconciliation key and returns this view.
255    ///
256    /// Empty and duplicate strings are accepted here. Reconciliation compares
257    /// exact UTF-8 text and has compatibility behavior for duplicates.
258    ///
259    /// # Examples
260    ///
261    /// ```
262    /// use ailloli_ui_runtime::component::View;
263    /// assert_eq!(View::<()>::empty().key("toolbar").key_ref(), Some("toolbar"));
264    /// ```
265    pub fn key(mut self, key: impl Into<String>) -> Self {
266        self.key = Some(key.into());
267        self
268    }
269
270    /// Wraps a declarative builder into a [`View`] (same as [`IntoView::into_view`]).
271    ///
272    /// # Examples
273    ///
274    /// ```
275    /// use ailloli_ui_runtime::component::View;
276    /// let view = View::<()>::from(View::empty().key("root"));
277    /// assert_eq!(view.key_ref(), Some("root"));
278    /// ```
279    pub fn from<V: IntoView<A>>(value: V) -> Self {
280        value.into_view()
281    }
282}
283
284/// Converts a declarative widget builder into a runtime [`View`] node.
285///
286/// Application code returns [`View<A>`] and calls [`.into_view()`](Self::into_view) on the root builder
287/// (re-export this trait in the app `view/prelude`, not in `ailloli_ui::prelude`). Framework uses it
288/// for `.child`, `Window::content`, and custom widgets.
289///
290/// # Examples
291///
292/// ```
293/// use ailloli_ui_runtime::component::{IntoView, View};
294/// let view: View<()> = View::empty().into_view();
295/// assert!(view.children.is_empty());
296/// ```
297pub trait IntoView<A> {
298    /// Consumes a builder/value and returns its declarative view tree.
299    ///
300    /// # Examples
301    ///
302    /// ```
303    /// use ailloli_ui_runtime::component::{IntoView, View};
304    /// let view: View<()> = IntoView::into_view(View::empty());
305    /// assert!(view.key.is_none());
306    /// ```
307    fn into_view(self) -> View<A>;
308}
309
310/// Implements the `IntoView<A>` contract for `View<A>`.
311impl<A> IntoView<A> for View<A> {
312    /// Converts this value into its retained view representation.
313    fn into_view(self) -> View<A> {
314        self
315    }
316}
317
318/// Allows `widget.key("id")` instead of `widget.into_view().key("id")`.
319///
320/// # Examples
321///
322/// ```
323/// use ailloli_ui_runtime::component::{IntoViewKeyExt, View};
324/// let view: View<()> = IntoViewKeyExt::key(View::empty(), "item");
325/// assert_eq!(view.key_ref(), Some("item"));
326/// ```
327pub trait IntoViewKeyExt<A>: IntoView<A> + Sized {
328    /// Converts `self`, assigns the owned key, and returns the view.
329    ///
330    /// # Examples
331    ///
332    /// ```
333    /// use ailloli_ui_runtime::component::{IntoViewKeyExt, View};
334    /// let view: View<()> = IntoViewKeyExt::key(View::empty(), String::from("x"));
335    /// assert_eq!(view.key_ref(), Some("x"));
336    /// ```
337    fn key(self, key: impl Into<String>) -> View<A> {
338        self.into_view().key(key)
339    }
340}
341
342/// Implements the `IntoViewKeyExt<A>` contract for `T where T: IntoView<A>`.
343impl<T, A> IntoViewKeyExt<A> for T where T: IntoView<A> {}
344
345/// Widget contract for the `View` tree (separate from `layout::Widget` retained nodes).
346///
347/// Callbacks run synchronously on the UI thread and panics propagate. Layout
348/// and paint use logical pixels. Implementations must not retain borrowed
349/// engines, contexts, child slices, events, bounds, or results. Reactive reads
350/// made by successful layout and paint callbacks are observed automatically;
351/// event-callback reads are passive unless the state also has an owner-provided
352/// invalidator. Reconciliation preserves a mount for a fresh payload of the
353/// same concrete widget type and starts a new mount generation when that type
354/// changes.
355///
356/// # Examples
357///
358/// ```
359/// use ailloli_ui_core::{Constraints, Rect};
360/// use ailloli_ui_runtime::component::Widget;
361/// use ailloli_ui_runtime::layout::{LayoutChild, LayoutCtx, LayoutEngine, LayoutResult};
362/// use ailloli_ui_runtime::scene::PaintCtx;
363/// struct Spacer;
364/// impl Widget<()> for Spacer {
365///     fn debug_name(&self) -> &'static str { "Spacer" }
366///     fn layout(&self, _: &mut LayoutEngine<'_, ()>, _: &mut LayoutCtx<'_>, _: &mut [LayoutChild], _: Constraints) -> LayoutResult { LayoutResult::empty() }
367///     fn paint(&self, _: &mut PaintCtx<'_>, _: Rect, _: &LayoutResult) {}
368/// }
369/// assert_eq!(Spacer.debug_name(), "Spacer");
370/// ```
371pub trait Widget<A>: 'static {
372    /// Returns a stable, non-sensitive static name for diagnostics.
373    ///
374    /// # Examples
375    ///
376    /// ```
377    /// use ailloli_ui_core::{Constraints, Rect};
378    /// use ailloli_ui_runtime::component::Widget;
379    /// use ailloli_ui_runtime::layout::{LayoutChild, LayoutCtx, LayoutEngine, LayoutResult};
380    /// use ailloli_ui_runtime::scene::PaintCtx;
381    /// struct W;
382    /// impl Widget<()> for W {
383    ///     fn debug_name(&self) -> &'static str { "W" }
384    ///     fn layout(&self, _: &mut LayoutEngine<'_, ()>, _: &mut LayoutCtx<'_>, _: &mut [LayoutChild], _: Constraints) -> LayoutResult { LayoutResult::empty() }
385    ///     fn paint(&self, _: &mut PaintCtx<'_>, _: Rect, _: &LayoutResult) {}
386    /// }
387    /// assert_eq!(W.debug_name(), "W");
388    /// ```
389    fn debug_name(&self) -> &'static str;
390
391    /// Revision of widget-owned inputs that can change its layout result.
392    ///
393    /// Runtime-created signals normally invalidate their owning element. This
394    /// hook also covers externally created reactive bindings when a layout pass
395    /// is requested for another reason, without invalidating stable siblings.
396    /// Implementations must return a cheap, stable and monotone value.
397    /// The default is the zero sentinel for no external dependency.
398    ///
399    /// # Examples
400    ///
401    /// ```
402    /// # use ailloli_ui_core::{Constraints, Rect};
403    /// # use ailloli_ui_runtime::component::Widget;
404    /// # use ailloli_ui_runtime::layout::{LayoutChild, LayoutCtx, LayoutEngine, LayoutResult};
405    /// # use ailloli_ui_runtime::scene::PaintCtx;
406    /// # struct W;
407    /// # impl Widget<()> for W { fn debug_name(&self)->&'static str{"W"} fn layout(&self,_:&mut LayoutEngine<'_,()>,_:&mut LayoutCtx<'_>,_:&mut[LayoutChild],_:Constraints)->LayoutResult{LayoutResult::empty()} fn paint(&self,_:&mut PaintCtx<'_>,_:Rect,_:&LayoutResult){} }
408    /// assert_eq!(W.layout_dependency_revision(), 0);
409    /// ```
410    fn layout_dependency_revision(&self) -> u64 {
411        0
412    }
413
414    /// Measures and positions this widget's direct retained children.
415    ///
416    /// The returned child vector must correspond by index to `children` for
417    /// commit, paint, and hit testing. Implementations define constraint and
418    /// non-finite handling and may recursively call [`LayoutChild::layout`].
419    /// [`LayoutCtx::layout_pass`] distinguishes speculative measurement from
420    /// authoritative allocation. Implementations must not persist effects
421    /// derived from geometry during a measurement pass.
422    ///
423    /// Reactive reads from measurement are staged for the current layout
424    /// attempt. Reads from measurements that contribute to the accepted result
425    /// are combined with reads from authoritative allocation and published only
426    /// after the attempt succeeds. Abandoned alternatives and panicking attempts
427    /// do not replace the previous dependency set.
428    ///
429    /// # Examples
430    ///
431    /// ```
432    /// # use ailloli_ui_core::{Constraints, Rect, Scale};
433    /// # use ailloli_ui_runtime::component::Widget;
434    /// # use ailloli_ui_runtime::element::ElementTree;
435    /// # use ailloli_ui_runtime::layout::{LayoutChild, LayoutCtx, LayoutEngine, LayoutResult};
436    /// # use ailloli_ui_runtime::scene::PaintCtx;
437    /// # struct W;
438    /// # impl Widget<()> for W { fn debug_name(&self)->&'static str{"W"} fn layout(&self,_:&mut LayoutEngine<'_,()>,_:&mut LayoutCtx<'_>,_:&mut[LayoutChild],_:Constraints)->LayoutResult{LayoutResult::empty()} fn paint(&self,_:&mut PaintCtx<'_>,_:Rect,_:&LayoutResult){} }
439    /// let mut tree = ElementTree::<()>::new();
440    /// let mut engine = LayoutEngine::new(&mut tree);
441    /// let mut ctx = LayoutCtx::new(Scale::new(1.0));
442    /// assert_eq!(W.layout(&mut engine, &mut ctx, &mut [], Constraints::tight(1.0, 1.0)).size.w, 0.0);
443    /// ```
444    fn layout(
445        &self,
446        engine: &mut LayoutEngine<'_, A>,
447        ctx: &mut LayoutCtx<'_>,
448        children: &mut [LayoutChild],
449        constraints: Constraints,
450    ) -> LayoutResult;
451
452    /// Emits base draw commands for absolute logical-pixel `bounds`.
453    ///
454    /// Base paint runs before descendants. `layout` is the cached result from
455    /// this element's committed layout pass and may contain a reusable artifact.
456    /// Geometry-dependent drawing must use that committed result; a widget must
457    /// not reshape newly read content into bounds computed for older content.
458    /// When geometry dependencies are stale, the runtime schedules layout for a
459    /// later traversal and does not invoke unsafe paint against stale geometry.
460    ///
461    /// Reactive reads from base and overlay paint form one dependency set. It
462    /// replaces the prior paint set only after the element's paint traversal
463    /// succeeds; a panic preserves the prior set.
464    ///
465    /// # Examples
466    ///
467    /// ```
468    /// # use ailloli_ui_core::{Constraints, Rect};
469    /// # use ailloli_ui_runtime::component::Widget;
470    /// # use ailloli_ui_runtime::layout::{LayoutChild, LayoutCtx, LayoutEngine, LayoutResult};
471    /// # use ailloli_ui_runtime::scene::PaintCtx;
472    /// # struct W;
473    /// # impl Widget<()> for W { fn debug_name(&self)->&'static str{"W"} fn layout(&self,_:&mut LayoutEngine<'_,()>,_:&mut LayoutCtx<'_>,_:&mut[LayoutChild],_:Constraints)->LayoutResult{LayoutResult::empty()} fn paint(&self,_:&mut PaintCtx<'_>,_:Rect,_:&LayoutResult){} }
474    /// let mut ctx = PaintCtx::new();
475    /// W.paint(&mut ctx, Rect::new(0.0, 0.0, 1.0, 1.0), &LayoutResult::empty());
476    /// assert_eq!(ctx.layers[0].cmds.len(), 0);
477    /// ```
478    fn paint(&self, ctx: &mut PaintCtx<'_>, bounds: Rect, layout: &LayoutResult);
479
480    /// Returns whether stale paint may replay this widget's committed text artifact.
481    ///
482    /// The default is fail-closed. A widget may opt in only when its complete
483    /// stale-safe visual unit is the immutable [`crate::layout::LayoutArtifact::Text`]
484    /// stored by its successful authoritative layout. Controls that add frames,
485    /// padding, scrolling, selection, carets, or overlays must keep the default.
486    /// The runtime calls this hook without invoking `paint` or reading widget state.
487    #[doc(hidden)]
488    fn can_replay_committed_text_artifact(&self) -> bool {
489        false
490    }
491
492    /// Observes newly committed absolute bounds after layout.
493    ///
494    /// The default does nothing. It runs after a real authoritative layout
495    /// callback, or when committed bounds or retained hook inputs changed.
496    /// Stable cache hits do not rerun it. The complete visited subtree already
497    /// has authoritative bounds; the hook should not mutate tree topology.
498    /// Reactive reads are combined with the successful authoritative layout
499    /// observations. This callback is not run for speculative measurement.
500    /// If it panics, the authoritative geometry remains committed and the
501    /// previously published hook dependencies remain active. Reactive writes
502    /// performed before the panic are not rolled back; any invalidation they
503    /// emit remains queued for a later frame rather than re-entering layout.
504    ///
505    /// # Examples
506    ///
507    /// ```
508    /// # use ailloli_ui_core::{Constraints, Rect, Scale};
509    /// # use ailloli_ui_runtime::component::Widget;
510    /// # use ailloli_ui_runtime::layout::{LayoutChild, LayoutCtx, LayoutEngine, LayoutResult};
511    /// # use ailloli_ui_runtime::scene::PaintCtx;
512    /// # struct W;
513    /// # impl Widget<()> for W { fn debug_name(&self)->&'static str{"W"} fn layout(&self,_:&mut LayoutEngine<'_,()>,_:&mut LayoutCtx<'_>,_:&mut[LayoutChild],_:Constraints)->LayoutResult{LayoutResult::empty()} fn paint(&self,_:&mut PaintCtx<'_>,_:Rect,_:&LayoutResult){} }
514    /// let mut ctx = LayoutCtx::new(Scale::new(1.0));
515    /// W.layout_committed(&mut ctx, Rect::new(0.0, 0.0, 1.0, 1.0), &LayoutResult::empty());
516    /// ```
517    fn layout_committed(&self, _ctx: &mut LayoutCtx<'_>, _bounds: Rect, _layout: &LayoutResult) {}
518
519    /// Emits top-level paint commands after this widget's descendants.
520    ///
521    /// The default does nothing. Use [`PaintCtx::push_overlay`] for commands
522    /// that must be globally ordered after base content.
523    ///
524    /// # Examples
525    ///
526    /// ```
527    /// # use ailloli_ui_core::{Constraints, Rect};
528    /// # use ailloli_ui_runtime::component::Widget;
529    /// # use ailloli_ui_runtime::layout::{LayoutChild, LayoutCtx, LayoutEngine, LayoutResult};
530    /// # use ailloli_ui_runtime::scene::PaintCtx;
531    /// # struct W;
532    /// # impl Widget<()> for W { fn debug_name(&self)->&'static str{"W"} fn layout(&self,_:&mut LayoutEngine<'_,()>,_:&mut LayoutCtx<'_>,_:&mut[LayoutChild],_:Constraints)->LayoutResult{LayoutResult::empty()} fn paint(&self,_:&mut PaintCtx<'_>,_:Rect,_:&LayoutResult){} }
533    /// let mut ctx = PaintCtx::new();
534    /// W.paint_overlay(&mut ctx, Rect::new(0.0, 0.0, 1.0, 1.0), &LayoutResult::empty());
535    /// assert!(ctx.overlay_layers.is_empty());
536    /// ```
537    fn paint_overlay(&self, _ctx: &mut PaintCtx<'_>, _bounds: Rect, _layout: &LayoutResult) {}
538
539    /// Handles one routed event for absolute bounds and cached layout.
540    ///
541    /// The default does nothing and leaves propagation enabled. Implementations
542    /// may dispatch actions, invalidate work, or stop bubbling through `ctx`.
543    ///
544    /// # Examples
545    ///
546    /// ```
547    /// # use ailloli_ui_core::{Constraints, ElementId, Event, Rect};
548    /// # use ailloli_ui_core::event::FocusEvent;
549    /// # use ailloli_ui_runtime::app::RuntimeHandle;
550    /// # use ailloli_ui_runtime::component::Widget;
551    /// # use ailloli_ui_runtime::input::EventCtx;
552    /// # use ailloli_ui_runtime::layout::{LayoutChild, LayoutCtx, LayoutEngine, LayoutResult};
553    /// # use ailloli_ui_runtime::scene::PaintCtx;
554    /// # struct W;
555    /// # impl Widget<()> for W { fn debug_name(&self)->&'static str{"W"} fn layout(&self,_:&mut LayoutEngine<'_,()>,_:&mut LayoutCtx<'_>,_:&mut[LayoutChild],_:Constraints)->LayoutResult{LayoutResult::empty()} fn paint(&self,_:&mut PaintCtx<'_>,_:Rect,_:&LayoutResult){} }
556    /// let mut ctx = EventCtx::new(RuntimeHandle::new(), ElementId(1));
557    /// W.event(&mut ctx, &Event::Focus(FocusEvent::new(true)), Rect::new(0.0,0.0,1.0,1.0), &LayoutResult::empty());
558    /// assert!(!ctx.is_propagation_stopped());
559    /// ```
560    fn event(&self, _ctx: &mut EventCtx<A>, _event: &Event, _bounds: Rect, _layout: &LayoutResult) {
561    }
562
563    /// Returns whether the widget may receive keyboard focus.
564    ///
565    /// The default is [`FocusPolicy::NotFocusable`].
566    ///
567    /// # Examples
568    ///
569    /// ```
570    /// # use ailloli_ui_core::{Constraints, Rect};
571    /// # use ailloli_ui_runtime::component::Widget;
572    /// # use ailloli_ui_runtime::input::FocusPolicy;
573    /// # use ailloli_ui_runtime::layout::{LayoutChild, LayoutCtx, LayoutEngine, LayoutResult};
574    /// # use ailloli_ui_runtime::scene::PaintCtx;
575    /// # struct W;
576    /// # impl Widget<()> for W { fn debug_name(&self)->&'static str{"W"} fn layout(&self,_:&mut LayoutEngine<'_,()>,_:&mut LayoutCtx<'_>,_:&mut[LayoutChild],_:Constraints)->LayoutResult{LayoutResult::empty()} fn paint(&self,_:&mut PaintCtx<'_>,_:Rect,_:&LayoutResult){} }
577    /// assert_eq!(W.focus_policy(), FocusPolicy::NotFocusable);
578    /// ```
579    fn focus_policy(&self) -> FocusPolicy {
580        FocusPolicy::NotFocusable
581    }
582
583    /// Policy used for provider-neutral focus-only pointer gestures.
584    ///
585    /// The default inherits from the parent; the input router falls back to
586    /// suppression at the root so custom action widgets are safe by default.
587    ///
588    /// # Examples
589    ///
590    /// ```
591    /// # use ailloli_ui_core::{Constraints, Rect};
592    /// # use ailloli_ui_runtime::component::Widget;
593    /// # use ailloli_ui_runtime::input::ActivationPolicy;
594    /// # use ailloli_ui_runtime::layout::{LayoutChild,LayoutCtx,LayoutEngine,LayoutResult};
595    /// # use ailloli_ui_runtime::scene::PaintCtx;
596    /// # struct W;
597    /// # impl Widget<()> for W { fn debug_name(&self)->&'static str{"W"} fn layout(&self,_:&mut LayoutEngine<'_,()>,_:&mut LayoutCtx<'_>,_:&mut[LayoutChild],_:Constraints)->LayoutResult{LayoutResult::empty()} fn paint(&self,_:&mut PaintCtx<'_>,_:Rect,_:&LayoutResult){} }
598    /// assert_eq!(W.activation_policy(), ActivationPolicy::Inherit);
599    /// ```
600    fn activation_policy(&self) -> ActivationPolicy {
601        ActivationPolicy::Inherit
602    }
603
604    /// Returns provider-neutral text-input semantics.
605    ///
606    /// The default is [`InputRole::None`] and does not imply focusability.
607    ///
608    /// # Examples
609    ///
610    /// ```
611    /// # use ailloli_ui_core::{Constraints,Rect};
612    /// # use ailloli_ui_runtime::component::Widget;
613    /// # use ailloli_ui_runtime::input::InputRole;
614    /// # use ailloli_ui_runtime::layout::{LayoutChild,LayoutCtx,LayoutEngine,LayoutResult};
615    /// # use ailloli_ui_runtime::scene::PaintCtx;
616    /// # struct W;
617    /// # impl Widget<()> for W { fn debug_name(&self)->&'static str{"W"} fn layout(&self,_:&mut LayoutEngine<'_,()>,_:&mut LayoutCtx<'_>,_:&mut[LayoutChild],_:Constraints)->LayoutResult{LayoutResult::empty()} fn paint(&self,_:&mut PaintCtx<'_>,_:Rect,_:&LayoutResult){} }
618    /// assert_eq!(W.input_role(), InputRole::None);
619    /// ```
620    fn input_role(&self) -> InputRole {
621        InputRole::None
622    }
623
624    /// Returns the widget-wide hover cursor role.
625    ///
626    /// By default text input roles map to `Text` and `None` maps to `Inherit`.
627    ///
628    /// # Examples
629    ///
630    /// ```
631    /// # use ailloli_ui_core::{Constraints,Rect};
632    /// # use ailloli_ui_runtime::component::Widget;
633    /// # use ailloli_ui_runtime::input::HoverCursorRole;
634    /// # use ailloli_ui_runtime::layout::{LayoutChild,LayoutCtx,LayoutEngine,LayoutResult};
635    /// # use ailloli_ui_runtime::scene::PaintCtx;
636    /// # struct W;
637    /// # impl Widget<()> for W { fn debug_name(&self)->&'static str{"W"} fn layout(&self,_:&mut LayoutEngine<'_,()>,_:&mut LayoutCtx<'_>,_:&mut[LayoutChild],_:Constraints)->LayoutResult{LayoutResult::empty()} fn paint(&self,_:&mut PaintCtx<'_>,_:Rect,_:&LayoutResult){} }
638    /// assert_eq!(W.hover_cursor_role(), HoverCursorRole::Inherit);
639    /// ```
640    fn hover_cursor_role(&self) -> HoverCursorRole {
641        match self.input_role() {
642            InputRole::TextSingleLine | InputRole::TextMultiLine => HoverCursorRole::Text,
643            InputRole::None => HoverCursorRole::Inherit,
644        }
645    }
646
647    /// Returns the hover cursor role at an absolute logical-pixel position.
648    ///
649    /// The default ignores position/bounds/layout and delegates to
650    /// [`Self::hover_cursor_role`].
651    ///
652    /// # Examples
653    ///
654    /// ```
655    /// # use ailloli_ui_core::{Constraints,Point,Rect};
656    /// # use ailloli_ui_runtime::component::Widget;
657    /// # use ailloli_ui_runtime::input::HoverCursorRole;
658    /// # use ailloli_ui_runtime::layout::{LayoutChild,LayoutCtx,LayoutEngine,LayoutResult};
659    /// # use ailloli_ui_runtime::scene::PaintCtx;
660    /// # struct W;
661    /// # impl Widget<()> for W { fn debug_name(&self)->&'static str{"W"} fn layout(&self,_:&mut LayoutEngine<'_,()>,_:&mut LayoutCtx<'_>,_:&mut[LayoutChild],_:Constraints)->LayoutResult{LayoutResult::empty()} fn paint(&self,_:&mut PaintCtx<'_>,_:Rect,_:&LayoutResult){} }
662    /// assert_eq!(W.hover_cursor_role_at(Rect::new(0.0,0.0,1.0,1.0), &LayoutResult::empty(), Point::new(0.5,0.5)), HoverCursorRole::Inherit);
663    /// ```
664    fn hover_cursor_role_at(
665        &self,
666        _bounds: Rect,
667        _layout: &LayoutResult,
668        _pos: Point,
669    ) -> HoverCursorRole {
670        self.hover_cursor_role()
671    }
672
673    /// Returns the absolute logical-pixel IME candidate/caret rectangle.
674    ///
675    /// `None`, the default, means the widget does not expose an IME cursor.
676    ///
677    /// # Examples
678    ///
679    /// ```
680    /// # use ailloli_ui_core::{Constraints,Rect};
681    /// # use ailloli_ui_runtime::component::Widget;
682    /// # use ailloli_ui_runtime::layout::{LayoutChild,LayoutCtx,LayoutEngine,LayoutResult};
683    /// # use ailloli_ui_runtime::scene::PaintCtx;
684    /// # struct W;
685    /// # impl Widget<()> for W { fn debug_name(&self)->&'static str{"W"} fn layout(&self,_:&mut LayoutEngine<'_,()>,_:&mut LayoutCtx<'_>,_:&mut[LayoutChild],_:Constraints)->LayoutResult{LayoutResult::empty()} fn paint(&self,_:&mut PaintCtx<'_>,_:Rect,_:&LayoutResult){} }
686    /// assert_eq!(W.ime_cursor_rect(Rect::new(0.0,0.0,1.0,1.0), &LayoutResult::empty()), None);
687    /// ```
688    fn ime_cursor_rect(&self, _bounds: Rect, _layout: &LayoutResult) -> Option<Rect> {
689        None
690    }
691}
692
693/// Stateful declarative component contract.
694///
695/// Each build runs synchronously with a fresh hook cursor and returns exactly
696/// one root view. Reactive reads are observed automatically. After a successful
697/// build, the sources reached by that build atomically replace the component's
698/// previous observations; this includes switching conditional branches or
699/// observing no source. A panic preserves the previous observations, although
700/// reconciliation and application-side mutations are not rolled back. All
701/// observations are removed when the component unmounts. A fresh payload of
702/// the same concrete component type preserves the mount; changing that type
703/// starts a new mount generation. Panics propagate.
704///
705/// # Examples
706///
707/// ```
708/// use ailloli_ui_runtime::component::{ComponentNode, Context, View};
709/// struct Empty;
710/// impl ComponentNode<()> for Empty {
711///     fn build(&self, _: &mut Context<()>) -> View<()> { View::empty() }
712/// }
713/// ```
714pub trait ComponentNode<A>: 'static {
715    /// Builds the component's current declarative root view.
716    ///
717    /// The callback runs synchronously on the UI thread. Reading a
718    /// [`Signal`](super::Signal),
719    /// [`State`](super::State), reactive [`Binding`](super::Binding), or derived
720    /// [`Memo`](super::Memo) records the source for this mounted component.
721    ///
722    /// # Examples
723    ///
724    /// ```
725    /// use ailloli_ui_core::ElementId;
726    /// use ailloli_ui_runtime::app::RuntimeHandle;
727    /// use ailloli_ui_runtime::component::{ComponentNode, Context, View};
728    /// struct Empty;
729    /// impl ComponentNode<()> for Empty { fn build(&self, _: &mut Context<()>) -> View<()> { View::empty() } }
730    /// let mut ctx = Context::new(ElementId(1), RuntimeHandle::new());
731    /// assert!(Empty.build(&mut ctx).children.is_empty());
732    /// ```
733    fn build(&self, context: &mut Context<A>) -> View<A>;
734}
735
736#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
737enum MountPayloadClass {
738    Widget,
739    Component,
740}
741
742struct MountIdentityRecord {
743    lifetime: Weak<dyn MountLiveness>,
744    identity: PrivateMountIdentity,
745}
746
747thread_local! {
748    static MOUNT_IDENTITIES: RefCell<MountIdentityRegistry> =
749        RefCell::new(MountIdentityRegistry::default());
750}
751
752#[derive(Debug, Clone, Copy, PartialEq, Eq)]
753pub(crate) struct PrivateMountIdentity(TypeId);
754
755#[derive(Default)]
756struct MountIdentityRegistry {
757    records: HashMap<(MountPayloadClass, usize), MountIdentityRecord>,
758}
759
760struct MountTracked<T> {
761    payload: T,
762    class: MountPayloadClass,
763}
764
765trait MountLiveness: 'static {}
766
767impl<T: 'static> MountLiveness for MountTracked<T> {}
768
769impl<T> Drop for MountTracked<T> {
770    fn drop(&mut self) {
771        let data_pointer = self as *const Self as usize;
772        MOUNT_IDENTITIES.with(|identities| {
773            identities
774                .borrow_mut()
775                .records
776                .remove(&(self.class, data_pointer));
777        });
778    }
779}
780
781fn register_mount_identity(
782    class: MountPayloadClass,
783    data_pointer: usize,
784    lifetime: Weak<dyn MountLiveness>,
785    identity: PrivateMountIdentity,
786) {
787    MOUNT_IDENTITIES.with(|identities| {
788        identities.borrow_mut().records.insert(
789            (class, data_pointer),
790            MountIdentityRecord { lifetime, identity },
791        );
792    });
793}
794
795fn mount_identity(class: MountPayloadClass, data_pointer: usize) -> Option<PrivateMountIdentity> {
796    MOUNT_IDENTITIES.with(|identities| {
797        let mut identities = identities.borrow_mut();
798        let key = (class, data_pointer);
799        let live = identities
800            .records
801            .get(&key)
802            .is_some_and(|record| record.lifetime.strong_count() > 0);
803        if !live {
804            identities.records.remove(&key);
805            return None;
806        }
807        identities.records.get(&key).map(|record| record.identity)
808    })
809}
810
811fn new_mount_identity<T: 'static>() -> PrivateMountIdentity {
812    PrivateMountIdentity(TypeId::of::<T>())
813}
814
815fn track_widget<A, T>(widget: T) -> Rc<dyn Widget<A>>
816where
817    T: Widget<A> + 'static,
818{
819    let tracked: Rc<MountTracked<T>> = Rc::new(MountTracked {
820        payload: widget,
821        class: MountPayloadClass::Widget,
822    });
823    let liveness: Rc<dyn MountLiveness> = tracked.clone();
824    let lifetime = Rc::downgrade(&liveness);
825    let erased: Rc<dyn Widget<A>> = tracked;
826    register_mount_identity(
827        MountPayloadClass::Widget,
828        Rc::as_ptr(&erased) as *const () as usize,
829        lifetime,
830        new_mount_identity::<T>(),
831    );
832    erased
833}
834
835fn track_component<A, T>(component: T) -> Rc<dyn ComponentNode<A>>
836where
837    T: ComponentNode<A> + 'static,
838{
839    let tracked: Rc<MountTracked<T>> = Rc::new(MountTracked {
840        payload: component,
841        class: MountPayloadClass::Component,
842    });
843    let liveness: Rc<dyn MountLiveness> = tracked.clone();
844    let lifetime = Rc::downgrade(&liveness);
845    let erased: Rc<dyn ComponentNode<A>> = tracked;
846    register_mount_identity(
847        MountPayloadClass::Component,
848        Rc::as_ptr(&erased) as *const () as usize,
849        lifetime,
850        new_mount_identity::<T>(),
851    );
852    erased
853}
854
855pub(crate) fn widget_mount_identity<A>(widget: &Rc<dyn Widget<A>>) -> Option<PrivateMountIdentity> {
856    mount_identity(
857        MountPayloadClass::Widget,
858        Rc::as_ptr(widget) as *const () as usize,
859    )
860}
861
862pub(crate) fn component_mount_identity<A>(
863    component: &Rc<dyn ComponentNode<A>>,
864) -> Option<PrivateMountIdentity> {
865    mount_identity(
866        MountPayloadClass::Component,
867        Rc::as_ptr(component) as *const () as usize,
868    )
869}
870
871impl<T, A> Widget<A> for MountTracked<T>
872where
873    T: Widget<A> + 'static,
874{
875    fn debug_name(&self) -> &'static str {
876        self.payload.debug_name()
877    }
878
879    fn layout_dependency_revision(&self) -> u64 {
880        self.payload.layout_dependency_revision()
881    }
882
883    fn layout(
884        &self,
885        engine: &mut LayoutEngine<'_, A>,
886        ctx: &mut LayoutCtx<'_>,
887        children: &mut [LayoutChild],
888        constraints: Constraints,
889    ) -> LayoutResult {
890        self.payload.layout(engine, ctx, children, constraints)
891    }
892
893    fn paint(&self, ctx: &mut PaintCtx<'_>, bounds: Rect, layout: &LayoutResult) {
894        self.payload.paint(ctx, bounds, layout);
895    }
896
897    fn can_replay_committed_text_artifact(&self) -> bool {
898        self.payload.can_replay_committed_text_artifact()
899    }
900
901    fn layout_committed(&self, ctx: &mut LayoutCtx<'_>, bounds: Rect, layout: &LayoutResult) {
902        self.payload.layout_committed(ctx, bounds, layout);
903    }
904
905    fn paint_overlay(&self, ctx: &mut PaintCtx<'_>, bounds: Rect, layout: &LayoutResult) {
906        self.payload.paint_overlay(ctx, bounds, layout);
907    }
908
909    fn event(&self, ctx: &mut EventCtx<A>, event: &Event, bounds: Rect, layout: &LayoutResult) {
910        self.payload.event(ctx, event, bounds, layout);
911    }
912
913    fn focus_policy(&self) -> FocusPolicy {
914        self.payload.focus_policy()
915    }
916
917    fn activation_policy(&self) -> ActivationPolicy {
918        self.payload.activation_policy()
919    }
920
921    fn input_role(&self) -> InputRole {
922        self.payload.input_role()
923    }
924
925    fn hover_cursor_role(&self) -> HoverCursorRole {
926        self.payload.hover_cursor_role()
927    }
928
929    fn hover_cursor_role_at(
930        &self,
931        bounds: Rect,
932        layout: &LayoutResult,
933        pos: Point,
934    ) -> HoverCursorRole {
935        self.payload.hover_cursor_role_at(bounds, layout, pos)
936    }
937
938    fn ime_cursor_rect(&self, bounds: Rect, layout: &LayoutResult) -> Option<Rect> {
939        self.payload.ime_cursor_rect(bounds, layout)
940    }
941}
942
943impl<T, A> ComponentNode<A> for MountTracked<T>
944where
945    T: ComponentNode<A> + 'static,
946{
947    fn build(&self, context: &mut Context<A>) -> View<A> {
948        self.payload.build(context)
949    }
950}
951
952/// Function-backed component storing cloneable props.
953///
954/// Every build clones `props` and passes the clone by value to `render`.
955/// Function items and closure expressions keep their concrete Rust type, which
956/// lets reconciliation distinguish different render implementations without
957/// adding identity methods to [`ComponentNode`].
958///
959/// Existing annotations such as `Component<P, A>` remain valid and use the
960/// default erased function-pointer type. That explicit erasure preserves the
961/// historical type-based reconciliation contract: changing the callback while
962/// keeping the same erased type does not identify a new mount. Callers that
963/// intentionally replace an erased callback should use a distinct component
964/// type or reconciliation key.
965///
966/// # Examples
967///
968/// ```
969/// use ailloli_ui_runtime::component::{Component, Context, IntoView, View};
970/// fn render(_: &mut Context<()>, label: String) -> View<()> { View::empty().key(label) }
971/// let view = Component::new(String::from("root"), render).into_view();
972/// assert!(matches!(view.kind, ailloli_ui_runtime::component::ViewKind::Component(_)));
973/// ```
974pub struct Component<P, A, F = fn(&mut Context<A>, P) -> View<A>> {
975    /// Cloneable properties passed by value to every render invocation.
976    props: P,
977    /// Typed render callback used to build the retained view.
978    render: F,
979    /// Application type retained without imposing ownership or auto-trait bounds.
980    application: PhantomData<fn() -> A>,
981}
982
983impl<P, A, F> Component<P, A, F>
984where
985    P: Clone + 'static,
986    A: 'static,
987    F: Fn(&mut Context<A>, P) -> View<A> + 'static,
988{
989    /// Creates a function-backed component without running `render`.
990    ///
991    /// # Examples
992    ///
993    /// ```
994    /// use ailloli_ui_runtime::component::{Component, Context, IntoView, View};
995    /// fn render(_: &mut Context<()>, _: u8) -> View<()> { View::empty() }
996    /// let view = Component::new(7, render).into_view();
997    /// assert!(view.children.is_empty());
998    /// ```
999    pub fn new(props: P, render: F) -> Self {
1000        Self {
1001            props,
1002            render,
1003            application: PhantomData,
1004        }
1005    }
1006}
1007
1008impl<P, A, F> ComponentNode<A> for Component<P, A, F>
1009where
1010    P: Clone + 'static,
1011    A: 'static,
1012    F: Fn(&mut Context<A>, P) -> View<A> + 'static,
1013{
1014    /// Builds the retained view required by this component.
1015    fn build(&self, context: &mut Context<A>) -> View<A> {
1016        (self.render)(context, self.props.clone())
1017    }
1018}
1019
1020impl<P, A, F> IntoView<A> for Component<P, A, F>
1021where
1022    P: Clone + 'static,
1023    A: 'static,
1024    F: Fn(&mut Context<A>, P) -> View<A> + 'static,
1025{
1026    /// Converts this value into its retained view representation.
1027    fn into_view(self) -> View<A> {
1028        View::component(self)
1029    }
1030}