Skip to main content

sequoia_view/
runtime.rs

1use std::any::Any;
2use std::cell::{Cell, RefCell};
3use std::collections::{HashMap, HashSet};
4use std::panic::AssertUnwindSafe;
5use std::rc::{Rc, Weak};
6
7use crate::dnd::{
8    ActiveDrag, DragEnd, DragId, DragOptions, DragPayload, DragSession, DropPosition, PendingDrag,
9    exceeded_threshold, grab_offset, preview_origin,
10};
11use crate::overlay::{
12    Anchor, InputMode, OverlayEntry, OverlayId, OverlayLayer, OverlayStore, compute_position,
13};
14use sequoia_core::{
15    Constraints, Dirty, Edges, Length, Point, Rect, RenderList, ShaderId, Size, Transform, Vec2,
16    WindowId,
17};
18use sequoia_input::{
19    CursorIcon, Event, EventPhase, ImeEvent, Key, KeyEvent, Modifiers, PenTool, PointerButton,
20    PointerEvent,
21};
22use sequoia_layout::{
23    Align, Axis, ContainerStyle, Dock, DockStyle, FlexItem, GridPlacement, GridStyle, LayoutCtx,
24    LayoutStyle, MasonryStyle, Overflow, Track, WrapStyle, clamp_to, distribute_flex,
25    dock_layout_order, dock_thickness, masonry_assign, masonry_column_width, resolve_cross,
26    resolve_dock, resolve_flex_basis, resolve_main, resolve_tracks, sanitize_gap, track_offsets,
27    wrap_lines,
28};
29use sequoia_reactive::{Environment, ReadSet, Signal, Watcher, capture_reads, flush_effects};
30use sequoia_style::{Theme, VisualState};
31use sequoia_text::TextSystem;
32
33use crate::any::{AnyView, ErasedView};
34use crate::cache::{LayerCache, LayerKey, LayerStamp};
35use crate::clock::FrameClock;
36use crate::live::{AnimSpec, AnimState, Live, Prop, SpringSpec};
37use crate::paint_region::CullCtx;
38use crate::surface::SurfaceDraw;
39use crate::view::{EventCtx, PaintCtx, View};
40
41#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
42pub struct ElementId {
43    pub(crate) index: u32,
44    pub(crate) generation: u32,
45}
46
47impl ElementId {
48    /// Stable key that includes the arena generation, for cache invalidation.
49    pub fn key(self) -> u64 {
50        (u64::from(self.generation) << 32) | u64::from(self.index)
51    }
52}
53
54#[derive(Clone, Copy)]
55struct PaintPass<'a> {
56    env: &'a Environment,
57    hover_chain: &'a [ElementId],
58}
59
60impl PaintPass<'_> {
61    fn hovered_subtree(&self, id: ElementId) -> bool {
62        self.hover_chain.contains(&id)
63    }
64}
65
66pub trait ViewState: 'static + Default {}
67impl<T: Default + 'static> ViewState for T {}
68
69const MAX_ERRORS: usize = 64;
70
71#[derive(Clone, Copy, Debug, Default, PartialEq)]
72pub struct Stats {
73    pub mounts: u64,
74
75    pub unmounts: u64,
76
77    pub rebuilds: u64,
78
79    pub measure_invalidations: u64,
80
81    pub errors: u64,
82}
83
84#[derive(Clone, Debug, PartialEq)]
85pub struct RuntimeError {
86    pub element: ElementId,
87
88    pub type_name: String,
89
90    pub message: String,
91}
92
93#[derive(Clone, Copy, Debug, Default, PartialEq)]
94pub struct InteractState {
95    pub hovered: Option<ElementId>,
96
97    pub pressed: Option<ElementId>,
98
99    pub captured: Option<ElementId>,
100
101    pub focus: Option<ElementId>,
102
103    pub cursor: Point,
104
105    pub pointer_seen: bool,
106}
107
108struct Element {
109    parent: Option<ElementId>,
110    key: u64,
111    children: Vec<ElementId>,
112    view: Option<Rc<dyn ErasedView>>,
113    watcher: Option<Watcher>,
114    reads: ReadSet,
115    dirty: Dirty,
116    revision: u64,
117    rect: Cell<Rect>,
118}
119
120#[derive(Default)]
121struct Arena {
122    slots: Vec<Option<Element>>,
123    generations: Vec<u32>,
124    free: Vec<u32>,
125}
126
127impl Arena {
128    fn get(&self, id: ElementId) -> Option<&Element> {
129        let i = id.index as usize;
130        if self.generations.get(i)? != &id.generation {
131            return None;
132        }
133        self.slots.get(i)?.as_ref()
134    }
135    fn get_mut(&mut self, id: ElementId) -> Option<&mut Element> {
136        let i = id.index as usize;
137        if self.generations.get(i)? != &id.generation {
138            return None;
139        }
140        self.slots.get_mut(i)?.as_mut()
141    }
142    fn alloc_slot(&mut self) -> (u32, u32) {
143        match self.free.pop() {
144            Some(i) => (i, self.generations[i as usize]),
145            None => {
146                let i = self.slots.len() as u32;
147                self.slots.push(None);
148                self.generations.push(0);
149                (i, 0)
150            }
151        }
152    }
153    fn free_slot(&mut self, slot: u32) {
154        self.slots[slot as usize] = None;
155        self.generations[slot as usize] = self.generations[slot as usize].wrapping_add(1);
156        self.free.push(slot);
157    }
158    fn alive(&self) -> usize {
159        self.slots.iter().filter(|s| s.is_some()).count()
160    }
161}
162
163#[derive(Clone)]
164struct MeasureEntry {
165    constraints: Constraints,
166    style: LayoutStyle,
167    container: Option<ContainerStyle>,
168
169    grid: Option<GridStyle>,
170    placements: Vec<GridPlacement>,
171
172    dock: Option<DockStyle>,
173
174    wrap: Option<WrapStyle>,
175    masonry: Option<MasonryStyle>,
176    children_len: usize,
177    size: Size,
178}
179
180struct DispatchCells {
181    stopped: Cell<bool>,
182    handled: Cell<bool>,
183    capture_req: Cell<Option<ElementId>>,
184    release_req: Cell<bool>,
185    focus_req: Cell<bool>,
186}
187
188impl DispatchCells {
189    fn new() -> Self {
190        Self {
191            stopped: Cell::new(false),
192            handled: Cell::new(false),
193            capture_req: Cell::new(None),
194            release_req: Cell::new(false),
195            focus_req: Cell::new(false),
196        }
197    }
198}
199
200struct Inner {
201    arena: RefCell<Arena>,
202    queue: RefCell<Vec<ElementId>>,
203    root: Cell<Option<ElementId>>,
204    stats: Cell<Stats>,
205    measures: RefCell<HashMap<ElementId, MeasureEntry>>,
206    interact: Cell<InteractState>,
207    environment: RefCell<Environment>,
208
209    env_listener: RefCell<Option<sequoia_reactive::Listener>>,
210
211    redraw: RefCell<Option<Box<dyn Fn()>>>,
212    surfaces: RefCell<Vec<SurfaceDraw>>,
213    overlays: RefCell<OverlayStore>,
214
215    pending_drag: RefCell<Option<PendingDrag>>,
216
217    active_drag: RefCell<Option<ActiveDrag>>,
218
219    next_drag_id: Cell<u64>,
220
221    viewport: Cell<Rect>,
222
223    root_fill: Cell<bool>,
224
225    window_size: Signal<Size>,
226
227    scale: Cell<f32>,
228
229    safe_insets: Cell<Edges>,
230
231    errors: RefCell<Vec<RuntimeError>>,
232
233    errors_seq: Signal<u64>,
234
235    stylus_pressure: Cell<f32>,
236
237    stylus_tool: Cell<PenTool>,
238
239    touches: RefCell<HashMap<u64, Point>>,
240
241    shapes: sequoia_draw::ShapeCache,
242
243    window: Cell<WindowId>,
244
245    wrap_cache: RefCell<HashMap<(u32, u32, u8), bool>>,
246
247    live_anims: RefCell<HashMap<(ElementId, u64), AnimState>>,
248
249    clock: Cell<FrameClock>,
250
251    damage: RefCell<crate::damage::DamageTracker>,
252
253    layers: RefCell<LayerCache>,
254    layer_hits: Cell<u64>,
255    layer_misses: Cell<u64>,
256
257    states: RefCell<HashMap<ElementId, Box<dyn Any>>>,
258
259    layout_epoch: Cell<u64>,
260
261    layout_dirty: Cell<bool>,
262
263    scoped_cancels: RefCell<HashMap<ElementId, Vec<Box<dyn FnOnce()>>>>,
264
265    shader_ids: RefCell<HashMap<String, ShaderId>>,
266
267    next_shader_id: Cell<u32>,
268}
269
270impl Inner {
271    fn schedule(&self, id: ElementId) {
272        let view = self.arena.borrow().get(id).and_then(|e| e.view.clone());
273        let dirty = view
274            .map(|v| v.signal_dirty_erased())
275            .unwrap_or(Dirty::LAYOUT | Dirty::CHILDREN);
276        self.mark(id, dirty);
277    }
278
279    fn mark(&self, id: ElementId, flags: Dirty) {
280        if flags.is_empty() {
281            return;
282        }
283        if flags.needs_repaint() {
284            self.bump_revision(id);
285        }
286        let (newly, rect) = {
287            let mut arena = self.arena.borrow_mut();
288            match arena.get_mut(id) {
289                Some(e) => {
290                    let was = e.dirty;
291                    e.dirty |= flags;
292                    (was.is_empty(), e.rect.get())
293                }
294                None => return,
295            }
296        };
297        if newly {
298            self.queue.borrow_mut().push(id);
299        }
300
301        {
302            let mut damage = self.damage.borrow_mut();
303            damage.mark(flags);
304            if flags.needs_repaint() {
305                damage.mark_rect(rect);
306            }
307        }
308
309        if flags.needs_repaint()
310            && let Some(h) = self.redraw.borrow().as_ref()
311        {
312            h();
313        }
314    }
315
316    fn bump_revision(&self, id: ElementId) {
317        let mut arena = self.arena.borrow_mut();
318        let mut cur = Some(id);
319        let mut walked = 0u32;
320        while let Some(c) = cur {
321            let Some(e) = arena.get_mut(c) else {
322                break;
323            };
324            e.revision = e.revision.wrapping_add(1);
325            cur = e.parent;
326            walked += 1;
327            if walked > 4096 {
328                break;
329            }
330        }
331    }
332}
333
334#[derive(Clone)]
335pub struct Runtime {
336    inner: Rc<Inner>,
337}
338
339impl Default for Runtime {
340    fn default() -> Self {
341        Self::new()
342    }
343}
344
345impl Runtime {
346    pub fn new() -> Self {
347        Self {
348            inner: Rc::new(Inner {
349                arena: RefCell::default(),
350                queue: RefCell::default(),
351                root: Cell::new(None),
352                stats: Cell::new(Stats::default()),
353                measures: RefCell::new(HashMap::new()),
354                interact: Cell::new(InteractState::default()),
355                environment: RefCell::new(Environment::new()),
356                env_listener: RefCell::new(None),
357                shapes: sequoia_draw::ShapeCache::default(),
358                redraw: RefCell::new(None),
359                surfaces: RefCell::new(Vec::new()),
360                overlays: RefCell::default(),
361                pending_drag: RefCell::new(None),
362                active_drag: RefCell::new(None),
363                next_drag_id: Cell::new(0),
364                viewport: Cell::new(Rect::ZERO),
365                root_fill: Cell::new(false),
366                window_size: Signal::new(Size::ZERO),
367                scale: Cell::new(1.0),
368                safe_insets: Cell::new(Edges::ZERO),
369                errors: RefCell::new(Vec::new()),
370                errors_seq: Signal::new(0),
371                stylus_pressure: Cell::new(1.0),
372                stylus_tool: Cell::new(PenTool::Mouse),
373                touches: RefCell::new(HashMap::new()),
374                window: Cell::new(WindowId::PRIMARY),
375                wrap_cache: RefCell::new(HashMap::new()),
376                live_anims: RefCell::new(HashMap::new()),
377                clock: Cell::new(FrameClock::new()),
378                damage: RefCell::new(crate::damage::DamageTracker::new()),
379                layers: RefCell::new(LayerCache::new(128)),
380                layer_hits: Cell::new(0),
381                layer_misses: Cell::new(0),
382                states: RefCell::new(HashMap::new()),
383                layout_epoch: Cell::new(0),
384                layout_dirty: Cell::new(true),
385                scoped_cancels: RefCell::new(HashMap::new()),
386                shader_ids: RefCell::new(HashMap::new()),
387                next_shader_id: Cell::new(0),
388            }),
389        }
390    }
391
392    /// Associates a shader name with a backend [`ShaderId`].
393    pub fn register_shader_id_with(&self, name: impl Into<String>, id: ShaderId) {
394        self.inner.shader_ids.borrow_mut().insert(name.into(), id);
395    }
396
397    /// Registers a shader name and returns a stable headless [`ShaderId`].
398    pub fn register_shader_id(&self, name: impl Into<String>) -> ShaderId {
399        let name = name.into();
400        if let Some(id) = self.inner.shader_ids.borrow().get(&name).copied() {
401            return id;
402        }
403        let id = ShaderId::new(self.inner.next_shader_id.get());
404        self.inner
405            .next_shader_id
406            .set(self.inner.next_shader_id.get() + 1);
407        self.inner.shader_ids.borrow_mut().insert(name, id);
408        id
409    }
410
411    /// Looks up a registered shader name.
412    pub fn shader_id(&self, name: &str) -> Option<ShaderId> {
413        self.inner.shader_ids.borrow().get(name).copied()
414    }
415
416    /// Names of every shader registered on this runtime, sorted.
417    pub fn shader_names(&self) -> Vec<String> {
418        let mut names: Vec<String> = self.inner.shader_ids.borrow().keys().cloned().collect();
419        names.sort();
420        names
421    }
422
423    /// Whether a shader with `name` is registered.
424    pub fn has_shader(&self, name: &str) -> bool {
425        self.inner.shader_ids.borrow().contains_key(name)
426    }
427
428    pub fn window_id(&self) -> WindowId {
429        self.inner.window.get()
430    }
431
432    pub fn set_window_id(&self, id: WindowId) {
433        self.inner.window.set(id);
434    }
435
436    pub fn window_size(&self) -> Signal<Size> {
437        self.inner.window_size.clone()
438    }
439
440    pub fn window_size_value(&self) -> Size {
441        self.inner.window_size.get_untracked()
442    }
443
444    pub fn window_rect(&self) -> Rect {
445        self.inner.viewport.get()
446    }
447
448    pub fn viewport_rect(&self) -> Rect {
449        self.inner.viewport.get()
450    }
451
452    pub fn set_root_fill(&self, enabled: bool) {
453        if self.inner.root_fill.get() == enabled {
454            return;
455        }
456        self.inner.root_fill.set(enabled);
457        self.inner.layout_dirty.set(true);
458        self.mark_full_damage();
459    }
460
461    pub fn scale(&self) -> f32 {
462        self.inner.scale.get()
463    }
464
465    pub fn set_scale(&self, scale: f32) {
466        let s = if scale.is_finite() && scale > 0.0 {
467            scale
468        } else {
469            1.0
470        };
471        if self.inner.scale.get() == s {
472            return;
473        }
474        self.inner.scale.set(s);
475        self.mark_full_damage();
476    }
477
478    pub fn safe_insets(&self) -> Edges {
479        self.inner.safe_insets.get()
480    }
481
482    pub fn set_safe_insets(&self, insets: Edges) {
483        if self.inner.safe_insets.get() == insets {
484            return;
485        }
486        self.inner.safe_insets.set(insets);
487        self.inner.layout_dirty.set(true);
488        self.mark_full_damage();
489    }
490
491    pub fn set_environment(&self, env: Environment) {
492        *self.inner.environment.borrow_mut() = env.clone();
493        self.inner.layout_dirty.set(true);
494        self.mark_full_damage();
495        let weak = Rc::downgrade(&self.inner);
496        *self.inner.env_listener.borrow_mut() = Some(env.on_change(move || {
497            if let Some(inner) = weak.upgrade() {
498                inner.layout_dirty.set(true);
499                inner.layers.borrow_mut().invalidate_all();
500                inner.damage.borrow_mut().mark_full();
501                if let Some(h) = inner.redraw.borrow().as_ref() {
502                    h();
503                }
504            }
505        }));
506    }
507
508    pub fn set_redraw_hook(&self, hook: impl Fn() + 'static) {
509        *self.inner.redraw.borrow_mut() = Some(Box::new(hook));
510    }
511
512    fn request_redraw(&self) {
513        self.inner.damage.borrow_mut().mark(Dirty::PAINT);
514        if let Some(h) = self.inner.redraw.borrow().as_ref() {
515            h();
516        }
517    }
518
519    pub fn request_frame(&self) {
520        self.inner.damage.borrow_mut().request_frame();
521        self.request_redraw();
522    }
523
524    /// Mark an element as damaged. Uses its current bounds for partial repaint.
525    pub(crate) fn mark_damage(&self, el: ElementId, flags: Dirty) {
526        let repaint = flags.needs_repaint();
527        if repaint {
528            self.inner.bump_revision(el);
529        }
530        let rect = if repaint { self.rect_of(el) } else { None };
531        let mut damage = self.inner.damage.borrow_mut();
532        damage.mark(flags);
533        if let Some(rect) = rect {
534            damage.mark_rect(rect);
535        }
536    }
537
538    /// Mark the whole viewport as damaged (resize, DPI, theme, root swap).
539    pub fn mark_full_damage(&self) {
540        self.inner.layers.borrow_mut().invalidate_all();
541        self.inner.damage.borrow_mut().mark_full();
542    }
543
544    /// Drops all cached layer snapshots.
545    pub fn clear_layer_cache(&self) {
546        self.inner.layers.borrow_mut().invalidate_all();
547    }
548
549    /// Returns `(hits, misses)` for the layer cache.
550    pub fn layer_cache_stats(&self) -> (u64, u64) {
551        (self.inner.layer_hits.get(), self.inner.layer_misses.get())
552    }
553
554    pub fn needs_frame(&self) -> bool {
555        self.inner.damage.borrow().needs_frame()
556    }
557
558    pub fn damage_flags(&self) -> Dirty {
559        self.inner.damage.borrow().flags()
560    }
561
562    pub fn damage_rect(&self) -> Option<Rect> {
563        self.inner.damage.borrow().paint_region()
564    }
565
566    /// Returns the disjoint damage regions for the next frame.
567    ///
568    /// `None` means a full repaint. Overlapping marks are merged, and a
569    /// bounded set of regions is kept so a backend can scissor each one
570    /// without unbounded work.
571    pub fn damage_regions(&self) -> Option<Vec<Rect>> {
572        self.inner.damage.borrow().paint_regions()
573    }
574
575    pub fn damage_stats(&self) -> (u64, u64) {
576        let damage = self.inner.damage.borrow();
577        (damage.frames_painted(), damage.frames_skipped())
578    }
579
580    /// Paint only when something changed since the previous frame.
581    ///
582    /// A frame is skipped when there is no pending animation request and the
583    /// accumulated flags do not require pixels (`Dirty::needs_repaint`).
584    /// Bookkeeping-only flags (`HIT_TEST`, `DATA`) are discarded by the skip.
585    /// Returns [`FrameOutcome::Skipped`] without building a render list.
586    pub fn paint_if_dirty(
587        &self,
588        list: &mut RenderList,
589        text: &mut TextSystem,
590    ) -> crate::damage::FrameOutcome {
591        let had_motion = self.inner.damage.borrow_mut().take_motion();
592        if !had_motion && self.inner.damage.borrow().is_empty() {
593            self.inner.damage.borrow_mut().record_skipped();
594            return crate::damage::FrameOutcome::Skipped;
595        }
596        if had_motion {
597            self.inner.layers.borrow_mut().invalidate_all();
598        }
599        self.paint_region(list, text, CullCtx::ENABLED);
600        self.inner.damage.borrow_mut().after_paint();
601        crate::damage::FrameOutcome::Painted
602    }
603
604    /// Snapshot of the frame clock used by every view animation.
605    pub fn clock(&self) -> FrameClock {
606        self.inner.clock.get()
607    }
608
609    pub fn now(&self) -> std::time::Instant {
610        self.inner.clock.get().now()
611    }
612
613    pub fn elapsed_secs(&self) -> f32 {
614        self.inner.clock.get().elapsed_secs()
615    }
616
617    pub fn frame_dt_secs(&self) -> f32 {
618        self.inner.clock.get().dt_secs()
619    }
620
621    pub fn reduced_motion(&self) -> bool {
622        self.inner.clock.get().reduced_motion()
623    }
624
625    pub fn set_reduced_motion(&self, reduced: bool) {
626        let mut c = self.inner.clock.get();
627        if c.reduced_motion() == reduced {
628            return;
629        }
630        c.set_reduced_motion(reduced);
631        self.inner.clock.set(c);
632        self.inner.layout_dirty.set(true);
633        self.request_redraw();
634    }
635
636    pub fn set_animation_speed(&self, speed: f32) {
637        let mut c = self.inner.clock.get();
638        c.set_speed(speed);
639        self.inner.clock.set(c);
640        self.request_redraw();
641    }
642
643    pub fn animation_speed(&self) -> f32 {
644        self.inner.clock.get().speed()
645    }
646
647    /// Switch to explicit time control. Hosts and tests can then call
648    /// [`Runtime::advance_for_test`] without coupling to wall time.
649    pub fn set_manual_clock(&self, manual: bool) {
650        let mut c = self.inner.clock.get();
651        c.set_manual(manual);
652        self.inner.clock.set(c);
653    }
654
655    /// Advance the shared clock by an exact duration.
656    ///
657    /// This also flips the clock into manual mode, so subsequent `tick` calls
658    /// do not advance it from the wall clock.
659    pub fn advance_for_test(&self, dt: std::time::Duration) {
660        let mut c = self.inner.clock.get();
661        c.set_manual(true);
662        c.advance_by(dt);
663        self.inner.clock.set(c);
664        self.invalidate_animated_layout();
665    }
666
667    pub fn reset_clock(&self) {
668        let mut c = self.inner.clock.get();
669        c.reset();
670        self.inner.clock.set(c);
671        self.invalidate_animated_layout();
672    }
673
674    fn has_layout_animation(&self) -> bool {
675        self.inner
676            .live_anims
677            .borrow()
678            .keys()
679            .any(|(_, prop)| *prop == Prop::WIDTH.0 || *prop == Prop::HEIGHT.0)
680    }
681
682    fn invalidate_animated_layout(&self) {
683        if self.has_layout_animation() {
684            self.inner.layout_dirty.set(true);
685        }
686    }
687
688    fn advance_clock(&self) {
689        let mut c = self.inner.clock.get();
690        c.advance();
691        self.inner.clock.set(c);
692        self.invalidate_animated_layout();
693    }
694
695    pub fn live_f32(
696        &self,
697        el: ElementId,
698        prop: Prop,
699        target: f32,
700        tween: Option<&AnimSpec>,
701        spring: Option<&SpringSpec>,
702    ) -> (f32, bool) {
703        let key = (el, prop.0);
704        if self.reduced_motion() || (tween.is_none() && spring.is_none()) {
705            self.inner.live_anims.borrow_mut().remove(&key);
706            return (target, false);
707        }
708        let now = self.now();
709        let mut map = self.inner.live_anims.borrow_mut();
710        let entry = map.entry(key).or_insert_with(|| {
711            AnimState::F32(crate::live::F32State {
712                current: target,
713                from: target,
714                start: now,
715                last_target: target,
716                tween: tween.copied(),
717                spring: spring.copied(),
718                velocity: 0.0,
719                last_tick: now,
720                initialized: false,
721            })
722        });
723
724        if !matches!(entry, AnimState::F32(_)) {
725            *entry = AnimState::F32(crate::live::F32State {
726                current: target,
727                from: target,
728                start: now,
729                last_target: target,
730                tween: tween.copied(),
731                spring: spring.copied(),
732                velocity: 0.0,
733                last_tick: now,
734                initialized: false,
735            });
736        }
737        let AnimState::F32(st) = entry else {
738            return (target, false);
739        };
740        let (v, running) = AnimState::sample_f32(st, target, tween, spring, now);
741        drop(map);
742        if running {
743            self.request_frame();
744        }
745        (v, running)
746    }
747
748    pub fn live_color(
749        &self,
750        el: ElementId,
751        prop: Prop,
752        target: sequoia_core::Color,
753        tween: Option<&AnimSpec>,
754    ) -> (sequoia_core::Color, bool) {
755        let key = (el, prop.0);
756        if self.reduced_motion() || tween.is_none() {
757            self.inner.live_anims.borrow_mut().remove(&key);
758            return (target, false);
759        }
760        let now = self.now();
761        let mut map = self.inner.live_anims.borrow_mut();
762        let entry = map.entry(key).or_insert_with(|| {
763            AnimState::Color(crate::live::ColorState {
764                current: target,
765                from: target,
766                start: now,
767                last_target: target,
768                tween: tween.copied(),
769                smooth_rate: None,
770                last_tick: now,
771                initialized: false,
772            })
773        });
774        if !matches!(entry, AnimState::Color(_)) {
775            *entry = AnimState::Color(crate::live::ColorState {
776                current: target,
777                from: target,
778                start: now,
779                last_target: target,
780                tween: tween.copied(),
781                smooth_rate: None,
782                last_tick: now,
783                initialized: false,
784            });
785        }
786        let AnimState::Color(st) = entry else {
787            return (target, false);
788        };
789        let (v, running) = AnimState::sample_color(st, target, tween, None, now);
790        drop(map);
791        if running {
792            self.request_frame();
793        }
794        (v, running)
795    }
796
797    pub fn live_color_exp(
798        &self,
799        el: ElementId,
800        prop: Prop,
801        target: sequoia_core::Color,
802        rate: f32,
803    ) -> (sequoia_core::Color, bool) {
804        let key = (el, prop.0);
805        if self.reduced_motion() || !(rate > 0.0) {
806            self.inner.live_anims.borrow_mut().remove(&key);
807            return (target, false);
808        }
809        let now = self.now();
810        let mut map = self.inner.live_anims.borrow_mut();
811        let entry = map.entry(key).or_insert_with(|| {
812            AnimState::Color(crate::live::ColorState {
813                current: target,
814                from: target,
815                start: now,
816                last_target: target,
817                tween: None,
818                smooth_rate: Some(rate),
819                last_tick: now,
820                initialized: false,
821            })
822        });
823        if !matches!(entry, AnimState::Color(_)) {
824            *entry = AnimState::Color(crate::live::ColorState {
825                current: target,
826                from: target,
827                start: now,
828                last_target: target,
829                tween: None,
830                smooth_rate: Some(rate),
831                last_tick: now,
832                initialized: false,
833            });
834        }
835        let AnimState::Color(st) = entry else {
836            return (target, false);
837        };
838        let (v, running) = AnimState::sample_color(st, target, None, Some(rate), now);
839        drop(map);
840        if running {
841            self.request_frame();
842        }
843        (v, running)
844    }
845
846    pub fn live_vec2(
847        &self,
848        el: ElementId,
849        prop: Prop,
850        target: sequoia_core::Vec2,
851        tween: Option<&AnimSpec>,
852        spring: Option<&SpringSpec>,
853    ) -> (sequoia_core::Vec2, bool) {
854        let key = (el, prop.0);
855        if self.reduced_motion() || (tween.is_none() && spring.is_none()) {
856            self.inner.live_anims.borrow_mut().remove(&key);
857            return (target, false);
858        }
859        let now = self.now();
860        let mut map = self.inner.live_anims.borrow_mut();
861        let entry = map.entry(key).or_insert_with(|| {
862            AnimState::Vec2(crate::live::Vec2State {
863                current: target,
864                from: target,
865                start: now,
866                last_target: target,
867                tween: tween.copied(),
868                spring: spring.copied(),
869                velocity: sequoia_core::Vec2::ZERO,
870                last_tick: now,
871                initialized: false,
872            })
873        });
874        if !matches!(entry, AnimState::Vec2(_)) {
875            *entry = AnimState::Vec2(crate::live::Vec2State {
876                current: target,
877                from: target,
878                start: now,
879                last_target: target,
880                tween: tween.copied(),
881                spring: spring.copied(),
882                velocity: sequoia_core::Vec2::ZERO,
883                last_tick: now,
884                initialized: false,
885            });
886        }
887        let AnimState::Vec2(st) = entry else {
888            return (target, false);
889        };
890        let (v, running) = AnimState::sample_vec2(st, target, tween, spring, now);
891        drop(map);
892        if running {
893            self.request_frame();
894        }
895        (v, running)
896    }
897
898    pub fn resolve_live_f32(&self, el: ElementId, prop: Prop, live: &Live<f32>) -> f32 {
899        let target = live.target_untracked();
900        let (v, _) = self.live_f32(
901            el,
902            prop,
903            target,
904            live.tween_spec().as_ref(),
905            live.spring_spec().as_ref(),
906        );
907        v
908    }
909
910    pub fn resolve_live_vec2(
911        &self,
912        el: ElementId,
913        prop: Prop,
914        live: &Live<sequoia_core::Vec2>,
915    ) -> sequoia_core::Vec2 {
916        let target = live.target_untracked();
917        let (v, _) = self.live_vec2(
918            el,
919            prop,
920            target,
921            live.tween_spec().as_ref(),
922            live.spring_spec().as_ref(),
923        );
924        v
925    }
926
927    pub fn live_anim_count(&self) -> usize {
928        self.inner.live_anims.borrow().len()
929    }
930
931    pub fn shapes(&self) -> &sequoia_draw::ShapeCache {
932        &self.inner.shapes
933    }
934
935    pub fn mount(&self, root: impl View + 'static) {
936        self.mount_any(AnyView::new(root));
937    }
938
939    pub fn mount_any(&self, root: AnyView) {
940        if let Some(old) = self.inner.root.get() {
941            self.unmount_subtree(old);
942        }
943        let value = unwrap_any(root.into_inner());
944        let id = self.alloc(value, None, 0, 0);
945        if let Some(v) = self.view_rc(id) {
946            self.build_with(id, v);
947        }
948        if let Some(v) = self.view_rc(id) {
949            let type_name = v.type_name().to_string();
950            if std::panic::catch_unwind(AssertUnwindSafe(|| {
951                v.on_mount();
952            }))
953            .is_err()
954            {
955                self.record_error(id, type_name, "on_mount panicked".to_string());
956            }
957        }
958        self.inner.root.set(Some(id));
959        self.mark_full_damage();
960    }
961
962    pub fn with_state<S: Default + 'static, R>(
963        &self,
964        el: ElementId,
965        f: impl FnOnce(&mut S) -> R,
966    ) -> R {
967        struct Reinsert<'a> {
968            map: &'a RefCell<HashMap<ElementId, Box<dyn Any>>>,
969            el: ElementId,
970            slot: Option<Box<dyn Any>>,
971        }
972        impl Drop for Reinsert<'_> {
973            fn drop(&mut self) {
974                if let Some(slot) = self.slot.take() {
975                    self.map.borrow_mut().insert(self.el, slot);
976                }
977            }
978        }
979
980        let slot = match self.inner.states.borrow_mut().remove(&el) {
981            Some(b) if b.is::<S>() => b,
982
983            Some(_) | None => Box::new(S::default()),
984        };
985        let mut guard = Reinsert {
986            map: &self.inner.states,
987            el,
988            slot: Some(slot),
989        };
990        let s = guard
991            .slot
992            .as_mut()
993            .and_then(|b| b.downcast_mut::<S>())
994            .expect("slot ma typ S po sprawdzeniu is::<S>()");
995        f(s)
996    }
997
998    pub fn read_state<S: Default + 'static, R>(&self, el: ElementId, f: impl FnOnce(&S) -> R) -> R {
999        self.with_state::<S, R>(el, |s| f(s))
1000    }
1001
1002    pub fn state_count(&self) -> usize {
1003        self.inner.states.borrow().len()
1004    }
1005
1006    pub fn has_state(&self, el: ElementId) -> bool {
1007        self.inner.states.borrow().contains_key(&el)
1008    }
1009
1010    pub fn track_scoped(&self, el: ElementId, cancel: impl FnOnce() + 'static) {
1011        if !self.is_alive(el) {
1012            cancel();
1013            return;
1014        }
1015        self.inner
1016            .scoped_cancels
1017            .borrow_mut()
1018            .entry(el)
1019            .or_default()
1020            .push(Box::new(cancel));
1021    }
1022
1023    pub fn scoped_count(&self) -> usize {
1024        self.inner
1025            .scoped_cancels
1026            .borrow()
1027            .values()
1028            .map(|v| v.len())
1029            .sum()
1030    }
1031
1032    pub fn request_rebuild(&self, el: ElementId) {
1033        self.inner.schedule(el);
1034    }
1035
1036    pub fn tick(&self) {
1037        self.advance_clock();
1038        self.settle();
1039    }
1040
1041    fn settle(&self) {
1042        flush_effects();
1043        loop {
1044            let next = self.inner.queue.borrow_mut().pop();
1045            let Some(id) = next else { break };
1046            let flags = {
1047                self.inner
1048                    .arena
1049                    .borrow()
1050                    .get(id)
1051                    .map(|e| e.dirty)
1052                    .unwrap_or_else(Dirty::empty)
1053            };
1054            if flags.is_empty() {
1055                continue;
1056            }
1057            if let Some(e) = self.inner.arena.borrow_mut().get_mut(id) {
1058                e.dirty = Dirty::empty();
1059            }
1060            if !flags.needs_layout() {
1061                continue;
1062            }
1063            let Some(v) = self.view_rc(id) else { continue };
1064            self.build_with(id, v);
1065            self.mark_damage(id, Dirty::PAINT);
1066            let mut s = self.inner.stats.get();
1067            s.rebuilds += 1;
1068            self.inner.stats.set(s);
1069        }
1070    }
1071
1072    #[must_use]
1073    pub fn root(&self) -> Option<ElementId> {
1074        self.inner.root.get()
1075    }
1076
1077    pub fn is_alive(&self, id: ElementId) -> bool {
1078        self.inner.arena.borrow().get(id).is_some()
1079    }
1080
1081    pub fn children(&self, id: ElementId) -> Vec<ElementId> {
1082        self.inner
1083            .arena
1084            .borrow()
1085            .get(id)
1086            .map(|e| e.children.clone())
1087            .unwrap_or_default()
1088    }
1089
1090    pub fn children_len(&self, id: ElementId) -> usize {
1091        self.inner
1092            .arena
1093            .borrow()
1094            .get(id)
1095            .map(|e| e.children.len())
1096            .unwrap_or(0)
1097    }
1098
1099    pub fn key_of(&self, id: ElementId) -> Option<u64> {
1100        self.inner.arena.borrow().get(id).map(|e| e.key)
1101    }
1102
1103    pub fn rect_of(&self, id: ElementId) -> Option<Rect> {
1104        self.inner.arena.borrow().get(id).map(|e| e.rect.get())
1105    }
1106    pub fn find_by_test_id(&self, test_id: &str) -> Option<ElementId> {
1107        let arena = self.inner.arena.borrow();
1108        for (i, slot) in arena.slots.iter().enumerate() {
1109            let Some(el) = slot else { continue };
1110            let id = ElementId {
1111                index: i as u32,
1112                generation: arena.generations[i],
1113            };
1114            if arena.get(id).is_none() {
1115                continue;
1116            }
1117            let Some(view) = el.view.clone() else {
1118                continue;
1119            };
1120            if view.test_id_erased().as_deref() == Some(test_id) {
1121                return Some(id);
1122            }
1123        }
1124        None
1125    }
1126
1127    pub fn element_count(&self) -> usize {
1128        self.inner.arena.borrow().alive()
1129    }
1130
1131    pub fn stats(&self) -> Stats {
1132        self.inner.stats.get()
1133    }
1134
1135    pub fn errors(&self) -> Vec<RuntimeError> {
1136        self.inner.errors.borrow().clone()
1137    }
1138
1139    pub fn errors_seq(&self) -> Signal<u64> {
1140        self.inner.errors_seq.clone()
1141    }
1142
1143    pub fn clear_errors(&self) {
1144        self.inner.errors.borrow_mut().clear();
1145    }
1146
1147    pub fn is_descendant_of(&self, el: ElementId, ancestor: ElementId) -> bool {
1148        let mut cur = Some(el);
1149        while let Some(e) = cur {
1150            if e == ancestor {
1151                return true;
1152            }
1153            cur = self.parent_of(e);
1154        }
1155        false
1156    }
1157
1158    fn record_error(&self, element: ElementId, type_name: String, message: String) {
1159        let err = RuntimeError {
1160            element,
1161            type_name,
1162            message,
1163        };
1164        {
1165            let mut errors = self.inner.errors.borrow_mut();
1166            if errors.len() >= MAX_ERRORS {
1167                errors.remove(0);
1168            }
1169            errors.push(err.clone());
1170        }
1171        let mut s = self.inner.stats.get();
1172        s.errors += 1;
1173        self.inner.stats.set(s);
1174        self.inner.errors_seq.update(|n| *n += 1);
1175        self.notify_boundaries(element, &err);
1176    }
1177
1178    fn notify_boundaries(&self, element: ElementId, err: &RuntimeError) {
1179        let chain: Vec<ElementId> = {
1180            let Ok(arena) = self.inner.arena.try_borrow() else {
1181                return;
1182            };
1183            let mut out = Vec::new();
1184            let mut cur = arena.get(element).and_then(|_| self.parent_of(element));
1185            while let Some(id) = cur {
1186                out.push(id);
1187                cur = arena.get(id).and_then(|e| e.parent);
1188            }
1189            out
1190        };
1191        for id in chain {
1192            if let Some(boundary) = self.view::<crate::error_boundary::ErrorBoundary>(id) {
1193                boundary.notify(self, id, err);
1194                return;
1195            }
1196        }
1197    }
1198
1199    pub fn interact(&self) -> InteractState {
1200        self.inner.interact.get()
1201    }
1202
1203    pub fn pointer_pos(&self) -> Option<Point> {
1204        let st = self.inner.interact.get();
1205        st.pointer_seen.then_some(st.cursor)
1206    }
1207
1208    fn subtree_wants_hover(&self, mut el: Option<ElementId>) -> bool {
1209        while let Some(e) = el {
1210            if self
1211                .view_rc(e)
1212                .map(|v| v.wants_hover_updates_erased())
1213                .unwrap_or(false)
1214            {
1215                return true;
1216            }
1217            el = self.parent_of(e);
1218        }
1219        false
1220    }
1221
1222    pub fn cursor_icon(&self) -> CursorIcon {
1223        if self.inner.active_drag.borrow().is_some() {
1224            return CursorIcon::Grabbing;
1225        }
1226        let st = self.inner.interact.get();
1227        let mut cur = st.captured.or(st.hovered);
1228        while let Some(id) = cur {
1229            let Some(v) = self.view_rc(id) else {
1230                break;
1231            };
1232            let (state, _) = self.view_state(id);
1233            let icon = v.cursor_icon_erased(state);
1234            if icon != CursorIcon::Default {
1235                return icon;
1236            }
1237            cur = self.parent_of(id);
1238        }
1239        CursorIcon::Default
1240    }
1241
1242    pub fn type_name_of(&self, id: ElementId) -> Option<String> {
1243        self.inner
1244            .arena
1245            .borrow()
1246            .get(id)?
1247            .view
1248            .as_ref()
1249            .map(|v| v.type_name().to_string())
1250    }
1251
1252    pub fn view<T: View + 'static>(&self, id: ElementId) -> Option<Rc<T>> {
1253        let rc = self.view_rc(id)?;
1254        let any: Rc<dyn Any> = rc.as_any_rc();
1255        any.downcast::<T>().ok()
1256    }
1257
1258    /// Whether the next [`Runtime::layout`] call has work to do.
1259    ///
1260    /// True for structural rebuilds and pending layout marks.
1261    /// Paint-only damage leaves the layout clean.
1262    pub fn layout_dirty(&self) -> bool {
1263        self.inner.layout_dirty.get() || self.has_pending_layout()
1264    }
1265
1266    pub(crate) fn invalidate_layout(&self) {
1267        self.inner.layout_dirty.set(true);
1268    }
1269
1270    /// Solves layout for `bounds`.
1271    ///
1272    /// The pass is skipped when the viewport and [`Runtime::layout_dirty`] are
1273    /// clean. Structural changes, scroll offsets, layout animations, and new
1274    /// viewports mark layout dirty.
1275    pub fn layout(&self, bounds: Rect, text: &mut TextSystem) {
1276        const MAX_SETTLE_PASSES: u8 = 4;
1277        if self.inner.viewport.get() == bounds
1278            && !self.inner.layout_dirty.get()
1279            && !self.has_pending_layout()
1280        {
1281            return;
1282        }
1283        for pass in 0..MAX_SETTLE_PASSES {
1284            self.inner.layout_dirty.set(false);
1285            self.layout_once(bounds, text);
1286            if !self.has_pending_layout() {
1287                return;
1288            }
1289            if pass + 1 < MAX_SETTLE_PASSES {
1290                self.settle();
1291            }
1292        }
1293        log::warn!(
1294            "sequoia-view: layout still dirty after {MAX_SETTLE_PASSES} passes; \
1295             a view is scheduling layout while being laid out"
1296        );
1297    }
1298
1299    fn has_pending_layout(&self) -> bool {
1300        let queue = self.inner.queue.borrow();
1301        if queue.is_empty() {
1302            return false;
1303        }
1304        let arena = self.inner.arena.borrow();
1305        queue.iter().any(|id| {
1306            arena
1307                .get(*id)
1308                .map(|e| e.dirty.needs_layout())
1309                .unwrap_or(false)
1310        })
1311    }
1312
1313    fn layout_once(&self, bounds: Rect, text: &mut TextSystem) {
1314        let Some(root) = self.inner.root.get() else {
1315            return;
1316        };
1317        self.inner.wrap_cache.borrow_mut().clear();
1318        if self.inner.viewport.get() != bounds {
1319            self.inner.layers.borrow_mut().invalidate_all();
1320            let mut damage = self.inner.damage.borrow_mut();
1321            damage.mark_full();
1322            damage.mark_layout();
1323        }
1324        self.inner.viewport.set(bounds);
1325
1326        self.inner.window_size.set(bounds.size);
1327        let default_font_size = self
1328            .inner
1329            .environment
1330            .borrow()
1331            .get::<Theme>()
1332            .unwrap_or_else(Theme::dark)
1333            .font_size;
1334        let mut ctx = LayoutCtx::new(text, default_font_size);
1335        let c = Constraints::loose(Size::new(bounds.size.width, bounds.size.height));
1336        let measured = self.measure(root, c, &mut ctx);
1337        let root_size = if self.inner.root_fill.get() {
1338            filled_root_size(self.child_layout_style(root), bounds.size, measured)
1339        } else {
1340            measured
1341        };
1342        self.warn_if_root_not_fill(root, bounds, root_size);
1343        let root_rect = Rect::new(bounds.origin, root_size);
1344        self.arrange(root, root_rect, &mut ctx);
1345
1346        self.layout_overlays(bounds, &mut ctx);
1347    }
1348
1349    pub fn paint(&self, list: &mut RenderList, text: &mut TextSystem) {
1350        self.paint_region(list, text, CullCtx::ENABLED);
1351    }
1352
1353    /// Paint with recoverable culling driven by `cull`.
1354    ///
1355    /// `paint` keeps the enabled culling path that this runtime used before
1356    /// the damage work, so existing benchmarks and callers do not pay for
1357    /// off-screen shaping. `CullCtx::DISABLED` is available for callers that
1358    /// need a full CPU pass; RenderList still drops items fully outside the
1359    /// active clip at push time, so the visible output is the same.
1360    pub fn paint_region(&self, list: &mut RenderList, text: &mut TextSystem, cull: CullCtx) {
1361        self.inner.surfaces.borrow_mut().clear();
1362
1363        let doomed = self.inner.overlays.borrow().transient_ids();
1364        for id in doomed {
1365            self.overlay_hide(id);
1366        }
1367        let Some(root) = self.inner.root.get() else {
1368            return;
1369        };
1370        let env = self.inner.environment.borrow().clone();
1371        let hover_chain = self.hover_chain();
1372        let pass = PaintPass {
1373            env: &env,
1374            hover_chain: &hover_chain,
1375        };
1376        self.paint_rec(
1377            root,
1378            Vec2::ZERO,
1379            1.0,
1380            Transform::IDENTITY,
1381            list,
1382            text,
1383            cull,
1384            pass,
1385        );
1386
1387        {
1388            let viewport = self.inner.viewport.get();
1389            let default_font_size = self
1390                .inner
1391                .environment
1392                .borrow()
1393                .get::<Theme>()
1394                .unwrap_or_else(Theme::dark)
1395                .font_size;
1396            let mut ctx = LayoutCtx::new(&mut *text, default_font_size);
1397            self.layout_overlays(viewport, &mut ctx);
1398        }
1399
1400        let ids = self.inner.overlays.borrow().ordered_ids();
1401        for id in ids {
1402            let el = self
1403                .inner
1404                .overlays
1405                .borrow()
1406                .get(id)
1407                .and_then(|e| e.element.get());
1408            if let Some(el) = el {
1409                self.paint_rec(
1410                    el,
1411                    Vec2::ZERO,
1412                    1.0,
1413                    Transform::IDENTITY,
1414                    list,
1415                    text,
1416                    cull,
1417                    pass,
1418                );
1419            }
1420        }
1421
1422        self.inner
1423            .live_anims
1424            .borrow_mut()
1425            .retain(|(el, _), _| self.inner.arena.borrow().get(*el).is_some());
1426    }
1427
1428    pub fn take_surface_draws(&self) -> Vec<SurfaceDraw> {
1429        std::mem::take(&mut *self.inner.surfaces.borrow_mut())
1430    }
1431
1432    pub fn hit_test(&self, pos: Point) -> Option<ElementId> {
1433        if let Some(hit) = self.hit_overlays(pos) {
1434            return Some(hit);
1435        }
1436
1437        let root = self.inner.root.get()?;
1438        self.hit_rec(root, pos)
1439    }
1440
1441    fn hit_rec(&self, el: ElementId, pos: Point) -> Option<ElementId> {
1442        let rect = {
1443            let arena = self.inner.arena.borrow();
1444            arena.get(el)?.rect.get()
1445        };
1446        if !rect.contains(pos) {
1447            return None;
1448        }
1449        for c in self.children(el).iter().rev() {
1450            if let Some(h) = self.hit_rec(*c, pos) {
1451                return Some(h);
1452            }
1453        }
1454        let hitable = self
1455            .view_rc(el)
1456            .map(|v| v.hit_testable_erased())
1457            .unwrap_or(true);
1458        if hitable { Some(el) } else { None }
1459    }
1460
1461    /// Moves the pointer without keyboard modifiers.
1462    ///
1463    /// Prefer [`Runtime::pointer_move_with_mods`] on platforms that track
1464    /// modifier state.
1465    pub fn pointer_move(&self, pos: Point, text: &mut TextSystem) {
1466        self.pointer_move_with_mods(pos, Modifiers::NONE, text);
1467    }
1468
1469    /// Moves the pointer and reports the current keyboard modifiers on the
1470    /// resulting [`PointerEvent::Move`].
1471    pub fn pointer_move_with_mods(&self, pos: Point, modifiers: Modifiers, text: &mut TextSystem) {
1472        let mut st = self.inner.interact.get();
1473        st.cursor = pos;
1474        st.pointer_seen = true;
1475        let old_hover = st.hovered;
1476        if st.captured.is_none() {
1477            st.hovered = self.hit_test(pos);
1478            self.inner.interact.set(st);
1479        }
1480        if old_hover != st.hovered {
1481            for el in [old_hover, st.hovered].iter().flatten() {
1482                self.inner.mark(*el, Dirty::PAINT);
1483            }
1484            self.request_redraw();
1485        }
1486
1487        if self.subtree_wants_hover(st.hovered) {
1488            self.request_redraw();
1489        }
1490
1491        if self.inner.pending_drag.borrow().is_some() || self.inner.active_drag.borrow().is_some() {
1492            self.poll_pending(pos, modifiers, text);
1493        }
1494        let ev = Event::Pointer(PointerEvent::Move { pos, modifiers });
1495        match st.captured {
1496            Some(c) => {
1497                let _ = self.dispatch_single(c, &ev, pos, text);
1498            }
1499            None => {
1500                if let Some(t) = st.hovered {
1501                    let _ = self.dispatch_single(t, &ev, pos, text);
1502                }
1503            }
1504        }
1505        if let Event::Pointer(pe) = &ev {
1506            match st.captured {
1507                Some(c) => self.forward_surface_pointer(c, pe),
1508                None => {
1509                    if let Some(t) = st.hovered {
1510                        self.forward_surface_pointer(t, pe);
1511                    }
1512                }
1513            }
1514        }
1515    }
1516
1517    fn forward_surface_pointer(&self, el: ElementId, event: &PointerEvent) {
1518        let Some(v) = self.view_rc(el) else { return };
1519        let Some(mut binding) = v.surface_input_erased() else {
1520            return;
1521        };
1522        let Some(rect) = self.rect_of(el) else { return };
1523        (binding.on_pointer)(event, rect);
1524    }
1525
1526    fn forward_surface_key(&self, el: ElementId, event: &KeyEvent) {
1527        let Some(v) = self.view_rc(el) else { return };
1528        let Some(mut binding) = v.surface_input_erased() else {
1529            return;
1530        };
1531        (binding.on_key)(event);
1532    }
1533
1534    pub fn pointer_down(
1535        &self,
1536        pos: Point,
1537        button: PointerButton,
1538        mods: Modifiers,
1539        text: &mut TextSystem,
1540    ) {
1541        if button != PointerButton::Left && self.inner.active_drag.borrow().is_some() {
1542            self.cancel_drag(text);
1543            return;
1544        }
1545
1546        let doomed = self.inner.overlays.borrow().outside_dismiss_ids(pos);
1547        for id in doomed {
1548            self.overlay_hide(id);
1549        }
1550        let target = self.hit_test(pos);
1551        {
1552            let mut st = self.inner.interact.get();
1553            st.cursor = pos;
1554            st.pressed = target;
1555
1556            st.focus = match target {
1557                None => None,
1558                Some(t) => self.nearest_focusable(t).or(st.focus),
1559            };
1560
1561            if st.focus.is_some_and(|f| !self.is_alive(f)) {
1562                st.focus = None;
1563            }
1564            self.inner.interact.set(st);
1565        }
1566        self.dismiss_focus_lost(target);
1567        if let Some(t) = target {
1568            let ev = Event::Pointer(PointerEvent::Down {
1569                button,
1570                pos,
1571                modifiers: mods,
1572            });
1573            self.dispatch_chain(t, &ev, pos, text);
1574            if let Event::Pointer(pe) = &ev {
1575                self.forward_surface_pointer(t, pe);
1576            }
1577            if let Some(cap) = self.auto_capture(t) {
1578                let mut st = self.inner.interact.get();
1579                st.captured = Some(cap);
1580                self.inner.interact.set(st);
1581            }
1582        }
1583        self.request_redraw();
1584    }
1585
1586    pub fn pointer_up(
1587        &self,
1588        pos: Point,
1589        button: PointerButton,
1590        mods: Modifiers,
1591        text: &mut TextSystem,
1592    ) {
1593        if self.inner.active_drag.borrow().is_some() {
1594            if button == PointerButton::Left {
1595                self.finish_drop(pos, mods, text);
1596            } else {
1597                self.cancel_drag(text);
1598            }
1599            return;
1600        }
1601
1602        if self.inner.pending_drag.borrow().is_some() {
1603            self.inner.pending_drag.borrow_mut().take();
1604            let mut st = self.inner.interact.get();
1605            st.captured = None;
1606            self.inner.interact.set(st);
1607        }
1608        let mut st = self.inner.interact.get();
1609        st.cursor = pos;
1610        st.hovered = self.hit_test(pos);
1611        self.inner.interact.set(st);
1612        let ev = Event::Pointer(PointerEvent::Up {
1613            button,
1614            pos,
1615            modifiers: mods,
1616        });
1617        match st.captured {
1618            Some(c) => {
1619                let handled = self.dispatch_single(c, &ev, pos, text);
1620
1621                if !handled
1622                    && let Some(p) = st.pressed
1623                    && p != c
1624                    && self.is_alive(p)
1625                {
1626                    self.dispatch_chain(p, &ev, pos, text);
1627                }
1628            }
1629            None => {
1630                if let Some(p) = st.pressed {
1631                    self.dispatch_chain(p, &ev, pos, text);
1632                }
1633            }
1634        }
1635        if let Event::Pointer(pe) = &ev {
1636            match st.captured {
1637                Some(c) => self.forward_surface_pointer(c, pe),
1638                None => {
1639                    if let Some(p) = st.pressed {
1640                        self.forward_surface_pointer(p, pe);
1641                    }
1642                }
1643            }
1644        }
1645        let mut st2 = self.inner.interact.get();
1646        st2.pressed = None;
1647        st2.captured = None;
1648        self.inner.interact.set(st2);
1649        self.request_redraw();
1650    }
1651
1652    pub fn wheel(&self, pos: Point, delta: Vec2, modifiers: Modifiers, text: &mut TextSystem) {
1653        let Some(target) = self.hit_test(pos) else {
1654            return;
1655        };
1656        let ev = Event::Pointer(PointerEvent::Wheel {
1657            delta,
1658            pos,
1659            modifiers,
1660        });
1661        self.dispatch_chain(target, &ev, pos, text);
1662        if let Event::Pointer(pe) = &ev {
1663            self.forward_surface_pointer(target, pe);
1664        }
1665        self.request_redraw();
1666    }
1667
1668    pub fn pointer_pressure(&self) -> f32 {
1669        self.inner.stylus_pressure.get()
1670    }
1671
1672    pub fn pointer_tool(&self) -> PenTool {
1673        self.inner.stylus_tool.get()
1674    }
1675
1676    pub fn active_touches(&self) -> Vec<(u64, Point)> {
1677        self.inner
1678            .touches
1679            .borrow()
1680            .iter()
1681            .map(|(k, v)| (*k, *v))
1682            .collect()
1683    }
1684
1685    fn set_stylus(&self, pressure: f32, tool: PenTool) {
1686        self.inner.stylus_pressure.set(pressure.clamp(0.0, 1.0));
1687        self.inner.stylus_tool.set(tool);
1688    }
1689
1690    pub fn pen_down(
1691        &self,
1692        pos: Point,
1693        button: PointerButton,
1694        pressure: f32,
1695        tilt: Vec2,
1696        tool: PenTool,
1697        mods: Modifiers,
1698        text: &mut TextSystem,
1699    ) {
1700        let _ = tilt;
1701        self.set_stylus(pressure, tool);
1702        self.pointer_down(pos, button, mods, text);
1703    }
1704
1705    pub fn pen_move(
1706        &self,
1707        pos: Point,
1708        pressure: f32,
1709        tilt: Vec2,
1710        tool: PenTool,
1711        text: &mut TextSystem,
1712    ) {
1713        let _ = tilt;
1714        self.set_stylus(pressure, tool);
1715        self.pointer_move(pos, text);
1716    }
1717
1718    pub fn pen_up(
1719        &self,
1720        pos: Point,
1721        button: PointerButton,
1722        pressure: f32,
1723        mods: Modifiers,
1724        text: &mut TextSystem,
1725    ) {
1726        self.set_stylus(pressure, self.inner.stylus_tool.get());
1727        self.pointer_up(pos, button, mods, text);
1728    }
1729
1730    pub fn touch_down(
1731        &self,
1732        id: u64,
1733        pos: Point,
1734        force: Option<f32>,
1735        mods: Modifiers,
1736        text: &mut TextSystem,
1737    ) {
1738        let first = self.inner.touches.borrow().is_empty();
1739        self.inner.touches.borrow_mut().insert(id, pos);
1740        self.set_stylus(force.unwrap_or(1.0), PenTool::Touch);
1741        if first {
1742            self.pointer_down(pos, PointerButton::Left, mods, text);
1743        }
1744        if let Some(t) = self.hit_test(pos) {
1745            let ev = Event::Pointer(PointerEvent::TouchDown {
1746                id,
1747                pos,
1748                force,
1749                modifiers: mods,
1750            });
1751            self.dispatch_chain(t, &ev, pos, text);
1752            if let Event::Pointer(pe) = &ev {
1753                self.forward_surface_pointer(t, pe);
1754            }
1755        }
1756        self.request_redraw();
1757    }
1758
1759    pub fn touch_move(&self, id: u64, pos: Point, force: Option<f32>, text: &mut TextSystem) {
1760        let is_first = self
1761            .inner
1762            .touches
1763            .borrow()
1764            .keys()
1765            .next()
1766            .is_some_and(|first| *first == id);
1767        self.inner.touches.borrow_mut().insert(id, pos);
1768        self.set_stylus(force.unwrap_or(1.0), PenTool::Touch);
1769        if is_first {
1770            self.pointer_move(pos, text);
1771        }
1772        let ev = Event::Pointer(PointerEvent::TouchMove {
1773            id,
1774            pos,
1775            force,
1776            modifiers: Modifiers::NONE,
1777        });
1778
1779        match self.inner.interact.get().captured {
1780            Some(c) => {
1781                self.dispatch_single(c, &ev, pos, text);
1782                if let Event::Pointer(pe) = &ev {
1783                    self.forward_surface_pointer(c, pe);
1784                }
1785            }
1786            None => {
1787                if let Some(t) = self.hit_test(pos) {
1788                    self.dispatch_chain(t, &ev, pos, text);
1789                    if let Event::Pointer(pe) = &ev {
1790                        self.forward_surface_pointer(t, pe);
1791                    }
1792                }
1793            }
1794        }
1795        self.request_redraw();
1796    }
1797
1798    pub fn touch_up(&self, id: u64, pos: Point, mods: Modifiers, text: &mut TextSystem) {
1799        let was_last = {
1800            let mut touches = self.inner.touches.borrow_mut();
1801            touches.remove(&id);
1802            touches.is_empty()
1803        };
1804        let ev = Event::Pointer(PointerEvent::TouchUp {
1805            id,
1806            pos,
1807            modifiers: mods,
1808        });
1809
1810        match self.inner.interact.get().captured {
1811            Some(c) => {
1812                self.dispatch_single(c, &ev, pos, text);
1813                if let Event::Pointer(pe) = &ev {
1814                    self.forward_surface_pointer(c, pe);
1815                }
1816            }
1817            None => {
1818                if let Some(t) = self.hit_test(pos) {
1819                    self.dispatch_chain(t, &ev, pos, text);
1820                    if let Event::Pointer(pe) = &ev {
1821                        self.forward_surface_pointer(t, pe);
1822                    }
1823                }
1824            }
1825        }
1826        if was_last {
1827            self.pointer_up(pos, PointerButton::Left, mods, text);
1828        } else {
1829            self.request_redraw();
1830        }
1831    }
1832
1833    pub fn pinch(&self, pos: Point, delta: f32, mods: Modifiers, text: &mut TextSystem) {
1834        let Some(target) = self.hit_test(pos) else {
1835            return;
1836        };
1837        let ev = Event::Pointer(PointerEvent::Pinch {
1838            delta,
1839            pos,
1840            modifiers: mods,
1841        });
1842        self.dispatch_chain(target, &ev, pos, text);
1843        if let Event::Pointer(pe) = &ev {
1844            self.forward_surface_pointer(target, pe);
1845        }
1846        self.request_redraw();
1847    }
1848
1849    pub fn key(&self, key: Key, mods: Modifiers, text: &mut TextSystem) {
1850        if key == Key::ESCAPE && self.inner.active_drag.borrow().is_some() {
1851            self.cancel_drag(text);
1852            return;
1853        }
1854        let esc = self.inner.overlays.borrow().escape_dismiss_id();
1855        if key == Key::ESCAPE
1856            && let Some(id) = esc
1857        {
1858            self.overlay_hide(id);
1859            return;
1860        }
1861        if key == Key::TAB && !self.focused_accepts_tab() {
1862            self.focus_next(mods.contains(Modifiers::SHIFT));
1863            self.request_redraw();
1864            return;
1865        }
1866        let st = self.inner.interact.get();
1867        let Some(f) = st.focus else { return };
1868        if !self.is_alive(f) {
1869            return;
1870        }
1871        let ev = Event::Key(KeyEvent {
1872            key,
1873            modifiers: mods,
1874        });
1875        self.dispatch_chain(f, &ev, Point::ZERO, text);
1876        if let Event::Key(ke) = &ev {
1877            self.forward_surface_key(f, ke);
1878        }
1879        self.request_redraw();
1880    }
1881
1882    pub fn ime(&self, event: ImeEvent, text: &mut TextSystem) {
1883        let st = self.inner.interact.get();
1884        let Some(f) = st.focus else { return };
1885        if !self.is_alive(f) {
1886            return;
1887        }
1888        let ev = Event::Ime(event);
1889
1890        let cursor = st.cursor;
1891        self.dispatch_chain(f, &ev, cursor, text);
1892        self.request_redraw();
1893    }
1894
1895    pub fn files_hovered(&self, paths: Vec<std::path::PathBuf>, pos: Point, text: &mut TextSystem) {
1896        let Some(target) = self.hit_test(pos) else {
1897            return;
1898        };
1899        let ev = Event::FilesHovered {
1900            paths,
1901            position: pos,
1902        };
1903        self.dispatch_chain(target, &ev, pos, text);
1904        self.request_redraw();
1905    }
1906
1907    pub fn files_dropped(&self, paths: Vec<std::path::PathBuf>, pos: Point, text: &mut TextSystem) {
1908        let Some(target) = self.hit_test(pos) else {
1909            return;
1910        };
1911        let ev = Event::FilesDropped {
1912            paths,
1913            position: pos,
1914        };
1915        self.dispatch_chain(target, &ev, pos, text);
1916        self.request_redraw();
1917    }
1918
1919    pub fn files_cancelled(&self, text: &mut TextSystem) {
1920        let pos = self.inner.interact.get().cursor;
1921        let ev = Event::FilesCancelled;
1922        if let Some(target) = self.hit_test(pos) {
1923            self.dispatch_chain(target, &ev, pos, text);
1924        }
1925        self.request_redraw();
1926    }
1927
1928    pub fn ime_cursor_rect(&self) -> Option<Rect> {
1929        let st = self.inner.interact.get();
1930        let f = st.focus?;
1931        let caret = self.view_rc(f).and_then(|v| v.ime_caret_rect_erased());
1932        caret.or_else(|| self.rect_of(f))
1933    }
1934
1935    pub fn overlay_show(&self, entry: OverlayEntry) -> OverlayId {
1936        let transient = entry.transient;
1937        let view = entry.view.clone_inner();
1938        let value = unwrap_any(view);
1939        let el = self.alloc(value, None, 0, 0);
1940        if let Some(v) = self.view_rc(el) {
1941            self.build_with(el, v);
1942        }
1943        if let Some(v) = self.view_rc(el) {
1944            let type_name = v.type_name().to_string();
1945            if std::panic::catch_unwind(AssertUnwindSafe(|| {
1946                v.on_mount();
1947            }))
1948            .is_err()
1949            {
1950                self.record_error(el, type_name, "on_mount panicked".to_string());
1951            }
1952        }
1953        entry.element.set(Some(el));
1954        let id = self.inner.overlays.borrow_mut().insert(entry);
1955        self.inner
1956            .damage
1957            .borrow_mut()
1958            .mark(Dirty::PAINT | Dirty::OVERLAY);
1959        if !transient {
1960            self.request_redraw();
1961        }
1962        id
1963    }
1964
1965    pub fn overlay_hide(&self, id: OverlayId) -> bool {
1966        let Some(entry) = self.inner.overlays.borrow_mut().remove(id) else {
1967            return false;
1968        };
1969        if let Some(el) = entry.element.get() {
1970            self.unmount_subtree(el);
1971        }
1972        self.request_redraw();
1973        true
1974    }
1975
1976    pub fn overlay_rect(&self, id: OverlayId) -> Option<Rect> {
1977        self.inner.overlays.borrow().get(id).map(|e| e.rect.get())
1978    }
1979
1980    pub fn overlay_exists(&self, id: OverlayId) -> bool {
1981        self.inner.overlays.borrow().get(id).is_some()
1982    }
1983
1984    pub fn overlay_element(&self, id: OverlayId) -> Option<ElementId> {
1985        self.inner
1986            .overlays
1987            .borrow()
1988            .get(id)
1989            .and_then(|e| e.element.get())
1990    }
1991
1992    pub fn accessibility_tree(&self) -> Vec<crate::a11y::AccessibleNode> {
1993        let Some(root) = self.inner.root.get() else {
1994            return Vec::new();
1995        };
1996        self.accessibility_children(root)
1997    }
1998
1999    fn accessibility_children(&self, el: ElementId) -> Vec<crate::a11y::AccessibleNode> {
2000        let mut out = Vec::new();
2001        for child in self.children(el) {
2002            if !self.is_alive(child) {
2003                continue;
2004            }
2005            let kids = self.accessibility_children(child);
2006            match self.view_rc(child).and_then(|v| v.accessibility_erased()) {
2007                Some(a) => out.push(crate::a11y::AccessibleNode {
2008                    element: child,
2009                    role: a.role,
2010                    label: a.label,
2011                    value: a.value,
2012                    caret: a.caret,
2013                    selection: a.selection,
2014                    disabled: a.disabled,
2015                    children: kids,
2016                }),
2017                None => out.extend(kids),
2018            }
2019        }
2020        out
2021    }
2022
2023    pub fn overlay_ids(&self) -> Vec<OverlayId> {
2024        self.inner.overlays.borrow().ordered_ids()
2025    }
2026
2027    pub fn overlay_count(&self) -> usize {
2028        self.inner.overlays.borrow().len()
2029    }
2030
2031    pub fn overlay_set_position(&self, id: OverlayId, x: f32, y: f32) -> bool {
2032        let ok = self.inner.overlays.borrow_mut().set_offset(id, (x, y));
2033        if ok {
2034            self.request_redraw();
2035        }
2036        ok
2037    }
2038
2039    pub fn begin_pending(
2040        &self,
2041        source: ElementId,
2042        payload: DragPayload,
2043        origin: Point,
2044        source_rect: Rect,
2045        options: DragOptions,
2046    ) {
2047        if self.inner.active_drag.borrow().is_some() || !self.is_alive(source) {
2048            return;
2049        }
2050        *self.inner.pending_drag.borrow_mut() = Some(PendingDrag {
2051            source,
2052            origin,
2053            source_rect,
2054            payload,
2055            preview: options.preview,
2056            threshold: options.threshold,
2057        });
2058    }
2059
2060    pub fn current_drag(&self) -> Option<DragSession> {
2061        self.inner
2062            .active_drag
2063            .borrow()
2064            .as_ref()
2065            .map(ActiveDrag::snapshot)
2066    }
2067
2068    pub fn has_pending_drag(&self) -> bool {
2069        self.inner.pending_drag.borrow().is_some()
2070    }
2071
2072    pub fn drag_target_contains(&self, el: ElementId) -> bool {
2073        let Some(target) = self
2074            .inner
2075            .active_drag
2076            .borrow()
2077            .as_ref()
2078            .and_then(|a| a.target)
2079        else {
2080            return false;
2081        };
2082        let mut cur = Some(el);
2083        while let Some(e) = cur {
2084            if e == target {
2085                return true;
2086            }
2087            cur = self.parent_of(e);
2088        }
2089        false
2090    }
2091
2092    pub fn drop_target_at(&self, pos: Point, payload: &DragPayload) -> Option<ElementId> {
2093        let mut cur = self.hit_test(pos);
2094        while let Some(el) = cur {
2095            if self.is_alive(el)
2096                && self
2097                    .view_rc(el)
2098                    .is_some_and(|v| v.accepts_drop_erased(payload))
2099            {
2100                return Some(el);
2101            }
2102            cur = self.parent_of(el);
2103        }
2104        None
2105    }
2106
2107    pub fn cancel_drag(&self, text: &mut TextSystem) {
2108        let (source, current) = {
2109            let guard = self.inner.active_drag.borrow();
2110            let Some(act) = guard.as_ref() else {
2111                drop(guard);
2112                self.inner.pending_drag.borrow_mut().take();
2113                return;
2114            };
2115            (act.source, act.current)
2116        };
2117        if let Some(src) = source.filter(|s| self.is_alive(*s)) {
2118            self.call_drag_end(
2119                src,
2120                DragEnd {
2121                    dropped: false,
2122                    cancelled: true,
2123                },
2124                current,
2125                text,
2126            );
2127        }
2128        let active = self.inner.active_drag.borrow_mut().take();
2129        self.inner.pending_drag.borrow_mut().take();
2130        if let Some(act) = active
2131            && let Some(pv) = act.preview
2132        {
2133            self.overlay_hide(pv);
2134        }
2135        let mut st = self.inner.interact.get();
2136        st.pressed = None;
2137        st.captured = None;
2138        self.inner.interact.set(st);
2139        self.request_redraw();
2140    }
2141
2142    fn poll_pending(&self, current: Point, modifiers: Modifiers, text: &mut TextSystem) {
2143        if self.inner.active_drag.borrow().is_some() {
2144            self.update_active(current, modifiers, text);
2145            return;
2146        }
2147        let pending = self.inner.pending_drag.borrow_mut().take();
2148        let Some(p) = pending else { return };
2149        if !self.is_alive(p.source) {
2150            return;
2151        }
2152        if !exceeded_threshold(p.origin, current, p.threshold) {
2153            *self.inner.pending_drag.borrow_mut() = Some(p);
2154            return;
2155        }
2156        let grab = grab_offset(p.origin, p.source_rect);
2157        let id = DragId(self.inner.next_drag_id.get());
2158        self.inner.next_drag_id.set(id.0 + 1);
2159        let preview = p.preview.map(|view| {
2160            let at = preview_origin(current, grab);
2161            self.overlay_show(
2162                OverlayEntry::new(view)
2163                    .at(at.x, at.y)
2164                    .layer(OverlayLayer::DragPreview)
2165                    .input_mode(InputMode::Passthrough),
2166            )
2167        });
2168        let target = self.drop_target_at(current, &p.payload);
2169        *self.inner.active_drag.borrow_mut() = Some(ActiveDrag {
2170            id,
2171            payload: p.payload,
2172            source: Some(p.source),
2173            origin: p.origin,
2174            grab_offset: grab,
2175            current,
2176            modifiers,
2177            target,
2178            preview,
2179        });
2180
2181        let mut st = self.inner.interact.get();
2182        st.captured = Some(p.source);
2183        self.inner.interact.set(st);
2184        self.call_drag_start(p.source, current, text);
2185        if let Some(t) = target {
2186            self.call_drag_over(t, current, modifiers, text);
2187        }
2188        self.request_redraw();
2189    }
2190
2191    fn update_active(&self, current: Point, modifiers: Modifiers, text: &mut TextSystem) {
2192        let (target, preview_move) = {
2193            let mut guard = self.inner.active_drag.borrow_mut();
2194            let Some(act) = guard.as_mut() else {
2195                return;
2196            };
2197            act.current = current;
2198            act.modifiers = modifiers;
2199            let t = self.drop_target_at(current, &act.payload);
2200            act.target = t;
2201            let mv = act
2202                .preview
2203                .map(|id| (id, preview_origin(current, act.grab_offset)));
2204            (t, mv)
2205        };
2206        if let Some((id, at)) = preview_move {
2207            self.overlay_set_position(id, at.x, at.y);
2208        }
2209        if let Some(t) = target {
2210            self.call_drag_over(t, current, modifiers, text);
2211        }
2212        self.request_redraw();
2213    }
2214
2215    fn finish_drop(&self, current: Point, modifiers: Modifiers, text: &mut TextSystem) -> bool {
2216        let payload = {
2217            let guard = self.inner.active_drag.borrow();
2218            let Some(act) = guard.as_ref() else {
2219                return false;
2220            };
2221            act.payload.clone()
2222        };
2223
2224        let target = self.drop_target_at(current, &payload);
2225        let mut dropped = false;
2226        if let Some(t) = target {
2227            let rect = self.rect_of(t).unwrap_or(Rect::ZERO);
2228            let pos = DropPosition::in_rect(current, rect, modifiers);
2229            dropped = self.call_drop(t, pos, text);
2230        }
2231        let (source, preview) = {
2232            let mut guard = self.inner.active_drag.borrow_mut();
2233            match guard.take() {
2234                Some(act) => (act.source, act.preview),
2235
2236                None => return true,
2237            }
2238        };
2239        self.inner.pending_drag.borrow_mut().take();
2240        if let Some(pv) = preview {
2241            self.overlay_hide(pv);
2242        }
2243        let mut st = self.inner.interact.get();
2244        st.pressed = None;
2245        st.captured = None;
2246        st.hovered = self.hit_test(current);
2247        self.inner.interact.set(st);
2248        if let Some(src) = source.filter(|s| self.is_alive(*s)) {
2249            self.call_drag_end(
2250                src,
2251                DragEnd {
2252                    dropped,
2253                    cancelled: false,
2254                },
2255                current,
2256                text,
2257            );
2258        }
2259        self.request_redraw();
2260        true
2261    }
2262
2263    fn drag_ctx_parts(&self, el: ElementId) -> (VisualState, bool, Rect, Environment) {
2264        let (state, hover_self) = self.view_state(el);
2265        let rect = {
2266            let arena = self.inner.arena.borrow();
2267            arena.get(el).map(|e| e.rect.get()).unwrap_or(Rect::ZERO)
2268        };
2269        let env = self.inner.environment.borrow().clone();
2270        (state, hover_self, rect, env)
2271    }
2272
2273    fn call_drag_start(&self, el: ElementId, pos: Point, text: &mut TextSystem) {
2274        let Some(v) = self.view_rc(el) else { return };
2275        let cells = DispatchCells::new();
2276        let (state, hover_self, rect, env) = self.drag_ctx_parts(el);
2277        let mut ctx = EventCtx {
2278            phase: EventPhase::Target,
2279            element: el,
2280            state,
2281            hover_self,
2282            pos,
2283            rect,
2284            environment: &env,
2285            window: self.inner.window.get(),
2286            stopped: &cells.stopped,
2287            handled: &cells.handled,
2288            capture_req: &cells.capture_req,
2289            release_req: &cells.release_req,
2290            focus_req: &cells.focus_req,
2291            text,
2292            runtime: self,
2293        };
2294        let type_name = v.type_name().to_string();
2295        if std::panic::catch_unwind(AssertUnwindSafe(|| {
2296            v.on_drag_start_erased(&mut ctx);
2297        }))
2298        .is_err()
2299        {
2300            self.record_error(el, type_name, "on_drag_start panicked".to_string());
2301        }
2302    }
2303
2304    fn call_drag_over(
2305        &self,
2306        el: ElementId,
2307        current: Point,
2308        modifiers: Modifiers,
2309        text: &mut TextSystem,
2310    ) {
2311        let Some(v) = self.view_rc(el) else { return };
2312        let cells = DispatchCells::new();
2313        let (state, hover_self, rect, env) = self.drag_ctx_parts(el);
2314        let mut ctx = EventCtx {
2315            phase: EventPhase::Target,
2316            element: el,
2317            state,
2318            hover_self,
2319            pos: current,
2320            rect,
2321            environment: &env,
2322            window: self.inner.window.get(),
2323            stopped: &cells.stopped,
2324            handled: &cells.handled,
2325            capture_req: &cells.capture_req,
2326            release_req: &cells.release_req,
2327            focus_req: &cells.focus_req,
2328            text,
2329            runtime: self,
2330        };
2331        let pos = DropPosition::in_rect(current, rect, modifiers);
2332        let type_name = v.type_name().to_string();
2333        if std::panic::catch_unwind(AssertUnwindSafe(|| {
2334            v.on_drag_over_erased(pos, &mut ctx);
2335        }))
2336        .is_err()
2337        {
2338            self.record_error(el, type_name, "on_drag_over panicked".to_string());
2339        }
2340    }
2341
2342    fn call_drop(&self, el: ElementId, pos: DropPosition, text: &mut TextSystem) -> bool {
2343        let Some(v) = self.view_rc(el) else {
2344            return false;
2345        };
2346        let cells = DispatchCells::new();
2347        let (state, hover_self, rect, env) = self.drag_ctx_parts(el);
2348        let mut ctx = EventCtx {
2349            phase: EventPhase::Target,
2350            element: el,
2351            state,
2352            hover_self,
2353            pos: pos.global,
2354            rect,
2355            environment: &env,
2356            window: self.inner.window.get(),
2357            stopped: &cells.stopped,
2358            handled: &cells.handled,
2359            capture_req: &cells.capture_req,
2360            release_req: &cells.release_req,
2361            focus_req: &cells.focus_req,
2362            text,
2363            runtime: self,
2364        };
2365        let type_name = v.type_name().to_string();
2366        if std::panic::catch_unwind(AssertUnwindSafe(|| {
2367            v.on_drop_erased(pos, &mut ctx);
2368        }))
2369        .is_err()
2370        {
2371            self.record_error(el, type_name, "on_drop panicked".to_string());
2372            return false;
2373        }
2374        cells.handled.get()
2375    }
2376
2377    fn call_drag_end(&self, el: ElementId, end: DragEnd, current: Point, text: &mut TextSystem) {
2378        let Some(v) = self.view_rc(el) else { return };
2379        let cells = DispatchCells::new();
2380        let (state, hover_self, rect, env) = self.drag_ctx_parts(el);
2381        let mut ctx = EventCtx {
2382            phase: EventPhase::Target,
2383            element: el,
2384            state,
2385            hover_self,
2386            pos: current,
2387            rect,
2388            environment: &env,
2389            window: self.inner.window.get(),
2390            stopped: &cells.stopped,
2391            handled: &cells.handled,
2392            capture_req: &cells.capture_req,
2393            release_req: &cells.release_req,
2394            focus_req: &cells.focus_req,
2395            text,
2396            runtime: self,
2397        };
2398        let type_name = v.type_name().to_string();
2399        if std::panic::catch_unwind(AssertUnwindSafe(|| {
2400            v.on_drag_end_erased(end, &mut ctx);
2401        }))
2402        .is_err()
2403        {
2404            self.record_error(el, type_name, "on_drag_end panicked".to_string());
2405        }
2406    }
2407
2408    fn layout_overlays(&self, viewport: Rect, ctx: &mut LayoutCtx) {
2409        for id in self.inner.overlays.borrow().ordered_ids() {
2410            self.layout_one_overlay(id, viewport, ctx);
2411        }
2412    }
2413
2414    fn layout_one_overlay(&self, id: OverlayId, viewport: Rect, ctx: &mut LayoutCtx) {
2415        let (anchor, placement, position, offset, overflow, el) = {
2416            let store = self.inner.overlays.borrow();
2417            let Some(e) = store.get(id) else { return };
2418            let Some(el) = e.element.get() else { return };
2419            (e.anchor, e.placement, e.position, e.offset, e.overflow, el)
2420        };
2421
2422        let anchor_rect = match anchor {
2423            Anchor::View(aid) => match self.rect_of(aid) {
2424                Some(r) => r,
2425
2426                None => {
2427                    self.record_error(
2428                        el,
2429                        "Overlay".to_string(),
2430                        "dead Anchor::View: overlay falls back to (0,0); recreate entry with live ctx.element".to_string(),
2431                    );
2432                    Rect::ZERO
2433                }
2434            },
2435            _ => self.resolve_anchor(anchor, viewport),
2436        };
2437
2438        let measured = self.measure(el, Constraints::UNBOUNDED, ctx);
2439
2440        let pos = compute_position(
2441            position,
2442            placement,
2443            anchor_rect,
2444            measured,
2445            viewport,
2446            offset,
2447            overflow,
2448        );
2449        let rect = Rect::new(pos, measured);
2450
2451        self.arrange(el, rect, ctx);
2452
2453        if let Some(e) = self.inner.overlays.borrow().get(id) {
2454            e.rect.set(rect);
2455        }
2456    }
2457
2458    fn resolve_anchor(&self, anchor: Anchor, viewport: Rect) -> Rect {
2459        match anchor {
2460            Anchor::View(id) => self.rect_of(id).unwrap_or(Rect::ZERO),
2461            Anchor::Rect(r) => r,
2462            Anchor::Point(p) => Rect::new(p, Size::ZERO),
2463            Anchor::Cursor => Rect::new(self.inner.interact.get().cursor, Size::ZERO),
2464            Anchor::Viewport => viewport,
2465        }
2466    }
2467
2468    fn hit_overlays(&self, pos: Point) -> Option<ElementId> {
2469        for id in self.inner.overlays.borrow().ordered_ids().into_iter().rev() {
2470            let (mode, el, rect) = {
2471                let store = self.inner.overlays.borrow();
2472                let Some(e) = store.get(id) else { continue };
2473                let Some(el) = e.element.get() else { continue };
2474                (e.input_mode, el, e.rect.get())
2475            };
2476            match mode {
2477                InputMode::Passthrough => continue,
2478                InputMode::Modal => {
2479                    if rect.contains(pos)
2480                        && let Some(hit) = self.hit_rec(el, pos)
2481                    {
2482                        return Some(hit);
2483                    }
2484                    return Some(el);
2485                }
2486                InputMode::Interactive => {
2487                    if rect.contains(pos)
2488                        && let Some(hit) = self.hit_rec(el, pos)
2489                    {
2490                        return Some(hit);
2491                    }
2492                }
2493            }
2494        }
2495        None
2496    }
2497
2498    fn interaction_target_within(
2499        &self,
2500        subtree: ElementId,
2501        target: Option<ElementId>,
2502    ) -> Option<u64> {
2503        let target = target?;
2504        let mut cur = Some(target);
2505        let mut walked = 0u32;
2506        while let Some(e) = cur {
2507            if e == subtree {
2508                return Some(target.key());
2509            }
2510            cur = self.parent_of(e);
2511            walked += 1;
2512            if walked > 4096 {
2513                break;
2514            }
2515        }
2516        None
2517    }
2518
2519    fn hover_chain(&self) -> Vec<ElementId> {
2520        let Some(hovered) = self.inner.interact.get().hovered else {
2521            return Vec::new();
2522        };
2523        let arena = self.inner.arena.borrow();
2524        let mut chain = Vec::new();
2525        let mut cur = Some(hovered);
2526        let mut walked = 0;
2527        while let Some(e) = cur {
2528            chain.push(e);
2529            cur = arena.get(e).and_then(|el| el.parent);
2530            walked += 1;
2531            if walked > 4096 {
2532                break;
2533            }
2534        }
2535        chain
2536    }
2537
2538    /// Whether the focused element is a text input.
2539    ///
2540    /// Hosts use this to suppress plain-key accelerators while the user is
2541    /// typing. Command shortcuts remain available.
2542    pub fn text_input_focused(&self) -> bool {
2543        let st = self.inner.interact.get();
2544        st.focus
2545            .and_then(|f| self.view_rc(f))
2546            .is_some_and(|v| v.accepts_text_input_erased())
2547    }
2548
2549    pub fn focus_in_subtree(&self, el: ElementId) -> bool {
2550        let st = self.inner.interact.get();
2551        let Some(f) = st.focus else { return false };
2552        if f == el {
2553            return true;
2554        }
2555        let mut cur = self.parent_of(f);
2556        while let Some(e) = cur {
2557            if e == el {
2558                return true;
2559            }
2560            cur = self.parent_of(e);
2561        }
2562        false
2563    }
2564
2565    pub fn parent_of(&self, el: ElementId) -> Option<ElementId> {
2566        self.inner.arena.borrow().get(el)?.parent
2567    }
2568
2569    fn focused_accepts_tab(&self) -> bool {
2570        let st = self.inner.interact.get();
2571        let Some(f) = st.focus else { return false };
2572        if !self.is_alive(f) {
2573            return false;
2574        }
2575        self.view_rc(f).is_some_and(|v| v.accepts_tab_erased())
2576    }
2577
2578    fn nearest_focusable(&self, mut el: ElementId) -> Option<ElementId> {
2579        loop {
2580            if let Some(v) = self.view_rc(el)
2581                && v.focusable_erased()
2582            {
2583                return Some(el);
2584            }
2585            {
2586                let p = self.parent_of(el)?;
2587                el = p
2588            }
2589        }
2590    }
2591
2592    fn nearest_trap_scope(&self, mut el: ElementId) -> Option<ElementId> {
2593        loop {
2594            if let Some(v) = self.view_rc(el)
2595                && v.focus_trap_erased()
2596            {
2597                return Some(el);
2598            }
2599            {
2600                let p = self.parent_of(el)?;
2601                el = p
2602            }
2603        }
2604    }
2605
2606    fn collect_focusables(&self, el: ElementId, out: &mut Vec<ElementId>) {
2607        if let Some(v) = self.view_rc(el)
2608            && v.focusable_erased()
2609        {
2610            out.push(el);
2611        }
2612        for c in self.children(el) {
2613            self.collect_focusables(c, out);
2614        }
2615    }
2616
2617    pub fn focus_next(&self, backward: bool) {
2618        let mut st = self.inner.interact.get();
2619
2620        let mut f = Vec::new();
2621        let scope = st.focus.and_then(|c| self.nearest_trap_scope(c));
2622        match scope {
2623            Some(s) => self.collect_focusables(s, &mut f),
2624            None => {
2625                if let Some(root) = self.inner.root.get() {
2626                    self.collect_focusables(root, &mut f);
2627                }
2628            }
2629        }
2630        if f.is_empty() {
2631            return;
2632        }
2633        let len = f.len();
2634        let next = match st.focus.and_then(|c| f.iter().position(|&e| e == c)) {
2635            Some(i) if backward => (i + len - 1) % len,
2636            Some(i) => (i + 1) % len,
2637            None => 0,
2638        };
2639        st.focus = Some(f[next]);
2640        self.inner.interact.set(st);
2641        self.dismiss_focus_lost(st.focus);
2642    }
2643
2644    pub fn set_focus(&self, el: Option<ElementId>) {
2645        let mut st = self.inner.interact.get();
2646        let old = st.focus;
2647        st.focus = el;
2648        self.inner.interact.set(st);
2649        for previous in [old, el].into_iter().flatten() {
2650            self.mark_damage(previous, Dirty::PAINT);
2651        }
2652        self.request_redraw();
2653        self.dismiss_focus_lost(el);
2654    }
2655
2656    fn target_in_overlay(&self, target: ElementId, overlay_el: Option<ElementId>) -> bool {
2657        let Some(root) = overlay_el else { return false };
2658        let mut cur = Some(target);
2659        while let Some(el) = cur {
2660            if el == root {
2661                return true;
2662            }
2663            cur = self.parent_of(el);
2664        }
2665        false
2666    }
2667
2668    fn dismiss_focus_lost(&self, target: Option<ElementId>) {
2669        let ids = self.inner.overlays.borrow().focus_lost_ids(target, |el| {
2670            target.is_some_and(|t| self.target_in_overlay(t, el))
2671        });
2672        for id in ids {
2673            self.overlay_hide(id);
2674        }
2675    }
2676
2677    fn auto_capture(&self, target: ElementId) -> Option<ElementId> {
2678        let mut cur = Some(target);
2679        while let Some(el) = cur {
2680            if let Some(v) = self.view_rc(el)
2681                && v.captures_pointer_ctx_erased(self, el)
2682            {
2683                return Some(el);
2684            }
2685            cur = self.parent_of(el);
2686        }
2687        None
2688    }
2689
2690    fn view_state(&self, el: ElementId) -> (VisualState, bool) {
2691        let st = self.inner.interact.get();
2692        let mut vs = VisualState::default();
2693        vs.hovered = self.hovered_within(el, st.hovered);
2694        vs.pressed = st.pressed == Some(el);
2695        vs.focused = st.focus == Some(el);
2696        (vs, st.hovered == Some(el))
2697    }
2698
2699    /// True when the pointer is over `el` or over one of its descendants.
2700    ///
2701    /// A container must keep its hover state while the pointer is on a child,
2702    /// including children that live in an overlay layer. The hover chain for
2703    /// overlay elements walks up to the overlay root just like the main tree,
2704    /// so checking ancestors fixes hover inside menus and popovers.
2705    fn hovered_within(&self, el: ElementId, hovered: Option<ElementId>) -> bool {
2706        let Some(mut cur) = hovered else {
2707            return false;
2708        };
2709        let mut walked = 0u32;
2710        loop {
2711            if cur == el {
2712                return true;
2713            }
2714            let Some(parent) = self.parent_of(cur) else {
2715                return false;
2716            };
2717            cur = parent;
2718            walked += 1;
2719            if walked > 4096 {
2720                return false;
2721            }
2722        }
2723    }
2724
2725    pub fn visual_state(&self, el: ElementId) -> VisualState {
2726        self.view_state(el).0
2727    }
2728
2729    fn call_on_event(
2730        &self,
2731        el: ElementId,
2732        event: &Event,
2733        phase: EventPhase,
2734        pos: Point,
2735        cells: &DispatchCells,
2736        text: &mut TextSystem,
2737    ) {
2738        let Some(v) = self.view_rc(el) else { return };
2739        let (state, hover_self) = self.view_state(el);
2740        let rect = {
2741            let arena = self.inner.arena.borrow();
2742            arena.get(el).map(|e| e.rect.get()).unwrap_or(Rect::ZERO)
2743        };
2744
2745        let env = self.inner.environment.borrow().clone();
2746        let mut ctx = EventCtx {
2747            phase,
2748            element: el,
2749            state,
2750            hover_self,
2751            pos,
2752            rect,
2753            environment: &env,
2754            window: self.inner.window.get(),
2755            stopped: &cells.stopped,
2756            handled: &cells.handled,
2757            capture_req: &cells.capture_req,
2758            release_req: &cells.release_req,
2759            focus_req: &cells.focus_req,
2760            text,
2761            runtime: self,
2762        };
2763
2764        let type_name = v.type_name().to_string();
2765        if std::panic::catch_unwind(AssertUnwindSafe(|| {
2766            v.on_event_erased(event, &mut ctx);
2767        }))
2768        .is_err()
2769        {
2770            let msg = format!("on_event panicked ({phase:?})");
2771            self.record_error(el, type_name, msg);
2772        }
2773    }
2774
2775    fn dispatch_chain(&self, target: ElementId, event: &Event, pos: Point, text: &mut TextSystem) {
2776        let mut path = vec![target];
2777        while let Some(last) = path.last() {
2778            let Some(p) = self.parent_of(*last) else {
2779                break;
2780            };
2781            path.push(p);
2782        }
2783        path.reverse();
2784        let n = path.len();
2785        let cells = DispatchCells::new();
2786
2787        for &el in &path[..n - 1] {
2788            if cells.stopped.get() || cells.handled.get() {
2789                self.apply_dispatch_requests(target, &cells);
2790                return;
2791            }
2792            self.call_on_event(el, event, EventPhase::Capture, pos, &cells, text);
2793        }
2794        if !(cells.stopped.get() || cells.handled.get()) {
2795            self.call_on_event(target, event, EventPhase::Target, pos, &cells, text);
2796        }
2797        for &el in path[..n - 1].iter().rev() {
2798            if cells.stopped.get() || cells.handled.get() {
2799                break;
2800            }
2801            self.call_on_event(el, event, EventPhase::Bubble, pos, &cells, text);
2802        }
2803        self.apply_dispatch_requests(target, &cells);
2804    }
2805
2806    fn dispatch_single(
2807        &self,
2808        el: ElementId,
2809        event: &Event,
2810        pos: Point,
2811        text: &mut TextSystem,
2812    ) -> bool {
2813        let cells = DispatchCells::new();
2814        self.call_on_event(el, event, EventPhase::Target, pos, &cells, text);
2815        self.apply_dispatch_requests(el, &cells);
2816        cells.handled.get() || cells.stopped.get()
2817    }
2818
2819    fn apply_dispatch_requests(&self, el: ElementId, cells: &DispatchCells) {
2820        let mut st = self.inner.interact.get();
2821        if cells.release_req.get() {
2822            if st.captured == Some(el) {
2823                st.captured = None;
2824            }
2825        } else if let Some(req) = cells.capture_req.get() {
2826            st.captured = Some(req);
2827        }
2828        if cells.focus_req.get() {
2829            st.focus = Some(el);
2830        }
2831        self.inner.interact.set(st);
2832    }
2833
2834    pub fn pointer_cancel(&self, text: &mut TextSystem) {
2835        let captured = self.inner.interact.get().captured;
2836        if let Some(c) = captured {
2837            let pos = self.inner.interact.get().cursor;
2838            let ev = Event::Pointer(PointerEvent::Cancel);
2839            let _ = self.dispatch_single(c, &ev, pos, text);
2840        }
2841        self.inner.pending_drag.borrow_mut().take();
2842        if self.inner.active_drag.borrow().is_some() {
2843            self.cancel_drag(text);
2844            return;
2845        }
2846        let mut st = self.inner.interact.get();
2847        st.pressed = None;
2848        st.captured = None;
2849        st.hovered = None;
2850        self.inner.interact.set(st);
2851        self.request_redraw();
2852    }
2853
2854    pub(crate) fn measure_node(
2855        &self,
2856        el: ElementId,
2857        constraints: Constraints,
2858        ctx: &mut LayoutCtx,
2859    ) -> Size {
2860        self.measure(el, constraints, ctx)
2861    }
2862
2863    pub(crate) fn arrange_node(&self, el: ElementId, rect: Rect, ctx: &mut LayoutCtx) {
2864        self.arrange(el, rect, ctx)
2865    }
2866
2867    pub(crate) fn layout_epoch(&self) -> u64 {
2868        self.inner.layout_epoch.get()
2869    }
2870
2871    pub(crate) fn translate_subtree(&self, root: ElementId, delta: Vec2) {
2872        if delta == Vec2::ZERO {
2873            return;
2874        }
2875        let mut stack = vec![root];
2876        while let Some(id) = stack.pop() {
2877            let children = {
2878                let mut arena = self.inner.arena.borrow_mut();
2879                let Some(e) = arena.get_mut(id) else {
2880                    continue;
2881                };
2882                e.rect.set(e.rect.get().translate(delta));
2883                e.children.clone()
2884            };
2885            stack.extend(children);
2886        }
2887    }
2888
2889    pub(crate) fn child_layout_style(&self, el: ElementId) -> LayoutStyle {
2890        let Some(view) = self.view_rc(el) else {
2891            return LayoutStyle::default();
2892        };
2893        let mut style = view.layout_style_ctx_erased(self, el);
2894        if view.is_leaf() || !(style.width.is_fit() || style.height.is_fit()) {
2895            return style;
2896        }
2897        let children = self.children(el);
2898        if children.len() != 1 {
2899            return style;
2900        }
2901        let child_style = self
2902            .view_rc(children[0])
2903            .map(|v| v.layout_style_ctx_erased(self, children[0]))
2904            .unwrap_or_default();
2905        if style.width.is_fit() && child_style.width.is_fill() {
2906            style.width = child_style.width;
2907        }
2908        if style.height.is_fit() && child_style.height.is_fill() {
2909            style.height = child_style.height;
2910        }
2911        style
2912    }
2913
2914    pub fn check_root_fill(&self, root: ElementId, bounds: Rect, measured: Size) -> bool {
2915        let style = self.child_layout_style(root);
2916        let fill_w = style.width.is_fill();
2917        let fill_h = style.height.is_fill();
2918        if fill_w && fill_h {
2919            return true;
2920        }
2921        let gap_w = bounds.size.width - measured.width;
2922        let gap_h = bounds.size.height - measured.height;
2923
2924        if gap_w <= 1.0 && gap_h <= 1.0 {
2925            return true;
2926        }
2927        let type_name = self.type_name_of(root).unwrap_or_else(|| "?".into());
2928        log::warn!(
2929            "sequoia-view: root `{type_name}` has no fill_size (width_fill={fill_w}, height_fill={fill_h}); \
2930             measured {measured:?} with window {:?} — unpainted background. Add `.fill_size()` to the root.",
2931            bounds.size
2932        );
2933        false
2934    }
2935
2936    fn warn_if_root_not_fill(&self, root: ElementId, bounds: Rect, measured: Size) {
2937        let _ = self.check_root_fill(root, bounds, measured);
2938    }
2939
2940    fn measure(&self, el: ElementId, constraints: Constraints, ctx: &mut LayoutCtx) -> Size {
2941        match std::panic::catch_unwind(AssertUnwindSafe(|| {
2942            self.measure_inner(el, constraints, ctx)
2943        })) {
2944            Ok(s) => s,
2945            Err(_) => {
2946                let type_name = self.type_name_of(el).unwrap_or_else(|| "?".into());
2947                self.record_error(el, type_name, "measure panicked".to_string());
2948                Size::ZERO
2949            }
2950        }
2951    }
2952
2953    fn baseline_offset(&self, el: ElementId, width: f32, ctx: &mut LayoutCtx) -> Option<f32> {
2954        self.view_rc(el)
2955            .and_then(|v| v.baseline_offset_erased(ctx, width))
2956    }
2957
2958    fn measure_inner(&self, el: ElementId, constraints: Constraints, ctx: &mut LayoutCtx) -> Size {
2959        {
2960            let arena = self.inner.arena.borrow();
2961            if arena.get(el).map(|e| e.dirty.is_empty()).unwrap_or(false) {
2962                drop(arena);
2963                let measures = self.inner.measures.borrow();
2964                if let Some(e) = measures.get(&el)
2965                    && e.constraints == constraints
2966                {
2967                    return e.size;
2968                }
2969            }
2970        }
2971        let Some(v) = self.view_rc(el) else {
2972            return Size::ZERO;
2973        };
2974        let children = self.children(el);
2975        let style = self.child_layout_style(el);
2976        let container = v.container();
2977        let grid = v.grid();
2978
2979        let dock = if grid.is_some() { None } else { v.dock() };
2980
2981        let wrap = if grid.is_some() || dock.is_some() {
2982            None
2983        } else {
2984            v.wrap()
2985        };
2986
2987        let masonry = if grid.is_some() || dock.is_some() || wrap.is_some() {
2988            None
2989        } else {
2990            v.masonry()
2991        };
2992
2993        let flex = v.flex_weights().unwrap_or_default();
2994        let pre_placements = v.grid_placements().unwrap_or_default();
2995        let children_len = children.len();
2996
2997        {
2998            let measures = self.inner.measures.borrow();
2999            if let Some(e) = measures.get(&el)
3000                && e.constraints == constraints
3001                && e.style == style
3002                && e.container == container
3003                && e.grid == grid
3004                && e.placements == pre_placements
3005                && e.dock == dock
3006                && e.wrap == wrap
3007                && e.masonry == masonry
3008                && e.children_len == children_len
3009            {
3010                return e.size;
3011            }
3012        }
3013
3014        ctx.set_available_width(constraints.max.width);
3015        let intrinsic = v.intrinsic_size(ctx);
3016        let avail = constraints.max;
3017        let mut w = match style.width {
3018            Length::Px(p) => p,
3019            Length::Percent(p) => {
3020                if avail.width.is_finite() && p.is_finite() {
3021                    (avail.width * p / 100.0).max(0.0)
3022                } else {
3023                    intrinsic.map(|i| i.width).unwrap_or(0.0)
3024                }
3025            }
3026            Length::Fill(_) => {
3027                if avail.width.is_finite() {
3028                    avail.width
3029                } else {
3030                    intrinsic.map(|i| i.width).unwrap_or(0.0)
3031                }
3032            }
3033            Length::Fit => intrinsic.map(|i| i.width).unwrap_or(0.0),
3034        };
3035        let mut h = match style.height {
3036            Length::Px(p) => p,
3037            Length::Percent(p) => {
3038                if avail.height.is_finite() && p.is_finite() {
3039                    (avail.height * p / 100.0).max(0.0)
3040                } else {
3041                    intrinsic.map(|i| i.height).unwrap_or(0.0)
3042                }
3043            }
3044            Length::Fill(_) => {
3045                if avail.height.is_finite() {
3046                    avail.height
3047                } else {
3048                    intrinsic.map(|i| i.height).unwrap_or(0.0)
3049                }
3050            }
3051            Length::Fit => intrinsic.map(|i| i.height).unwrap_or(0.0),
3052        };
3053
3054        let content_width = matches!(style.width, Length::Fit)
3055            || (style.width.is_fill() && !avail.width.is_finite());
3056        let content_height = matches!(style.height, Length::Fit)
3057            || (style.height.is_fill() && !avail.height.is_finite());
3058
3059        let apply_aspect = |mut w: f32, mut h: f32| -> (f32, f32) {
3060            if let Some(r) = style.aspect_ratio
3061                && r.is_finite()
3062                && r > 0.0
3063            {
3064                match (content_width, content_height) {
3065                    (true, false) => w = (h * r).max(0.0),
3066                    (false, true) => h = (w / r).max(0.0),
3067                    (true, true) => {
3068                        if w <= 0.0 && h > 0.0 {
3069                            w = (h * r).max(0.0);
3070                        } else if h <= 0.0 && w > 0.0 {
3071                            h = (w / r).max(0.0);
3072                        }
3073                    }
3074                    (false, false) => {}
3075                }
3076            }
3077            (w, h)
3078        };
3079
3080        let eff_container: Option<ContainerStyle> =
3081            if grid.is_some() || dock.is_some() || wrap.is_some() || masonry.is_some() {
3082                None
3083            } else {
3084                container.or_else(|| {
3085                    if children.is_empty()
3086                        || intrinsic.is_some()
3087                        || (!content_width && !content_height)
3088                    {
3089                        None
3090                    } else {
3091                        Some(ContainerStyle::new(Axis::Vertical, 0.0))
3092                    }
3093                })
3094            };
3095
3096        if let Some(cstyle) = eff_container
3097            && !children.is_empty()
3098            && !(intrinsic.is_some() && !content_width && !content_height)
3099            && !(v.contain_size_erased() && !content_width && !content_height)
3100        {
3101            let pad = style.padding;
3102            let inner_avail = Size::new(
3103                ((if content_width { avail.width } else { w }) - pad.horizontal_sum()).max(0.0),
3104                ((if content_height { avail.height } else { h }) - pad.vertical_sum()).max(0.0),
3105            );
3106            let (total_main, cross_max) = self.measure_children(
3107                &children,
3108                &cstyle,
3109                inner_avail,
3110                &flex,
3111                ctx,
3112                match cstyle.axis {
3113                    Axis::Horizontal => content_height,
3114                    Axis::Vertical => content_width,
3115                },
3116            );
3117            let (mw, mh) = match cstyle.axis {
3118                Axis::Horizontal => (total_main, cross_max),
3119                Axis::Vertical => (cross_max, total_main),
3120            };
3121            if content_width && intrinsic.is_none() {
3122                w = mw + pad.horizontal_sum();
3123            }
3124            if content_height && intrinsic.is_none() {
3125                h = mh + pad.vertical_sum();
3126            }
3127        }
3128
3129        let placements = v.grid_placements().unwrap_or_default();
3130        if let Some(ref gstyle) = grid
3131            && !children.is_empty()
3132            && !(intrinsic.is_some() && !content_width && !content_height)
3133            && !(v.contain_size_erased() && !content_width && !content_height)
3134        {
3135            let pad = style.padding;
3136            let inner_avail = Size::new(
3137                ((if content_width { avail.width } else { w }) - pad.horizontal_sum()).max(0.0),
3138                ((if content_height { avail.height } else { h }) - pad.vertical_sum()).max(0.0),
3139            );
3140            let (gw, gh) = self.measure_grid(&children, gstyle, &placements, inner_avail, ctx);
3141            if content_width && intrinsic.is_none() {
3142                w = gw + pad.horizontal_sum();
3143            }
3144            if content_height && intrinsic.is_none() {
3145                h = gh + pad.vertical_sum();
3146            }
3147        }
3148
3149        if let Some(ref dstyle) = dock
3150            && !children.is_empty()
3151            && !(intrinsic.is_some() && !content_width && !content_height)
3152            && !(v.contain_size_erased() && !content_width && !content_height)
3153        {
3154            let pad = style.padding;
3155            let inner_avail = Size::new(
3156                ((if content_width { avail.width } else { w }) - pad.horizontal_sum()).max(0.0),
3157                ((if content_height { avail.height } else { h }) - pad.vertical_sum()).max(0.0),
3158            );
3159            let (dw, dh) = self.measure_dock(&children, dstyle, inner_avail, ctx);
3160            if content_width && intrinsic.is_none() {
3161                w = dw + pad.horizontal_sum();
3162            }
3163            if content_height && intrinsic.is_none() {
3164                h = dh + pad.vertical_sum();
3165            }
3166        }
3167
3168        if let Some(wstyle) = wrap
3169            && !children.is_empty()
3170            && !(intrinsic.is_some() && !content_width && !content_height)
3171            && !(v.contain_size_erased() && !content_width && !content_height)
3172        {
3173            let pad = style.padding;
3174            let inner_avail = Size::new(
3175                ((if content_width { avail.width } else { w }) - pad.horizontal_sum()).max(0.0),
3176                ((if content_height { avail.height } else { h }) - pad.vertical_sum()).max(0.0),
3177            );
3178            let (ww, wh) = self.measure_wrap(&children, &wstyle, inner_avail, ctx);
3179            if content_width && intrinsic.is_none() {
3180                w = ww + pad.horizontal_sum();
3181            }
3182            if content_height && intrinsic.is_none() {
3183                h = wh + pad.vertical_sum();
3184            }
3185        }
3186
3187        if let Some(mstyle) = masonry
3188            && !children.is_empty()
3189            && !(intrinsic.is_some() && !content_width && !content_height)
3190            && !(v.contain_size_erased() && !content_width && !content_height)
3191        {
3192            let pad = style.padding;
3193            let inner_avail = Size::new(
3194                ((if content_width { avail.width } else { w }) - pad.horizontal_sum()).max(0.0),
3195                ((if content_height { avail.height } else { h }) - pad.vertical_sum()).max(0.0),
3196            );
3197            let (mw, mh) =
3198                self.measure_masonry(&children, &mstyle, inner_avail, content_width, ctx);
3199            if content_width && intrinsic.is_none() {
3200                w = mw + pad.horizontal_sum();
3201            }
3202            if content_height && intrinsic.is_none() {
3203                h = mh + pad.vertical_sum();
3204            }
3205        }
3206
3207        let (w, h) = apply_aspect(w, h);
3208        let size = clamp_to(style.clamp_size(Size::new(w, h)), constraints);
3209        let might_collapse = size.width <= 0.5 || size.height <= 0.5;
3210        if might_collapse
3211            && !v.is_leaf()
3212            && !children.is_empty()
3213            && (style.width.is_fit() || style.height.is_fit())
3214        {
3215            let mut fill_w = false;
3216            let mut fill_h = false;
3217            for child in &children {
3218                let child_style = self.child_layout_style(*child);
3219                fill_w |= child_style.width.is_fill();
3220                fill_h |= child_style.height.is_fill();
3221            }
3222            let collapsed_w =
3223                style.width.is_fit() && fill_w && size.width <= 0.5 && constraints.max.width > 1.0;
3224            let collapsed_h = style.height.is_fit()
3225                && fill_h
3226                && size.height <= 0.5
3227                && constraints.max.height > 1.0;
3228            if collapsed_w || collapsed_h {
3229                log::warn!(
3230                    "sequoia-view: composite `{}` collapsed to {size:?} while a body child \
3231                     requests fill; give the composite an explicit size or a `layout_style` \
3232                     using `Length::Fill(1.0)`",
3233                    v.type_name()
3234                );
3235            }
3236        }
3237        self.inner.measures.borrow_mut().insert(
3238            el,
3239            MeasureEntry {
3240                constraints,
3241                style,
3242                container,
3243                grid,
3244                placements,
3245                dock,
3246                wrap,
3247                masonry,
3248                children_len: children.len(),
3249                size,
3250            },
3251        );
3252        size
3253    }
3254
3255    fn measure_children(
3256        &self,
3257        children: &[ElementId],
3258        cstyle: &ContainerStyle,
3259        inner_avail: Size,
3260        flex: &[f32],
3261        ctx: &mut LayoutCtx,
3262        need_cross: bool,
3263    ) -> (f32, f32) {
3264        let flow: Vec<(usize, ElementId)> = children
3265            .iter()
3266            .copied()
3267            .enumerate()
3268            .filter(|(_, c)| !self.child_layout_style(*c).is_absolute())
3269            .collect();
3270        if flow.is_empty() {
3271            return (0.0, 0.0);
3272        }
3273
3274        if cstyle.wrap {
3275            return self.measure_children_wrapped(
3276                &flow,
3277                cstyle,
3278                inner_avail,
3279                flex,
3280                ctx,
3281                need_cross,
3282            );
3283        }
3284        let (avail_main, avail_cross) = match cstyle.axis {
3285            Axis::Horizontal => (inner_avail.width, inner_avail.height),
3286            Axis::Vertical => (inner_avail.height, inner_avail.width),
3287        };
3288
3289        let mut bases = Vec::with_capacity(flow.len());
3290        let mut crosses = if need_cross {
3291            Vec::with_capacity(flow.len())
3292        } else {
3293            Vec::new()
3294        };
3295        let mut items = Vec::with_capacity(flow.len());
3296        let mut child_styles = Vec::with_capacity(flow.len());
3297
3298        for &(orig_i, child) in &flow {
3299            let extra = flex.get(orig_i).copied().unwrap_or(0.0);
3300            let (base, grow, shrink, cross, mn, mx) =
3301                self.child_layout_info_full(child, cstyle, avail_main, avail_cross, extra, ctx);
3302            bases.push(base);
3303            if need_cross {
3304                crosses.push(cross);
3305            }
3306            items.push(FlexItem::new(base, grow, shrink, mn, mx));
3307            child_styles.push(self.child_layout_style(child));
3308        }
3309        let gaps = Self::indents_main_total(&child_styles, cstyle.axis, cstyle.gap);
3310
3311        {
3312            let mut sizes = bases.clone();
3313            distribute_flex(&items, &mut sizes, gaps, avail_main);
3314            bases = sizes;
3315        }
3316
3317        if need_cross && avail_main.is_finite() {
3318            for (k, &(_, child)) in flow.iter().enumerate() {
3319                if self.subtree_has_wrap_axis(child, cstyle.axis) {
3320                    crosses[k] =
3321                        self.constrained_cross(child, cstyle.axis, bases[k], avail_cross, ctx);
3322                }
3323            }
3324        }
3325
3326        let total_main = bases.iter().sum::<f32>() + gaps;
3327        let cross_max = if need_cross {
3328            crosses.iter().cloned().fold(0.0_f32, f32::max)
3329        } else {
3330            0.0
3331        };
3332        (total_main, cross_max)
3333    }
3334
3335    fn measure_children_wrapped(
3336        &self,
3337        flow: &[(usize, ElementId)],
3338        cstyle: &ContainerStyle,
3339        inner_avail: Size,
3340        flex: &[f32],
3341        ctx: &mut LayoutCtx,
3342        need_cross: bool,
3343    ) -> (f32, f32) {
3344        let (avail_main, avail_cross) = match cstyle.axis {
3345            Axis::Horizontal => (inner_avail.width, inner_avail.height),
3346            Axis::Vertical => (inner_avail.height, inner_avail.width),
3347        };
3348
3349        let mut mains = Vec::with_capacity(flow.len());
3350        let mut crosses = Vec::with_capacity(flow.len());
3351        let mut grows = Vec::with_capacity(flow.len());
3352        let mut shrinks = Vec::with_capacity(flow.len());
3353        let mut mins = Vec::with_capacity(flow.len());
3354        let mut maxs = Vec::with_capacity(flow.len());
3355        for &(orig_i, child) in flow {
3356            let extra = flex.get(orig_i).copied().unwrap_or(0.0);
3357            let (base, grow, shrink, cross, mn, mx) =
3358                self.child_layout_info_full(child, cstyle, avail_main, avail_cross, extra, ctx);
3359            mains.push(base);
3360            crosses.push(cross);
3361            grows.push(grow);
3362            shrinks.push(shrink);
3363            mins.push(mn);
3364            maxs.push(mx);
3365        }
3366        let lines = wrap_lines(&mains, avail_main, cstyle.gap);
3367        if lines.is_empty() {
3368            return (0.0, 0.0);
3369        }
3370        let mut main_max = 0.0f32;
3371        let mut cross_total = 0.0f32;
3372        for (li, &(s, e)) in lines.iter().enumerate() {
3373            let mut line_sizes = mains[s..e].to_vec();
3374            let items: Vec<FlexItem> = (s..e)
3375                .map(|i| FlexItem::new(mains[i], grows[i], shrinks[i], mins[i], maxs[i]))
3376                .collect();
3377            let line_gaps = cstyle.main_gaps_total(e - s);
3378            distribute_flex(&items, &mut line_sizes, line_gaps, avail_main);
3379            let used: f32 = line_sizes.iter().sum::<f32>() + line_gaps;
3380            main_max = main_max.max(used);
3381            if need_cross {
3382                let line_cross = crosses[s..e].iter().cloned().fold(0.0f32, f32::max);
3383                if li > 0 {
3384                    cross_total += cstyle.cross_gap;
3385                }
3386                cross_total += line_cross;
3387            }
3388        }
3389
3390        (main_max.max(0.0), cross_total.max(0.0))
3391    }
3392
3393    fn subtree_has_wrap_axis(&self, root: ElementId, axis: Axis) -> bool {
3394        let axis_u8 = match axis {
3395            Axis::Horizontal => 0,
3396            Axis::Vertical => 1,
3397        };
3398        let key = (root.index, root.generation, axis_u8);
3399        if let Some(hit) = self.inner.wrap_cache.borrow().get(&key).copied() {
3400            return hit;
3401        }
3402        let mut stack = vec![root];
3403        let mut found = false;
3404        while let Some(id) = stack.pop() {
3405            if let Some(v) = self.view_rc(id) {
3406                let grid = v.grid();
3407                let dock = if grid.is_some() { None } else { v.dock() };
3408                let wrap = if grid.is_some() || dock.is_some() {
3409                    None
3410                } else {
3411                    v.wrap()
3412                };
3413                if let Some(w) = wrap
3414                    && w.axis == axis
3415                {
3416                    found = true;
3417                    break;
3418                }
3419                if axis == Axis::Horizontal
3420                    && wrap.is_none()
3421                    && dock.is_none()
3422                    && grid.is_none()
3423                    && v.masonry().is_some()
3424                {
3425                    found = true;
3426                    break;
3427                }
3428            }
3429            {
3430                let arena = self.inner.arena.borrow();
3431                if let Some(e) = arena.get(id) {
3432                    stack.extend(e.children.iter().copied());
3433                }
3434            }
3435        }
3436        self.inner.wrap_cache.borrow_mut().insert(key, found);
3437        found
3438    }
3439
3440    fn subtree_redistributes(&self, root: ElementId) -> bool {
3441        let key = (root.index, root.generation, 2u8);
3442        if let Some(hit) = self.inner.wrap_cache.borrow().get(&key).copied() {
3443            return hit;
3444        }
3445        let mut found = false;
3446        let mut stack = vec![root];
3447        while let Some(id) = stack.pop() {
3448            let Some(v) = self.view_rc(id) else {
3449                continue;
3450            };
3451            if v.flex_weights().is_some_and(|w| w.iter().any(|f| *f > 0.0)) {
3452                found = true;
3453                break;
3454            }
3455            let cs = self.child_layout_style(id);
3456            if cs.flex_weight() > 0.0
3457                || matches!(cs.width, Length::Fill(_))
3458                || matches!(cs.height, Length::Fill(_))
3459            {
3460                found = true;
3461                break;
3462            }
3463            {
3464                let arena = self.inner.arena.borrow();
3465                if let Some(e) = arena.get(id) {
3466                    stack.extend(e.children.iter().copied());
3467                }
3468            }
3469        }
3470        self.inner.wrap_cache.borrow_mut().insert(key, found);
3471        found
3472    }
3473
3474    fn constrained_cross(
3475        &self,
3476        child: ElementId,
3477        container_axis: Axis,
3478        allocated_main: f32,
3479        avail_cross: f32,
3480        ctx: &mut LayoutCtx,
3481    ) -> f32 {
3482        if self.view_rc(child).is_none() {
3483            return 0.0;
3484        }
3485        let cs = self.child_layout_style(child);
3486
3487        let margin_main = cs.margin_main(container_axis);
3488        let allocated = (allocated_main - margin_main).max(0.0);
3489        let max = match container_axis {
3490            Axis::Horizontal => Size::new(allocated, avail_cross),
3491            Axis::Vertical => Size::new(avail_cross, allocated),
3492        };
3493        let m = self.measure(
3494            child,
3495            Constraints {
3496                min: Size::ZERO,
3497                max,
3498            },
3499            ctx,
3500        );
3501        let cross_margin = cs.margin_cross(container_axis);
3502        match container_axis {
3503            Axis::Horizontal => {
3504                cs.clamp_height(resolve_cross(cs.height, m.height, avail_cross))
3505                    .max(0.0)
3506                    + cross_margin
3507            }
3508            Axis::Vertical => {
3509                cs.clamp_width(resolve_cross(cs.width, m.width, avail_cross))
3510                    .max(0.0)
3511                    + cross_margin
3512            }
3513        }
3514    }
3515
3516    fn child_limits_full(&self, child: ElementId, axis: Axis) -> (f32, f32, f32, f32, f32) {
3517        match self.view_rc(child) {
3518            Some(_v) => {
3519                let cs = self.child_layout_style(child);
3520                let margin_main = cs.margin_main(axis);
3521                let margin_cross = cs.margin_cross(axis);
3522                let px_floor_main = match axis {
3523                    Axis::Horizontal => match cs.width {
3524                        Length::Px(w) => w.max(0.0),
3525                        _ => 0.0,
3526                    },
3527                    Axis::Vertical => match cs.height {
3528                        Length::Px(h) => h.max(0.0),
3529                        _ => 0.0,
3530                    },
3531                };
3532                let px_floor_cross = match axis {
3533                    Axis::Horizontal => match cs.height {
3534                        Length::Px(h) => h.max(0.0),
3535                        _ => 0.0,
3536                    },
3537                    Axis::Vertical => match cs.width {
3538                        Length::Px(w) => w.max(0.0),
3539                        _ => 0.0,
3540                    },
3541                };
3542                let (min_c, max_c) = match axis {
3543                    Axis::Horizontal => (cs.min_height, cs.max_height),
3544                    Axis::Vertical => (cs.min_width, cs.max_width),
3545                };
3546                let min_c = {
3547                    let m = if min_c.is_finite() {
3548                        min_c.max(0.0)
3549                    } else {
3550                        0.0
3551                    };
3552                    m.max(px_floor_cross)
3553                };
3554                let max_c = if max_c.is_nan() {
3555                    f32::INFINITY
3556                } else if max_c.is_infinite() {
3557                    if max_c > 0.0 { f32::INFINITY } else { 0.0 }
3558                } else {
3559                    max_c.max(0.0)
3560                };
3561                (
3562                    cs.min_main(axis).max(px_floor_main) + margin_main,
3563                    cs.max_main(axis) + margin_main,
3564                    min_c + margin_cross,
3565                    max_c + margin_cross,
3566                    cs.flex_shrink_weight(),
3567                )
3568            }
3569            None => (0.0, f32::INFINITY, 0.0, f32::INFINITY, 1.0),
3570        }
3571    }
3572
3573    fn child_layout_info_full(
3574        &self,
3575        child: ElementId,
3576        cstyle: &ContainerStyle,
3577        avail_main: f32,
3578        avail_cross: f32,
3579        flex_extra: f32,
3580        ctx: &mut LayoutCtx,
3581    ) -> (f32, f32, f32, f32, f32, f32) {
3582        if self.view_rc(child).is_none() {
3583            return (0.0, 0.0, 1.0, 0.0, 0.0, f32::INFINITY);
3584        }
3585        let cs = self.child_layout_style(child);
3586        let (main_len, cross_len) = match cstyle.axis {
3587            Axis::Horizontal => (cs.width, cs.height),
3588            Axis::Vertical => (cs.height, cs.width),
3589        };
3590
3591        let (child_max_w, child_max_h) = match cstyle.axis {
3592            Axis::Horizontal => (f32::INFINITY, avail_cross),
3593            Axis::Vertical => (avail_cross, f32::INFINITY),
3594        };
3595        let m = self.measure(
3596            child,
3597            Constraints {
3598                min: Size::ZERO,
3599                max: Size::new(child_max_w, child_max_h),
3600            },
3601            ctx,
3602        );
3603        let (main_intrinsic, cross_intrinsic) = match cstyle.axis {
3604            Axis::Horizontal => (m.width, m.height),
3605            Axis::Vertical => (m.height, m.width),
3606        };
3607
3608        let (mut base, fill_grow) = resolve_main(main_len, main_intrinsic, avail_main);
3609
3610        let (basis_base, basis_grow) =
3611            resolve_flex_basis(cs.flex_basis, (base, fill_grow), main_intrinsic, avail_main);
3612        base = basis_base;
3613        let mut grow_basis = fill_grow.max(basis_grow);
3614        let mut cross = resolve_cross(cross_len, cross_intrinsic, avail_cross);
3615
3616        if matches!(cross_len, Length::Fit) && !self.subtree_redistributes(child) {
3617            let m_true = self.measure(
3618                child,
3619                Constraints {
3620                    min: Size::ZERO,
3621                    max: Size::new(f32::INFINITY, f32::INFINITY),
3622                },
3623                ctx,
3624            );
3625            let true_main_intrinsic = match cstyle.axis {
3626                Axis::Horizontal => m_true.width,
3627                Axis::Vertical => m_true.height,
3628            };
3629            let true_cross_intrinsic = match cstyle.axis {
3630                Axis::Horizontal => m_true.height,
3631                Axis::Vertical => m_true.width,
3632            };
3633            // Content that grows on the main axis when the cross axis is
3634            // constrained has reflowed (auto-wrapped text). Its unconstrained
3635            // cross size is not a meaningful minimum, or wrapping could never
3636            // take effect.
3637            if main_intrinsic <= true_main_intrinsic + 0.01 {
3638                cross = cross.max(resolve_cross(cross_len, true_cross_intrinsic, avail_cross));
3639            }
3640        }
3641
3642        let main_auto =
3643            matches!(main_len, Length::Fit) || (main_len.is_fill() && !avail_main.is_finite());
3644        let cross_auto =
3645            matches!(cross_len, Length::Fit) || (cross_len.is_fill() && !avail_cross.is_finite());
3646
3647        let (bw, bh) = match cstyle.axis {
3648            Axis::Horizontal => cs.apply_aspect(base, cross, main_auto, cross_auto),
3649            Axis::Vertical => {
3650                let (h, w) = (base, cross);
3651                let (w2, h2) = cs.apply_aspect(w, h, cross_auto, main_auto);
3652                (h2, w2)
3653            }
3654        };
3655        base = bw;
3656        cross = bh;
3657
3658        let extra = if flex_extra.is_finite() && flex_extra > 0.0 {
3659            flex_extra
3660        } else {
3661            0.0
3662        };
3663        let grow = grow_basis.max(cs.flex_weight()).max(extra);
3664        grow_basis = grow;
3665        let shrink = cs.flex_shrink_weight();
3666
3667        let (mut mn, mx, _, _, _) = self.child_limits_full(child, cstyle.axis);
3668        let margin_main = cs.margin_main(cstyle.axis);
3669        let margin_cross = cs.margin_cross(cstyle.axis);
3670        let (base, cross) = match cstyle.axis {
3671            Axis::Horizontal => (
3672                cs.clamp_width(base).max(0.0) + margin_main,
3673                cs.clamp_height(cross).max(0.0) + margin_cross,
3674            ),
3675            Axis::Vertical => (
3676                cs.clamp_height(base).max(0.0) + margin_main,
3677                cs.clamp_width(cross).max(0.0) + margin_cross,
3678            ),
3679        };
3680
3681        if grow <= 0.0 && matches!(main_len, Length::Fit) {
3682            mn = mn.max(base);
3683        }
3684        let _ = grow_basis;
3685        (base.max(0.0), grow, shrink, cross.max(0.0), mn, mx)
3686    }
3687
3688    fn child_grid_placement(&self, child: ElementId) -> GridPlacement {
3689        self.view_rc(child)
3690            .and_then(|v| v.grid_placement())
3691            .unwrap_or(GridPlacement::AUTO)
3692    }
3693
3694    fn grid_cells(
3695        &self,
3696        children: &[ElementId],
3697        grid: &GridStyle,
3698        placements: &[GridPlacement],
3699    ) -> Vec<(usize, usize, usize, usize)> {
3700        let ncols = grid.columns.len().max(1);
3701        let mut cells = Vec::with_capacity(children.len());
3702        let mut occupied: Vec<Vec<bool>> = Vec::new();
3703        let ensure_row = |occupied: &mut Vec<Vec<bool>>, r: usize| {
3704            while occupied.len() <= r {
3705                occupied.push(vec![false; ncols]);
3706            }
3707        };
3708        let mut cursor = (0usize, 0usize);
3709        for (idx, &child) in children.iter().enumerate() {
3710            if self.child_layout_style(child).is_absolute() {
3711                cells.push((0, 0, 0, 0));
3712                continue;
3713            }
3714            let p = placements
3715                .get(idx)
3716                .copied()
3717                .filter(|p| !p.is_auto())
3718                .or_else(|| Some(self.child_grid_placement(child)))
3719                .unwrap_or(GridPlacement::AUTO);
3720            let cs = p.col_span.max(1).min(ncols);
3721            let rs = p.row_span.max(1);
3722            if let (Some(c), Some(r)) = (p.column, p.row) {
3723                let c = c.min(ncols.saturating_sub(1));
3724                ensure_row(&mut occupied, r + rs - 1);
3725                for row in occupied.iter_mut().skip(r).take(rs) {
3726                    for cell in row.iter_mut().take((c + cs).min(ncols)).skip(c) {
3727                        *cell = true;
3728                    }
3729                }
3730                cells.push((c, r, cs, rs));
3731                continue;
3732            }
3733
3734            match grid.auto_flow {
3735                sequoia_layout::GridFlow::Row => {
3736                    let (mut c, mut r) = cursor;
3737                    loop {
3738                        ensure_row(&mut occupied, r + rs - 1);
3739
3740                        let mut placed: Option<usize> = None;
3741                        let start_c = if r == cursor.1 { c } else { 0 };
3742                        for cc in start_c..ncols {
3743                            if cc + cs > ncols {
3744                                break;
3745                            }
3746                            let free =
3747                                (cc..cc + cs).all(|x| (r..r + rs).all(|rr| !occupied[rr][x]));
3748                            if free {
3749                                placed = Some(cc);
3750                                break;
3751                            }
3752                        }
3753                        if let Some(cc) = placed {
3754                            for row in occupied.iter_mut().skip(r).take(rs) {
3755                                for cell in row.iter_mut().skip(cc).take(cs) {
3756                                    *cell = true;
3757                                }
3758                            }
3759                            cells.push((cc, r, cs, rs));
3760                            cursor = (cc + cs, r);
3761                            if cursor.0 >= ncols {
3762                                cursor = (0, r + 1);
3763                            }
3764                            break;
3765                        }
3766
3767                        c = 0;
3768                        r += 1;
3769                    }
3770                }
3771                sequoia_layout::GridFlow::Column => {
3772                    let idx = cells.iter().filter(|(_, _, cs, _)| *cs > 0).count();
3773                    let nrows_est = children.len().div_ceil(ncols).max(1);
3774                    let col = (idx / nrows_est) % ncols;
3775                    let row = idx % nrows_est;
3776                    ensure_row(&mut occupied, row + rs - 1);
3777                    cells.push((col.min(ncols - 1), row, cs, rs));
3778                }
3779            }
3780        }
3781        cells
3782    }
3783
3784    fn track_cap(&self, track: Option<&Track>, avail: f32) -> f32 {
3785        match track {
3786            Some(Track::Px(v)) => v.max(0.0),
3787            Some(Track::Percent(p)) if avail.is_finite() && p.is_finite() => {
3788                (avail * p / 100.0).max(0.0)
3789            }
3790            Some(Track::MinMax { max, .. }) => self.track_cap(Some(max), avail),
3791            _ => f32::INFINITY,
3792        }
3793    }
3794
3795    fn grid_content_sizes(
3796        &self,
3797        children: &[ElementId],
3798        grid: &GridStyle,
3799        placements: &[GridPlacement],
3800        inner_avail: Size,
3801        ctx: &mut LayoutCtx,
3802    ) -> (Vec<f32>, Vec<f32>, Vec<(usize, usize, usize, usize)>, usize) {
3803        let ncols = grid.columns.len().max(1);
3804        let cells = self.grid_cells(children, grid, placements);
3805        let nrows_needed = cells
3806            .iter()
3807            .map(|(_, r, _, rs)| if *rs > 0 { r + rs } else { 0 })
3808            .max()
3809            .unwrap_or(0)
3810            .max(grid.rows.len())
3811            .max(1);
3812
3813        let mut content_col = vec![0.0_f32; ncols];
3814        let mut content_row = vec![0.0_f32; nrows_needed];
3815
3816        let tracks_fixed = grid.columns.iter().all(|t| {
3817            matches!(
3818                t,
3819                sequoia_layout::Track::Px(_)
3820                    | sequoia_layout::Track::Percent(_)
3821                    | sequoia_layout::Track::Fill(_)
3822            )
3823        }) && grid.rows.len() >= nrows_needed
3824            && grid.rows.iter().all(|t| {
3825                matches!(
3826                    t,
3827                    sequoia_layout::Track::Px(_)
3828                        | sequoia_layout::Track::Percent(_)
3829                        | sequoia_layout::Track::Fill(_)
3830                )
3831            });
3832        if tracks_fixed && inner_avail.width.is_finite() && inner_avail.height.is_finite() {
3833            return (content_col, content_row, cells, nrows_needed);
3834        }
3835
3836        for (i, &child) in children.iter().enumerate() {
3837            let (col, row, cs, _rs) = cells[i];
3838            if cs == 0 {
3839                continue;
3840            }
3841            let col_fixed = matches!(
3842                grid.columns.get(col),
3843                Some(sequoia_layout::Track::Px(_)) | Some(sequoia_layout::Track::Percent(_))
3844            );
3845            let row_fixed = matches!(
3846                grid.rows.get(row),
3847                Some(sequoia_layout::Track::Px(_)) | Some(sequoia_layout::Track::Percent(_))
3848            );
3849            if col_fixed && row_fixed {
3850                continue;
3851            }
3852            let col_max = self.track_cap(grid.columns.get(col), inner_avail.width);
3853            let m = self.measure(
3854                child,
3855                Constraints {
3856                    min: Size::ZERO,
3857                    max: Size::new(col_max, f32::INFINITY),
3858                },
3859                ctx,
3860            );
3861
3862            let style = self.child_layout_style(child);
3863            let outer = style.outer_size(m);
3864            content_col[col] = content_col[col].max(outer.width);
3865            if row < content_row.len() {
3866                content_row[row] = content_row[row].max(outer.height);
3867            }
3868        }
3869
3870        (content_col, content_row, cells, nrows_needed)
3871    }
3872
3873    fn grid_rows(grid: &GridStyle, nrows_needed: usize) -> Vec<Track> {
3874        let mut rows: Vec<Track> = grid.rows.clone();
3875        rows.resize(nrows_needed, Track::Fit);
3876        rows
3877    }
3878
3879    fn measure_grid(
3880        &self,
3881        children: &[ElementId],
3882        grid: &GridStyle,
3883        placements: &[GridPlacement],
3884        inner_avail: Size,
3885        ctx: &mut LayoutCtx,
3886    ) -> (f32, f32) {
3887        let (content_col, content_row, _cells, nrows_needed) =
3888            self.grid_content_sizes(children, grid, placements, inner_avail, ctx);
3889        let widths = resolve_tracks(
3890            &grid.columns,
3891            &content_col,
3892            inner_avail.width,
3893            grid.column_gap,
3894        );
3895        let rows = Self::grid_rows(grid, nrows_needed);
3896        let heights = resolve_tracks(&rows, &content_row, inner_avail.height, grid.row_gap);
3897
3898        let gaps_x = grid.column_gap * widths.len().saturating_sub(1) as f32;
3899        let gaps_y = grid.row_gap * heights.len().saturating_sub(1) as f32;
3900        (
3901            widths.iter().sum::<f32>() + gaps_x,
3902            heights.iter().sum::<f32>() + gaps_y,
3903        )
3904    }
3905
3906    fn measure_wrap(
3907        &self,
3908        children: &[ElementId],
3909        wstyle: &WrapStyle,
3910        inner_avail: Size,
3911        ctx: &mut LayoutCtx,
3912    ) -> (f32, f32) {
3913        let avail_main = match wstyle.axis {
3914            Axis::Horizontal => inner_avail.width,
3915            Axis::Vertical => inner_avail.height,
3916        };
3917        let (mains, crosses, _) = self.wrap_child_sizes(children, wstyle, ctx);
3918        let lines = wstyle.lines(&mains, avail_main);
3919
3920        let mut main_max = 0.0_f32;
3921        let mut cross_total = 0.0_f32;
3922        for (line_idx, &(start, end)) in lines.iter().enumerate() {
3923            let mut used = 0.0_f32;
3924            let mut line_cross = 0.0_f32;
3925            for i in start..end {
3926                if i > start {
3927                    used += wstyle.main_gap();
3928                }
3929                used += mains[i];
3930                line_cross = line_cross.max(crosses[i]);
3931            }
3932            main_max = main_max.max(used);
3933            if line_idx > 0 {
3934                cross_total += wstyle.cross_gap();
3935            }
3936            cross_total += line_cross;
3937        }
3938
3939        match wstyle.axis {
3940            Axis::Horizontal => (main_max, cross_total),
3941            Axis::Vertical => (cross_total, main_max),
3942        }
3943    }
3944
3945    fn measure_masonry(
3946        &self,
3947        children: &[ElementId],
3948        mstyle: &MasonryStyle,
3949        inner_avail: Size,
3950        content_width: bool,
3951        ctx: &mut LayoutCtx,
3952    ) -> (f32, f32) {
3953        let flow: Vec<ElementId> = children
3954            .iter()
3955            .copied()
3956            .filter(|c| !self.child_layout_style(*c).is_absolute())
3957            .collect();
3958        if flow.is_empty() {
3959            return (0.0, 0.0);
3960        }
3961
3962        let columns = mstyle.columns.max(1);
3963        let column_gap = sanitize_gap(mstyle.column_gap);
3964        let row_gap = sanitize_gap(mstyle.row_gap);
3965
3966        let column_width = if content_width || !inner_avail.width.is_finite() {
3967            flow.iter()
3968                .map(|&child| {
3969                    let m = self.measure(
3970                        child,
3971                        Constraints {
3972                            min: Size::ZERO,
3973                            max: Size::new(f32::INFINITY, f32::INFINITY),
3974                        },
3975                        ctx,
3976                    );
3977                    let cs = self.child_layout_style(child);
3978                    (m.width + cs.margin.horizontal_sum()).max(0.0)
3979                })
3980                .fold(0.0_f32, f32::max)
3981        } else {
3982            masonry_column_width(inner_avail.width, columns, column_gap)
3983        };
3984
3985        let mut heights = Vec::with_capacity(flow.len());
3986        for child in flow {
3987            let cs = self.child_layout_style(child);
3988            let max_width = (column_width - cs.margin.horizontal_sum()).max(0.0);
3989            let m = self.measure(
3990                child,
3991                Constraints {
3992                    min: Size::ZERO,
3993                    max: Size::new(max_width, f32::INFINITY),
3994                },
3995                ctx,
3996            );
3997            heights.push((m.height + cs.margin.vertical_sum()).max(0.0));
3998        }
3999
4000        let (_, height) = masonry_assign(&heights, columns, row_gap);
4001        let width = column_width * columns as f32 + column_gap * columns.saturating_sub(1) as f32;
4002        (width.max(0.0), height.max(0.0))
4003    }
4004
4005    fn wrap_child_sizes(
4006        &self,
4007        children: &[ElementId],
4008        wstyle: &WrapStyle,
4009        ctx: &mut LayoutCtx,
4010    ) -> (Vec<f32>, Vec<f32>, Vec<ElementId>) {
4011        let mut mains = Vec::with_capacity(children.len());
4012        let mut crosses = Vec::with_capacity(children.len());
4013        let mut ids = Vec::with_capacity(children.len());
4014        for &child in children {
4015            if self.child_layout_style(child).is_absolute() {
4016                continue;
4017            }
4018            let m = self.measure(
4019                child,
4020                Constraints {
4021                    min: Size::ZERO,
4022                    max: Size::new(f32::INFINITY, f32::INFINITY),
4023                },
4024                ctx,
4025            );
4026            let cs = self.child_layout_style(child);
4027            let (main, cross) = match wstyle.axis {
4028                Axis::Horizontal => (
4029                    m.width + cs.margin.horizontal_sum(),
4030                    m.height + cs.margin.vertical_sum(),
4031                ),
4032                Axis::Vertical => (
4033                    m.height + cs.margin.vertical_sum(),
4034                    m.width + cs.margin.horizontal_sum(),
4035                ),
4036            };
4037            mains.push(main.max(0.0));
4038            crosses.push(cross.max(0.0));
4039            ids.push(child);
4040        }
4041        (mains, crosses, ids)
4042    }
4043
4044    fn arrange(&self, el: ElementId, rect: Rect, ctx: &mut LayoutCtx) {
4045        if std::panic::catch_unwind(AssertUnwindSafe(|| self.arrange_inner(el, rect, ctx))).is_err()
4046        {
4047            let type_name = self.type_name_of(el).unwrap_or_else(|| "?".into());
4048            self.record_error(el, type_name, "arrange panicked".to_string());
4049        }
4050    }
4051
4052    fn arrange_inner(&self, el: ElementId, rect: Rect, ctx: &mut LayoutCtx) {
4053        if let Some(e) = self.inner.arena.borrow_mut().get_mut(el) {
4054            e.rect.set(rect);
4055        }
4056
4057        if let Some(v) = self.view_rc(el)
4058            && v.arrange_override_erased(self, el, rect, ctx)
4059        {
4060            return;
4061        }
4062
4063        let Some(v) = self.view_rc(el) else { return };
4064        let style = self.child_layout_style(el);
4065        let content = Rect::from_xywh(
4066            rect.left() + style.padding.left,
4067            rect.top() + style.padding.top,
4068            (rect.size.width - style.padding.horizontal_sum()).max(0.0),
4069            (rect.size.height - style.padding.vertical_sum()).max(0.0),
4070        );
4071
4072        if let Some(gstyle) = v.grid() {
4073            let children = self.children(el);
4074            if !children.is_empty() {
4075                let placements = v.grid_placements().unwrap_or_default();
4076                self.arrange_grid(&children, &gstyle, &placements, content, ctx);
4077            } else {
4078                for &child in &self.children(el) {
4079                    if self.child_layout_style(child).is_absolute() {
4080                        self.arrange_absolute(child, content, ctx);
4081                    }
4082                }
4083            }
4084            return;
4085        }
4086
4087        if let Some(dstyle) = v.dock() {
4088            let children = self.children(el);
4089            if !children.is_empty() {
4090                self.arrange_dock(&children, &dstyle, content, ctx);
4091            }
4092            return;
4093        }
4094
4095        if let Some(wstyle) = v.wrap() {
4096            let children = self.children(el);
4097            if !children.is_empty() {
4098                self.arrange_wrap(&children, &wstyle, content, ctx);
4099            }
4100            return;
4101        }
4102
4103        if let Some(mstyle) = v.masonry() {
4104            let children = self.children(el);
4105            if !children.is_empty() {
4106                self.arrange_masonry(&children, &mstyle, content, ctx);
4107            }
4108            return;
4109        }
4110
4111        if let Some(cstyle) = v.container() {
4112            let children = self.children(el);
4113            if !children.is_empty() {
4114                let flex = v.flex_weights().unwrap_or_default();
4115                self.arrange_children(&children, &cstyle, content, &flex, ctx);
4116                return;
4117            }
4118        }
4119
4120        let children = self.children(el);
4121        if children.len() == 1 {
4122            let child = children[0];
4123            let cs = self.child_layout_style(child);
4124            if cs.is_absolute() {
4125                self.arrange_absolute(child, content, ctx);
4126                return;
4127            }
4128            let avail = Size::new(
4129                (content.size.width - cs.margin.horizontal_sum()).max(0.0),
4130                (content.size.height - cs.margin.vertical_sum()).max(0.0),
4131            );
4132            let m = self.measure(
4133                child,
4134                Constraints {
4135                    min: Size::ZERO,
4136                    max: avail,
4137                },
4138                ctx,
4139            );
4140            let (cw, ch) = match self.view_rc(child) {
4141                Some(_v) => {
4142                    let cs = self.child_layout_style(child);
4143                    let w_auto = matches!(cs.width, Length::Fit)
4144                        || (cs.width.is_fill() && !avail.width.is_finite());
4145                    let h_auto = matches!(cs.height, Length::Fit)
4146                        || (cs.height.is_fill() && !avail.height.is_finite());
4147                    let w = resolve_cross(cs.width, m.width, avail.width);
4148                    let h = resolve_cross(cs.height, m.height, avail.height);
4149                    let (w, h) = cs.apply_aspect(w, h, w_auto, h_auto);
4150                    (cs.clamp_width(w).max(0.0), cs.clamp_height(h).max(0.0))
4151                }
4152                None => (avail.width, avail.height),
4153            };
4154            self.arrange(
4155                child,
4156                Rect::from_xywh(
4157                    content.left() + cs.margin.left,
4158                    content.top() + cs.margin.top,
4159                    cw,
4160                    ch,
4161                ),
4162                ctx,
4163            );
4164        } else if children.len() > 1 {
4165            let flow: Vec<ElementId> = children
4166                .iter()
4167                .copied()
4168                .filter(|c| !self.child_layout_style(*c).is_absolute())
4169                .collect();
4170            if flow.len() == 1 {
4171                let child = flow[0];
4172                let cs = self.child_layout_style(child);
4173                let avail = Size::new(
4174                    (content.size.width - cs.margin.horizontal_sum()).max(0.0),
4175                    (content.size.height - cs.margin.vertical_sum()).max(0.0),
4176                );
4177                let m = self.measure(child, Constraints::loose(avail), ctx);
4178                let w = cs
4179                    .clamp_width(resolve_cross(cs.width, m.width, avail.width))
4180                    .max(0.0);
4181                let h = cs
4182                    .clamp_height(resolve_cross(cs.height, m.height, avail.height))
4183                    .max(0.0);
4184                self.arrange(
4185                    child,
4186                    Rect::from_xywh(
4187                        content.left() + cs.margin.left,
4188                        content.top() + cs.margin.top,
4189                        w,
4190                        h,
4191                    ),
4192                    ctx,
4193                );
4194            } else if !flow.is_empty() {
4195                let cs = ContainerStyle::new(Axis::Vertical, 0.0);
4196                self.arrange_children(&flow, &cs, content, &[], ctx);
4197            }
4198            for &child in &children {
4199                if self.child_layout_style(child).is_absolute() {
4200                    self.arrange_absolute(child, content, ctx);
4201                }
4202            }
4203        }
4204    }
4205
4206    fn arrange_wrap(
4207        &self,
4208        children: &[ElementId],
4209        wstyle: &WrapStyle,
4210        content: Rect,
4211        ctx: &mut LayoutCtx,
4212    ) {
4213        let avail_main = match wstyle.axis {
4214            Axis::Horizontal => content.size.width,
4215            Axis::Vertical => content.size.height,
4216        };
4217        let (mains, crosses, ids) = self.wrap_child_sizes(children, wstyle, ctx);
4218        let lines = wstyle.lines(&mains, avail_main);
4219        let (main_gap, cross_gap) = (wstyle.main_gap(), wstyle.cross_gap());
4220
4221        let mut cross_cursor = 0.0_f32;
4222        for (line_idx, &(start, end)) in lines.iter().enumerate() {
4223            let line_cross = crosses[start..end].iter().copied().fold(0.0_f32, f32::max);
4224            if line_idx > 0 {
4225                cross_cursor += cross_gap;
4226            }
4227            let mut main_cursor = 0.0_f32;
4228            for i in start..end {
4229                let child = ids[i];
4230                if i > start {
4231                    main_cursor += main_gap;
4232                }
4233                let cs = self.child_layout_style(child);
4234                let (m_main_start, m_main_end, m_cross_start, m_cross_end) = match wstyle.axis {
4235                    Axis::Horizontal => (
4236                        cs.margin.left,
4237                        cs.margin.right,
4238                        cs.margin.top,
4239                        cs.margin.bottom,
4240                    ),
4241                    Axis::Vertical => (
4242                        cs.margin.top,
4243                        cs.margin.bottom,
4244                        cs.margin.left,
4245                        cs.margin.right,
4246                    ),
4247                };
4248                let main_outer = mains[i];
4249                let cross_outer = crosses[i];
4250                let main_inner = (main_outer - m_main_start - m_main_end).max(0.0);
4251                let mut cross_inner = (cross_outer - m_cross_start - m_cross_end).max(0.0);
4252                let cross_off = match wstyle.align {
4253                    Align::Start | Align::Stretch | Align::Baseline => 0.0,
4254                    Align::Center => ((line_cross - cross_outer) * 0.5).max(0.0),
4255                    Align::End => (line_cross - cross_outer).max(0.0),
4256                };
4257                if wstyle.align == Align::Stretch {
4258                    let cross_len = match wstyle.axis {
4259                        Axis::Horizontal => cs.height,
4260                        Axis::Vertical => cs.width,
4261                    };
4262                    if matches!(cross_len, Length::Fit) {
4263                        cross_inner = (line_cross - m_cross_start - m_cross_end).max(0.0);
4264                    }
4265                }
4266                let rect = match wstyle.axis {
4267                    Axis::Horizontal => Rect::from_xywh(
4268                        content.left() + main_cursor + m_main_start,
4269                        content.top() + cross_cursor + cross_off + m_cross_start,
4270                        main_inner,
4271                        cross_inner,
4272                    ),
4273                    Axis::Vertical => Rect::from_xywh(
4274                        content.left() + cross_cursor + cross_off + m_cross_start,
4275                        content.top() + main_cursor + m_main_start,
4276                        cross_inner,
4277                        main_inner,
4278                    ),
4279                };
4280                self.arrange(child, rect, ctx);
4281                main_cursor += main_outer;
4282            }
4283            cross_cursor += line_cross;
4284        }
4285
4286        for &child in children {
4287            if self.child_layout_style(child).is_absolute() {
4288                self.arrange_absolute(child, content, ctx);
4289            }
4290        }
4291    }
4292
4293    fn arrange_masonry(
4294        &self,
4295        children: &[ElementId],
4296        mstyle: &MasonryStyle,
4297        content: Rect,
4298        ctx: &mut LayoutCtx,
4299    ) {
4300        let flow: Vec<ElementId> = children
4301            .iter()
4302            .copied()
4303            .filter(|c| !self.child_layout_style(*c).is_absolute())
4304            .collect();
4305        let columns = mstyle.columns.max(1);
4306        let column_gap = sanitize_gap(mstyle.column_gap);
4307        let row_gap = sanitize_gap(mstyle.row_gap);
4308
4309        let column_width = if content.size.width.is_finite() {
4310            masonry_column_width(content.size.width, columns, column_gap)
4311        } else {
4312            flow.iter()
4313                .map(|&child| {
4314                    let m = self.measure(
4315                        child,
4316                        Constraints {
4317                            min: Size::ZERO,
4318                            max: Size::new(f32::INFINITY, f32::INFINITY),
4319                        },
4320                        ctx,
4321                    );
4322                    let cs = self.child_layout_style(child);
4323                    (m.width + cs.margin.horizontal_sum()).max(0.0)
4324                })
4325                .fold(0.0_f32, f32::max)
4326        };
4327
4328        let mut infos = Vec::with_capacity(flow.len());
4329        let mut heights = Vec::with_capacity(flow.len());
4330        for &child in &flow {
4331            let cs = self.child_layout_style(child);
4332            let max_width = (column_width - cs.margin.horizontal_sum()).max(0.0);
4333            let m = self.measure(
4334                child,
4335                Constraints {
4336                    min: Size::ZERO,
4337                    max: Size::new(max_width, f32::INFINITY),
4338                },
4339                ctx,
4340            );
4341            heights.push((m.height + cs.margin.vertical_sum()).max(0.0));
4342            infos.push((child, cs, m));
4343        }
4344
4345        let (placements, _) = masonry_assign(&heights, columns, row_gap);
4346        for (i, (child, cs, m)) in infos.into_iter().enumerate() {
4347            let placement = placements.get(i).copied().unwrap_or_default();
4348            let avail_w = (column_width - cs.margin.horizontal_sum()).max(0.0);
4349            let align = cs.effective_align(mstyle.align);
4350            let mut w = cs
4351                .clamp_width(resolve_cross(cs.width, m.width, avail_w))
4352                .max(0.0);
4353            if align == Align::Stretch && matches!(cs.width, Length::Fit) {
4354                w = avail_w.min(cs.max_width).max(cs.min_width).max(0.0);
4355            }
4356            let h = cs.clamp_height(m.height).max(0.0);
4357            let ox = match align {
4358                Align::Start | Align::Stretch | Align::Baseline => 0.0,
4359                Align::Center => ((avail_w - w).max(0.0)) * 0.5,
4360                Align::End => (avail_w - w).max(0.0),
4361            };
4362            let column_x = content.left()
4363                + placement.column as f32 * (column_width + column_gap)
4364                + cs.margin.left
4365                + ox;
4366            let y = content.top() + placement.top + cs.margin.top;
4367            self.arrange(
4368                child,
4369                Rect::from_xywh(column_x, y, w.max(0.0), h.max(0.0)),
4370                ctx,
4371            );
4372        }
4373
4374        for &child in children {
4375            if self.child_layout_style(child).is_absolute() {
4376                self.arrange_absolute(child, content, ctx);
4377            }
4378        }
4379    }
4380
4381    fn arrange_children(
4382        &self,
4383        children: &[ElementId],
4384        cstyle: &ContainerStyle,
4385        content: Rect,
4386        flex: &[f32],
4387        ctx: &mut LayoutCtx,
4388    ) {
4389        let flow_idx: Vec<usize> = children
4390            .iter()
4391            .enumerate()
4392            .filter(|(_, c)| !self.child_layout_style(**c).is_absolute())
4393            .map(|(i, _)| i)
4394            .collect();
4395        if cstyle.wrap {
4396            self.arrange_children_wrapped(children, &flow_idx, cstyle, content, flex, ctx);
4397        } else {
4398            self.arrange_children_linear(children, &flow_idx, cstyle, content, flex, ctx);
4399        }
4400
4401        for &child in children {
4402            if self.child_layout_style(child).is_absolute() {
4403                self.arrange_absolute(child, content, ctx);
4404            }
4405        }
4406    }
4407
4408    fn arrange_children_linear(
4409        &self,
4410        children: &[ElementId],
4411        flow_idx: &[usize],
4412        cstyle: &ContainerStyle,
4413        content: Rect,
4414        flex: &[f32],
4415        ctx: &mut LayoutCtx,
4416    ) {
4417        let (avail_main, avail_cross) = match cstyle.axis {
4418            Axis::Horizontal => (content.size.width, content.size.height),
4419            Axis::Vertical => (content.size.height, content.size.width),
4420        };
4421
4422        let mut infos: Vec<(ElementId, f32, f32, f32, f32, f32, f32, LayoutStyle)> =
4423            Vec::with_capacity(flow_idx.len());
4424        for &orig in flow_idx {
4425            let child = children[orig];
4426            let extra = flex.get(orig).copied().unwrap_or(0.0);
4427            let (base, grow, shrink, cross, mn, mx) =
4428                self.child_layout_info_full(child, cstyle, avail_main, avail_cross, extra, ctx);
4429            let cs = self.child_layout_style(child);
4430            infos.push((child, base, grow, shrink, cross, mn, mx, cs));
4431        }
4432        let child_styles: Vec<LayoutStyle> = infos.iter().map(|info| info.7).collect();
4433        let gaps = Self::indents_main_total(&child_styles, cstyle.axis, cstyle.gap);
4434
4435        {
4436            let items: Vec<FlexItem> = infos
4437                .iter()
4438                .map(|(_, b, g, s, _, mn, mx, _)| FlexItem::new(*b, *g, *s, *mn, *mx))
4439                .collect();
4440            let mut bases: Vec<f32> = infos.iter().map(|(_, b, ..)| *b).collect();
4441            distribute_flex(&items, &mut bases, gaps, avail_main);
4442            for (info, base) in infos.iter_mut().zip(bases) {
4443                info.1 = base;
4444            }
4445        }
4446
4447        if avail_main.is_finite() {
4448            for info in infos.iter_mut() {
4449                if self.subtree_has_wrap_axis(info.0, cstyle.axis) {
4450                    info.4 = self.constrained_cross(info.0, cstyle.axis, info.1, avail_cross, ctx);
4451                }
4452            }
4453        }
4454
4455        let used_main: f32 = infos.iter().map(|(_, b, ..)| *b).sum::<f32>() + gaps;
4456        let extra = Self::extra_main(avail_main, used_main);
4457        let (mut cursor, gap_step) = cstyle.justify.distribute(infos.len(), cstyle.gap, extra);
4458
4459        let baseline_aligned: Vec<bool> = if cstyle.axis == Axis::Horizontal {
4460            infos
4461                .iter()
4462                .map(|(_, _, _, _, _, _, _, cs)| {
4463                    cs.effective_align(cstyle.align) == Align::Baseline
4464                })
4465                .collect()
4466        } else {
4467            vec![false; infos.len()]
4468        };
4469        let baselines: Vec<f32> = if baseline_aligned.iter().any(|&b| b) {
4470            infos
4471                .iter()
4472                .zip(&baseline_aligned)
4473                .map(|((child, base, _, _, cross, _, _, cs), &aligned)| {
4474                    if !aligned {
4475                        return 0.0;
4476                    }
4477                    let inner_h = (*cross - cs.margin.top - cs.margin.bottom).max(0.0);
4478                    let width = (*base - cs.margin_main(cstyle.axis)).max(0.0);
4479                    self.baseline_offset(*child, width, ctx).unwrap_or(inner_h)
4480                })
4481                .collect()
4482        } else {
4483            vec![0.0; infos.len()]
4484        };
4485        let max_baseline = baselines.iter().copied().fold(0.0_f32, f32::max);
4486
4487        for (i, info) in infos.iter().enumerate() {
4488            let (child, base, _, _, cross, _, _, cs) = *info;
4489            if i > 0 {
4490                cursor += Self::indent_leading(&cs, cstyle.axis, cstyle.gap, false) + gap_step;
4491            }
4492            let (margin_main_start, margin_cross_start) = match cstyle.axis {
4493                Axis::Horizontal => (cs.margin.left, cs.margin.top),
4494                Axis::Vertical => (cs.margin.top, cs.margin.left),
4495            };
4496            let (cross_off, cross_size) = self.place_cross(child, cstyle, cross, avail_cross);
4497            let shift = Self::indent_cross_shift(&cs, cstyle.axis);
4498            let baseline_shift = if baseline_aligned[i] {
4499                (max_baseline - baselines[i]).max(0.0)
4500            } else {
4501                0.0
4502            };
4503            let base_inner = (base - cs.margin_main(cstyle.axis)).max(0.0);
4504            let (rx, ry, rw, rh) = match cstyle.axis {
4505                Axis::Horizontal => (
4506                    content.left() + cursor + margin_main_start,
4507                    content.top() + cross_off + shift + baseline_shift,
4508                    base_inner,
4509                    cross_size,
4510                ),
4511                Axis::Vertical => (
4512                    content.left() + cross_off + shift,
4513                    content.top() + cursor + margin_main_start,
4514                    cross_size,
4515                    base_inner,
4516                ),
4517            };
4518            let _ = margin_cross_start;
4519            cursor += base + Self::indent_trailing(&cs, cstyle.axis);
4520            self.arrange(
4521                child,
4522                Rect::from_xywh(rx, ry, rw.max(0.0), rh.max(0.0)),
4523                ctx,
4524            );
4525        }
4526    }
4527
4528    fn arrange_children_wrapped(
4529        &self,
4530        children: &[ElementId],
4531        flow_idx: &[usize],
4532        cstyle: &ContainerStyle,
4533        content: Rect,
4534        flex: &[f32],
4535        ctx: &mut LayoutCtx,
4536    ) {
4537        let (avail_main, avail_cross) = match cstyle.axis {
4538            Axis::Horizontal => (content.size.width, content.size.height),
4539            Axis::Vertical => (content.size.height, content.size.width),
4540        };
4541        let n = flow_idx.len();
4542        if n == 0 {
4543            return;
4544        }
4545        let mut bases = Vec::with_capacity(n);
4546        let mut crosses = Vec::with_capacity(n);
4547        let mut items = Vec::with_capacity(n);
4548        let mut ids = Vec::with_capacity(n);
4549        for &orig in flow_idx {
4550            let child = children[orig];
4551            let extra = flex.get(orig).copied().unwrap_or(0.0);
4552            let (base, grow, shrink, cross, mn, mx) =
4553                self.child_layout_info_full(child, cstyle, avail_main, avail_cross, extra, ctx);
4554            ids.push(child);
4555            bases.push(base);
4556            crosses.push(cross);
4557            items.push(FlexItem::new(base, grow, shrink, mn, mx));
4558        }
4559        let lines = wrap_lines(&bases, avail_main, cstyle.gap);
4560
4561        let mut line_crosses: Vec<f32> = Vec::with_capacity(lines.len());
4562        for &(s, e) in &lines {
4563            let mut line_sizes = bases[s..e].to_vec();
4564            distribute_flex(
4565                &items[s..e],
4566                &mut line_sizes,
4567                cstyle.main_gaps_total(e - s),
4568                avail_main,
4569            );
4570            for (k, v) in (s..e).zip(line_sizes) {
4571                bases[k] = v;
4572            }
4573
4574            if avail_main.is_finite() {
4575                for k in s..e {
4576                    if self.subtree_has_wrap_axis(ids[k], cstyle.axis) {
4577                        crosses[k] =
4578                            self.constrained_cross(ids[k], cstyle.axis, bases[k], avail_cross, ctx);
4579                    }
4580                }
4581            }
4582            line_crosses.push(crosses[s..e].iter().cloned().fold(0.0f32, f32::max));
4583        }
4584        let total_cross: f32 = line_crosses.iter().sum::<f32>()
4585            + cstyle.cross_gap * lines.len().saturating_sub(1) as f32;
4586        let cross_extra = if avail_cross.is_finite() {
4587            (avail_cross - total_cross).max(0.0)
4588        } else {
4589            0.0
4590        };
4591        let (mut cross_cursor, cross_step) = match cstyle.align_content {
4592            Align::Start | Align::Baseline => (0.0, 0.0),
4593            Align::Center => (cross_extra * 0.5, 0.0),
4594            Align::End => (cross_extra, 0.0),
4595            Align::Stretch if line_crosses.len() > 1 => {
4596                (0.0, cross_extra / (line_crosses.len() - 1) as f32)
4597            }
4598            Align::Stretch => (0.0, 0.0),
4599        };
4600
4601        for (li, &(s, e)) in lines.iter().enumerate() {
4602            let line_cross = line_crosses[li];
4603            if li > 0 {
4604                cross_cursor += cstyle.cross_gap + cross_step;
4605            }
4606            let used: f32 = bases[s..e].iter().sum::<f32>() + cstyle.main_gaps_total(e - s);
4607            let line_extra = if avail_main.is_finite() {
4608                (avail_main - used).max(0.0)
4609            } else {
4610                0.0
4611            };
4612            let (mut cursor, gap_step) = cstyle.justify.distribute(e - s, cstyle.gap, line_extra);
4613            for k in s..e {
4614                if k > s {
4615                    cursor += cstyle.gap + gap_step;
4616                }
4617                let child = ids[k];
4618                let cs = self.child_layout_style(child);
4619                let margin_main_start = match cstyle.axis {
4620                    Axis::Horizontal => cs.margin.left,
4621                    Axis::Vertical => cs.margin.top,
4622                };
4623                let (cross_off, cross_size) =
4624                    self.place_cross(child, cstyle, crosses[k], line_cross);
4625                let base_inner = (bases[k] - cs.margin_main(cstyle.axis)).max(0.0);
4626                let (rx, ry, rw, rh) = match cstyle.axis {
4627                    Axis::Horizontal => (
4628                        content.left() + cursor + margin_main_start,
4629                        content.top() + cross_cursor + cross_off,
4630                        base_inner,
4631                        cross_size,
4632                    ),
4633                    Axis::Vertical => (
4634                        content.left() + cross_cursor + cross_off,
4635                        content.top() + cursor + margin_main_start,
4636                        cross_size,
4637                        base_inner,
4638                    ),
4639                };
4640                cursor += bases[k];
4641                self.arrange(
4642                    child,
4643                    Rect::from_xywh(rx, ry, rw.max(0.0), rh.max(0.0)),
4644                    ctx,
4645                );
4646            }
4647            cross_cursor += line_cross;
4648        }
4649    }
4650
4651    fn arrange_grid(
4652        &self,
4653        children: &[ElementId],
4654        grid: &GridStyle,
4655        placements: &[GridPlacement],
4656        content: Rect,
4657        ctx: &mut LayoutCtx,
4658    ) {
4659        let (content_col, content_row, cells, nrows_needed) =
4660            self.grid_content_sizes(children, grid, placements, content.size, ctx);
4661        let widths = resolve_tracks(
4662            &grid.columns,
4663            &content_col,
4664            content.size.width,
4665            grid.column_gap,
4666        );
4667        let rows = Self::grid_rows(grid, nrows_needed);
4668        let heights = resolve_tracks(&rows, &content_row, content.size.height, grid.row_gap);
4669        let xs = track_offsets(&widths, grid.column_gap);
4670        let ys = track_offsets(&heights, grid.row_gap);
4671
4672        for (i, &child) in children.iter().enumerate() {
4673            let cs = self.child_layout_style(child);
4674            if cs.is_absolute() {
4675                self.arrange_absolute(child, content, ctx);
4676                continue;
4677            }
4678            let (col, row, col_span, row_span) = cells.get(i).copied().unwrap_or((0, 0, 1, 1));
4679            if col_span == 0 {
4680                continue;
4681            }
4682
4683            let cw = (col..(col + col_span).min(widths.len()))
4684                .map(|c| widths[c])
4685                .sum::<f32>()
4686                + grid.column_gap * col_span.saturating_sub(1) as f32;
4687            let ch = (row..(row + row_span).min(heights.len()))
4688                .map(|r| heights[r])
4689                .sum::<f32>()
4690                + grid.row_gap * row_span.saturating_sub(1) as f32;
4691            let col_max = self.track_cap(grid.columns.get(col), content.size.width);
4692            let m = self.measure(
4693                child,
4694                Constraints {
4695                    min: Size::ZERO,
4696                    max: Size::new(col_max, f32::INFINITY),
4697                },
4698                ctx,
4699            );
4700            let eff_h = cs.effective_align(grid.align_items);
4701            let eff_w = cs.effective_align(grid.justify_items);
4702
4703            let mut w = cs
4704                .clamp_width(resolve_cross(cs.width, m.width, cw))
4705                .max(0.0);
4706            let mut h = cs
4707                .clamp_height(resolve_cross(cs.height, m.height, ch))
4708                .max(0.0);
4709            if eff_w == Align::Stretch && matches!(cs.width, Length::Fit) && cw.is_finite() {
4710                w = (cw - cs.margin.horizontal_sum())
4711                    .max(0.0)
4712                    .min(cs.max_width)
4713                    .max(cs.min_width);
4714            }
4715            if eff_h == Align::Stretch && matches!(cs.height, Length::Fit) && ch.is_finite() {
4716                h = (ch - cs.margin.vertical_sum())
4717                    .max(0.0)
4718                    .min(cs.max_height)
4719                    .max(cs.min_height);
4720            }
4721            let cell_x = content.left() + xs.get(col).copied().unwrap_or(0.0);
4722            let cell_y = content.top() + ys.get(row).copied().unwrap_or(0.0);
4723            let avail_w = (cw - cs.margin.horizontal_sum()).max(0.0);
4724            let avail_h = (ch - cs.margin.vertical_sum()).max(0.0);
4725            let ox = match eff_w {
4726                Align::Start | Align::Stretch | Align::Baseline => 0.0,
4727                Align::Center => ((avail_w - w).max(0.0)) * 0.5,
4728                Align::End => (avail_w - w).max(0.0),
4729            };
4730            let oy = match eff_h {
4731                Align::Start | Align::Stretch | Align::Baseline => 0.0,
4732                Align::Center => ((avail_h - h).max(0.0)) * 0.5,
4733                Align::End => (avail_h - h).max(0.0),
4734            };
4735            self.arrange(
4736                child,
4737                Rect::from_xywh(
4738                    cell_x + cs.margin.left + ox,
4739                    cell_y + cs.margin.top + oy,
4740                    w,
4741                    h,
4742                ),
4743                ctx,
4744            );
4745        }
4746    }
4747
4748    fn measure_dock(
4749        &self,
4750        children: &[ElementId],
4751        dstyle: &DockStyle,
4752        inner_avail: Size,
4753        ctx: &mut LayoutCtx,
4754    ) -> (f32, f32) {
4755        let flow: Vec<ElementId> = children
4756            .iter()
4757            .copied()
4758            .filter(|c| !self.child_layout_style(*c).is_absolute())
4759            .collect();
4760        let n = flow.len();
4761        if n == 0 {
4762            return (0.0, 0.0);
4763        }
4764        let gap = sanitize_gap(dstyle.gap);
4765
4766        let order = dock_layout_order(&dstyle.docks, dstyle.last_child_fill, dstyle.ordered, n);
4767        let mut remaining = Size::new(inner_avail.width.max(0.0), inner_avail.height.max(0.0));
4768        let mut measured = vec![Size::ZERO; n];
4769        let mut thick = vec![0.0_f32; n];
4770
4771        for (pos, &i) in order.iter().enumerate() {
4772            let child = flow[i];
4773            let d = resolve_dock(&dstyle.docks, dstyle.last_child_fill, dstyle.ordered, i, n);
4774            let max = match d {
4775                Dock::Left | Dock::Right => Size::new(f32::INFINITY, remaining.height),
4776                Dock::Top | Dock::Bottom => Size::new(remaining.width, f32::INFINITY),
4777                Dock::Fill => remaining,
4778            };
4779            let m = self.measure(child, Constraints::loose(max), ctx);
4780            let cs = self.child_layout_style(child);
4781            let t = match d {
4782                Dock::Left | Dock::Right => Self::clamp_dock_thickness(
4783                    cs.clamp_width(dock_thickness(cs.width, m.width, remaining.width)),
4784                    remaining.width,
4785                ),
4786                Dock::Top | Dock::Bottom => Self::clamp_dock_thickness(
4787                    cs.clamp_height(dock_thickness(cs.height, m.height, remaining.height)),
4788                    remaining.height,
4789                ),
4790                Dock::Fill => 0.0,
4791            };
4792            measured[i] = m;
4793            thick[i] = t;
4794            remaining = Self::shrink_dock_remaining(remaining, d, t, gap, pos, n);
4795        }
4796
4797        let mut cw = 0.0_f32;
4798        let mut ch = 0.0_f32;
4799        for &i in order.iter().rev() {
4800            let m = measured[i];
4801            match resolve_dock(&dstyle.docks, dstyle.last_child_fill, dstyle.ordered, i, n) {
4802                Dock::Fill => {
4803                    cw = cw.max(m.width);
4804                    ch = ch.max(m.height);
4805                }
4806                Dock::Left | Dock::Right => {
4807                    cw += thick[i];
4808
4809                    if order.iter().position(|&x| x == i).unwrap_or(0) + 1 < n {
4810                        cw += gap;
4811                    }
4812                    ch = ch.max(m.height);
4813                }
4814                Dock::Top | Dock::Bottom => {
4815                    cw = cw.max(m.width);
4816                    ch += thick[i];
4817                    if order.iter().position(|&x| x == i).unwrap_or(0) + 1 < n {
4818                        ch += gap;
4819                    }
4820                }
4821            }
4822        }
4823        (cw.max(0.0), ch.max(0.0))
4824    }
4825
4826    fn arrange_dock(
4827        &self,
4828        children: &[ElementId],
4829        dstyle: &DockStyle,
4830        content: Rect,
4831        ctx: &mut LayoutCtx,
4832    ) {
4833        let flow: Vec<ElementId> = children
4834            .iter()
4835            .copied()
4836            .filter(|c| !self.child_layout_style(*c).is_absolute())
4837            .collect();
4838        let n = flow.len();
4839        let gap = sanitize_gap(dstyle.gap);
4840        let order = dock_layout_order(&dstyle.docks, dstyle.last_child_fill, dstyle.ordered, n);
4841        let mut remaining = content;
4842        for (pos, &i) in order.iter().enumerate() {
4843            let child = flow[i];
4844            let d = resolve_dock(&dstyle.docks, dstyle.last_child_fill, dstyle.ordered, i, n);
4845            let max = match d {
4846                Dock::Left | Dock::Right => {
4847                    Size::new(f32::INFINITY, remaining.size.height.max(0.0))
4848                }
4849                Dock::Top | Dock::Bottom => Size::new(remaining.size.width.max(0.0), f32::INFINITY),
4850                Dock::Fill => Size::new(
4851                    remaining.size.width.max(0.0),
4852                    remaining.size.height.max(0.0),
4853                ),
4854            };
4855            let m = self.measure(child, Constraints::loose(max), ctx);
4856            let cs = self.child_layout_style(child);
4857            let rect = match d {
4858                Dock::Left => {
4859                    let t = Self::clamp_dock_thickness(
4860                        cs.clamp_width(dock_thickness(cs.width, m.width, remaining.size.width)),
4861                        remaining.size.width,
4862                    );
4863                    let h = cs.clamp_height(remaining.size.height.max(0.0)).max(0.0);
4864                    Rect::from_xywh(remaining.left(), remaining.top(), t, h)
4865                }
4866                Dock::Right => {
4867                    let t = Self::clamp_dock_thickness(
4868                        cs.clamp_width(dock_thickness(cs.width, m.width, remaining.size.width)),
4869                        remaining.size.width,
4870                    );
4871                    let h = cs.clamp_height(remaining.size.height.max(0.0)).max(0.0);
4872                    Rect::from_xywh(remaining.right() - t, remaining.top(), t, h)
4873                }
4874                Dock::Top => {
4875                    let t = Self::clamp_dock_thickness(
4876                        cs.clamp_height(dock_thickness(cs.height, m.height, remaining.size.height)),
4877                        remaining.size.height,
4878                    );
4879                    let w = cs.clamp_width(remaining.size.width.max(0.0)).max(0.0);
4880                    Rect::from_xywh(remaining.left(), remaining.top(), w, t)
4881                }
4882                Dock::Bottom => {
4883                    let t = Self::clamp_dock_thickness(
4884                        cs.clamp_height(dock_thickness(cs.height, m.height, remaining.size.height)),
4885                        remaining.size.height,
4886                    );
4887                    let w = cs.clamp_width(remaining.size.width.max(0.0)).max(0.0);
4888                    Rect::from_xywh(remaining.left(), remaining.bottom() - t, w, t)
4889                }
4890                Dock::Fill => {
4891                    let w = cs.clamp_width(remaining.size.width.max(0.0)).max(0.0);
4892                    let h = cs.clamp_height(remaining.size.height.max(0.0)).max(0.0);
4893                    Rect::from_xywh(remaining.left(), remaining.top(), w, h)
4894                }
4895            };
4896            self.arrange(
4897                child,
4898                Rect::from_xywh(
4899                    rect.left(),
4900                    rect.top(),
4901                    rect.size.width.max(0.0),
4902                    rect.size.height.max(0.0),
4903                ),
4904                ctx,
4905            );
4906            remaining = Self::shrink_dock_rect(
4907                remaining,
4908                d,
4909                Self::dock_rect_thickness(rect, d),
4910                gap,
4911                pos,
4912                n,
4913            );
4914        }
4915        for &child in children {
4916            if self.child_layout_style(child).is_absolute() {
4917                self.arrange_absolute(child, content, ctx);
4918            }
4919        }
4920    }
4921
4922    fn dock_rect_thickness(rect: Rect, d: Dock) -> f32 {
4923        match d {
4924            Dock::Left | Dock::Right => rect.size.width.max(0.0),
4925            Dock::Top | Dock::Bottom => rect.size.height.max(0.0),
4926            Dock::Fill => 0.0,
4927        }
4928    }
4929
4930    fn clamp_dock_thickness(thickness: f32, remaining: f32) -> f32 {
4931        let t = if thickness.is_finite() {
4932            thickness.max(0.0)
4933        } else {
4934            0.0
4935        };
4936        if remaining.is_finite() {
4937            t.min(remaining.max(0.0))
4938        } else {
4939            t
4940        }
4941    }
4942
4943    fn shrink_dock_remaining(
4944        remaining: Size,
4945        d: Dock,
4946        thickness: f32,
4947        gap: f32,
4948        index: usize,
4949        count: usize,
4950    ) -> Size {
4951        let extra = if d.is_side() && index + 1 < count {
4952            gap
4953        } else {
4954            0.0
4955        };
4956        let sub = thickness + extra;
4957        match d {
4958            Dock::Left | Dock::Right => {
4959                Size::new(shrink_dock_axis(remaining.width, sub), remaining.height)
4960            }
4961            Dock::Top | Dock::Bottom => {
4962                Size::new(remaining.width, shrink_dock_axis(remaining.height, sub))
4963            }
4964            Dock::Fill => Size::ZERO,
4965        }
4966    }
4967
4968    fn shrink_dock_rect(
4969        remaining: Rect,
4970        d: Dock,
4971        thickness: f32,
4972        gap: f32,
4973        index: usize,
4974        count: usize,
4975    ) -> Rect {
4976        let extra = if d.is_side() && index + 1 < count {
4977            gap
4978        } else {
4979            0.0
4980        };
4981        let sub = thickness + extra;
4982        match d {
4983            Dock::Left => Rect::from_xywh(
4984                remaining.left() + sub,
4985                remaining.top(),
4986                shrink_dock_axis(remaining.size.width, sub),
4987                remaining.size.height.max(0.0),
4988            ),
4989            Dock::Right => Rect::from_xywh(
4990                remaining.left(),
4991                remaining.top(),
4992                shrink_dock_axis(remaining.size.width, sub),
4993                remaining.size.height.max(0.0),
4994            ),
4995            Dock::Top => Rect::from_xywh(
4996                remaining.left(),
4997                remaining.top() + sub,
4998                remaining.size.width.max(0.0),
4999                shrink_dock_axis(remaining.size.height, sub),
5000            ),
5001            Dock::Bottom => Rect::from_xywh(
5002                remaining.left(),
5003                remaining.top(),
5004                remaining.size.width.max(0.0),
5005                shrink_dock_axis(remaining.size.height, sub),
5006            ),
5007            Dock::Fill => Rect::from_xywh(remaining.left(), remaining.top(), 0.0, 0.0),
5008        }
5009    }
5010
5011    fn extra_main(avail_main: f32, used_main: f32) -> f32 {
5012        if avail_main.is_finite() {
5013            (avail_main - used_main).max(0.0)
5014        } else {
5015            0.0
5016        }
5017    }
5018
5019    fn sanitize_gap_value(v: f32) -> f32 {
5020        if v.is_finite() { v.max(0.0) } else { 0.0 }
5021    }
5022
5023    fn indent_leading(cs: &LayoutStyle, axis: Axis, default_gap: f32, is_first: bool) -> f32 {
5024        if is_first {
5025            return 0.0;
5026        }
5027        match cs.indent_main_start(axis) {
5028            Some(v) => Self::sanitize_gap_value(v),
5029            None => Self::sanitize_gap_value(default_gap),
5030        }
5031    }
5032
5033    fn indent_trailing(cs: &LayoutStyle, axis: Axis) -> f32 {
5034        match cs.indent_main_end(axis) {
5035            Some(v) => Self::sanitize_gap_value(v),
5036            None => 0.0,
5037        }
5038    }
5039
5040    fn indents_main_total(styles: &[LayoutStyle], axis: Axis, default_gap: f32) -> f32 {
5041        let mut total = 0.0;
5042        for (i, cs) in styles.iter().enumerate() {
5043            total += Self::indent_leading(cs, axis, default_gap, i == 0);
5044            total += Self::indent_trailing(cs, axis);
5045        }
5046        total
5047    }
5048
5049    fn indent_cross_shift(cs: &LayoutStyle, axis: Axis) -> f32 {
5050        let start = cs.indent_cross_start(axis).map(Self::sanitize_gap_value);
5051        let end = cs.indent_cross_end(axis).map(Self::sanitize_gap_value);
5052        start.unwrap_or(0.0) - end.unwrap_or(0.0)
5053    }
5054
5055    fn place_cross(
5056        &self,
5057        child: ElementId,
5058        cstyle: &ContainerStyle,
5059        cross: f32,
5060        avail_cross: f32,
5061    ) -> (f32, f32) {
5062        if self.view_rc(child).is_none() {
5063            return (0.0, 0.0);
5064        }
5065        let cs = self.child_layout_style(child);
5066        let eff = cs.effective_align(cstyle.align);
5067        let (m_start, m_end) = match cstyle.axis {
5068            Axis::Horizontal => (cs.margin.top, cs.margin.bottom),
5069            Axis::Vertical => (cs.margin.left, cs.margin.right),
5070        };
5071        let m_total = m_start + m_end;
5072        let cross_inner = (cross - m_total).max(0.0);
5073
5074        let floor = {
5075            let (cross_len, explicit_min, explicit_max) = match cstyle.axis {
5076                Axis::Horizontal => (cs.height, cs.min_height, cs.max_height),
5077                Axis::Vertical => (cs.width, cs.min_width, cs.max_width),
5078            };
5079            let px = match cross_len {
5080                Length::Px(v) if v.is_finite() => v.max(0.0),
5081                _ => 0.0,
5082            };
5083            let explicit_min = if explicit_min.is_finite() {
5084                explicit_min.max(0.0)
5085            } else {
5086                0.0
5087            };
5088            let mx = if explicit_max.is_nan() || explicit_max == f32::INFINITY {
5089                f32::INFINITY
5090            } else {
5091                explicit_max.max(0.0)
5092            };
5093
5094            let content_floor = match cross_len {
5095                Length::Fit => cross_inner,
5096                _ => 0.0,
5097            };
5098            px.max(explicit_min).max(content_floor).min(mx)
5099        };
5100
5101        let avail_inner = if avail_cross.is_finite() {
5102            (avail_cross - m_total).max(0.0)
5103        } else {
5104            f32::INFINITY
5105        };
5106        let placed_inner = cross_inner.min(avail_inner).max(floor);
5107        let slack = if avail_inner.is_finite() {
5108            (avail_inner - placed_inner).max(0.0)
5109        } else {
5110            0.0
5111        };
5112        match eff {
5113            Align::Start | Align::Baseline => (m_start, placed_inner),
5114            Align::Center => (m_start + slack * 0.5, placed_inner),
5115            Align::End => (m_start + slack, placed_inner),
5116            Align::Stretch => {
5117                let (auto_cross, max_cross) = match cstyle.axis {
5118                    Axis::Horizontal => (matches!(cs.height, Length::Fit), cs.max_height),
5119                    Axis::Vertical => (matches!(cs.width, Length::Fit), cs.max_width),
5120                };
5121
5122                if auto_cross && avail_inner.is_finite() {
5123                    let stretched = avail_inner.min(max_cross).max(floor).max(0.0);
5124                    (m_start, stretched)
5125                } else {
5126                    (m_start, placed_inner)
5127                }
5128            }
5129        }
5130    }
5131
5132    pub(crate) fn arrange_absolute(&self, child: ElementId, content: Rect, ctx: &mut LayoutCtx) {
5133        let cs = self.child_layout_style(child);
5134        let Some(inset) = cs.absolute_inset_of() else {
5135            let m = self.measure(child, Constraints::loose(content.size), ctx);
5136            let w = cs
5137                .clamp_width(resolve_cross(cs.width, m.width, content.size.width))
5138                .max(0.0);
5139            let h = cs
5140                .clamp_height(resolve_cross(cs.height, m.height, content.size.height))
5141                .max(0.0);
5142            self.arrange(
5143                child,
5144                Rect::from_xywh(
5145                    content.left() + cs.margin.left,
5146                    content.top() + cs.margin.top,
5147                    w,
5148                    h,
5149                ),
5150                ctx,
5151            );
5152            return;
5153        };
5154        let ml = cs.margin.left;
5155        let mt = cs.margin.top;
5156        let mr = cs.margin.right;
5157        let mb = cs.margin.bottom;
5158        let avail_w = (content.size.width - ml - mr).max(0.0);
5159        let avail_h = (content.size.height - mt - mb).max(0.0);
5160        let m = self.measure(child, Constraints::loose(Size::new(avail_w, avail_h)), ctx);
5161
5162        let mut w = resolve_cross(cs.width, m.width, avail_w);
5163        let mut h = resolve_cross(cs.height, m.height, avail_h);
5164        if let (Some(l), Some(r)) = (inset.left, inset.right)
5165            && (matches!(cs.width, Length::Fit) || matches!(cs.width, Length::Fill(_)))
5166        {
5167            w = (content.size.width - l.max(0.0) - r.max(0.0) - ml - mr).max(0.0);
5168        }
5169        if let (Some(t), Some(b)) = (inset.top, inset.bottom)
5170            && (matches!(cs.height, Length::Fit) || matches!(cs.height, Length::Fill(_)))
5171        {
5172            h = (content.size.height - t.max(0.0) - b.max(0.0) - mt - mb).max(0.0);
5173        }
5174        w = cs.clamp_width(w).max(0.0);
5175        h = cs.clamp_height(h).max(0.0);
5176        let x = match (inset.left, inset.right) {
5177            (Some(l), _) => content.left() + l + ml,
5178            (None, Some(r)) => content.right() - r - mr - w,
5179            (None, None) => content.left() + ml,
5180        };
5181        let y = match (inset.top, inset.bottom) {
5182            (Some(t), _) => content.top() + t + mt,
5183            (None, Some(b)) => content.bottom() - b - mb - h,
5184            (None, None) => content.top() + mt,
5185        };
5186        self.arrange(child, Rect::from_xywh(x, y, w, h), ctx);
5187    }
5188
5189    fn paint_rec(
5190        &self,
5191        id: ElementId,
5192        parent_offset: Vec2,
5193        parent_scale: f32,
5194        parent_transform: Transform,
5195        list: &mut RenderList,
5196        text: &mut TextSystem,
5197        cull: CullCtx,
5198        pass: PaintPass<'_>,
5199    ) {
5200        let depth = list.clip_depth();
5201        let opacity_depth = list.opacity_depth();
5202        let transform_depth = list.transform_depth();
5203        match std::panic::catch_unwind(AssertUnwindSafe(|| {
5204            self.paint_rec_inner(
5205                id,
5206                parent_offset,
5207                parent_scale,
5208                parent_transform,
5209                &mut *list,
5210                &mut *text,
5211                cull,
5212                pass,
5213            )
5214        })) {
5215            Ok(()) => {}
5216            Err(_) => {
5217                list.truncate_clips(depth);
5218                list.truncate_opacity(opacity_depth);
5219                list.truncate_transforms(transform_depth);
5220                let type_name = self.type_name_of(id).unwrap_or_else(|| "?".into());
5221                self.record_error(id, type_name, "paint panicked".to_string());
5222            }
5223        }
5224    }
5225
5226    fn paint_rec_inner(
5227        &self,
5228        id: ElementId,
5229        parent_offset: Vec2,
5230        parent_scale: f32,
5231        parent_transform: Transform,
5232        list: &mut RenderList,
5233        text: &mut TextSystem,
5234        cull: CullCtx,
5235        pass: PaintPass<'_>,
5236    ) {
5237        let Some(v) = self.view_rc(id) else { return };
5238        let base_rect = {
5239            let arena = self.inner.arena.borrow();
5240            arena.get(id).map(|e| e.rect.get()).unwrap_or(Rect::ZERO)
5241        };
5242
5243        let own = v.paint_offset_ctx_erased(self, id);
5244        let acc = Vec2::new(parent_offset.x + own.x, parent_offset.y + own.y);
5245        let rect = if acc.x == 0.0 && acc.y == 0.0 {
5246            base_rect
5247        } else {
5248            base_rect.translate(acc)
5249        };
5250
5251        let scale = {
5252            let own = v.paint_scale_ctx_erased(self, id);
5253            let own = if own.is_finite() && own > 0.0 {
5254                own
5255            } else {
5256                1.0
5257            };
5258            (parent_scale * own).max(0.0)
5259        };
5260        let rect = if (scale - 1.0).abs() > f32::EPSILON {
5261            let cx = rect.origin.x + rect.size.width * 0.5;
5262            let cy = rect.origin.y + rect.size.height * 0.5;
5263            let w = (rect.size.width * scale).max(0.0);
5264            let h = (rect.size.height * scale).max(0.0);
5265            Rect::from_xywh(cx - w * 0.5, cy - h * 0.5, w, h)
5266        } else {
5267            rect
5268        };
5269
5270        let own_rotation = {
5271            let d = v.paint_rotation_ctx_erased(self, id);
5272            if d.is_finite() { d } else { 0.0 }
5273        };
5274        let own_transform = if own_rotation.abs() > 0.001 {
5275            Transform::rotate_around_degrees(own_rotation, rect.center())
5276        } else {
5277            Transform::IDENTITY
5278        };
5279        let total_transform = parent_transform.then(own_transform);
5280        let pushes_transform = !own_transform.is_identity();
5281        if pushes_transform {
5282            list.push_transform(own_transform);
5283        }
5284
5285        let paint_bounds = total_transform.apply_rect_aabb(rect);
5286
5287        let opacity = v
5288            .paint_opacity_ctx_erased(self, id)
5289            .map(|o| {
5290                if o.is_finite() {
5291                    o.clamp(0.0, 1.0)
5292                } else {
5293                    1.0
5294                }
5295            })
5296            .unwrap_or(1.0);
5297        let pushes_opacity = opacity < 1.0 - f32::EPSILON;
5298        if pushes_opacity {
5299            list.push_opacity(opacity);
5300        }
5301
5302        let clip = list.active_clip();
5303        let clip_self = v.clip_self_erased();
5304        if cull.fully_clipped(paint_bounds, clip, clip_self) {
5305            if pushes_opacity {
5306                list.pop_opacity();
5307            }
5308            if pushes_transform {
5309                list.pop_transform();
5310            }
5311            return;
5312        }
5313        if !cull.intersects(paint_bounds, clip) {
5314            self.paint_children(id, acc, scale, total_transform, list, text, cull, pass);
5315            if pushes_opacity {
5316                list.pop_opacity();
5317            }
5318            if pushes_transform {
5319                list.pop_transform();
5320            }
5321            return;
5322        }
5323
5324        let should_clip =
5325            clip_self || v.layout_style_ctx_erased(self, id).overflow == Overflow::Hidden;
5326        if should_clip {
5327            let clip_rect = if pushes_transform { paint_bounds } else { rect };
5328            let r = v.clip_radius_erased();
5329            if r > 0.0 {
5330                list.push_rounded_clip(clip_rect, r);
5331            } else {
5332                list.push_clip(clip_rect);
5333            }
5334        }
5335
5336        let surface_target = v.surface_target_erased();
5337        let foreground_filter = v.foreground_filter_erased().filter(|f| !f.is_empty());
5338        let cacheable =
5339            v.cache_layer_erased() && surface_target.is_none() && foreground_filter.is_none();
5340
5341        let cache = if cacheable {
5342            let revision = self
5343                .inner
5344                .arena
5345                .borrow()
5346                .get(id)
5347                .map(|e| e.revision)
5348                .unwrap_or(0);
5349            let key = LayerKey {
5350                element_id: id.key(),
5351                revision,
5352            };
5353            let interact = self.inner.interact.get();
5354            let stamp = LayerStamp {
5355                rect,
5356                scale,
5357                transform: total_transform,
5358                clip: list.active_clip(),
5359                rounded_clip: list.active_rounded_clip(),
5360                opacity: list.active_opacity(),
5361                text_atlas: text.atlas().id(),
5362                text_epoch: text.atlas().epoch(),
5363                window: self.inner.window.get(),
5364                hovered: self.interaction_target_within(id, interact.hovered),
5365                pressed: self.interaction_target_within(id, interact.pressed),
5366                focused: self.interaction_target_within(id, interact.focus),
5367                captured: self.interaction_target_within(id, interact.captured),
5368                cull,
5369            };
5370            if let Some(hit) = self.inner.layers.borrow().get_stamped(key, &stamp) {
5371                self.inner
5372                    .layer_hits
5373                    .set(self.inner.layer_hits.get().wrapping_add(1));
5374                list.append(&hit);
5375                if should_clip {
5376                    list.pop_clip();
5377                }
5378                if pushes_opacity {
5379                    list.pop_opacity();
5380                }
5381                if pushes_transform {
5382                    list.pop_transform();
5383                }
5384                return;
5385            }
5386            self.inner
5387                .layer_misses
5388                .set(self.inner.layer_misses.get().wrapping_add(1));
5389            self.inner.layers.borrow_mut().invalidate_element(id.key());
5390            let surfaces = self.inner.surfaces.borrow().len();
5391            Some((key, stamp, list.mark(), surfaces))
5392        } else {
5393            None
5394        };
5395
5396        if let Some(target) = surface_target {
5397            let clipped = match list.active_clip() {
5398                Some(c) => c.intersect(rect),
5399                None => rect,
5400            };
5401            if !clipped.is_empty() {
5402                self.inner.surfaces.borrow_mut().push(SurfaceDraw {
5403                    rect: clipped,
5404                    full: rect,
5405                    target,
5406                });
5407            }
5408        }
5409
5410        let filter_handle = foreground_filter.and_then(|f| {
5411            let r = if pushes_transform { paint_bounds } else { rect };
5412            list.push_filter_begin(r, 0.0, f.blur_px(), f.span_mask())
5413        });
5414
5415        let depth = list.clip_depth();
5416
5417        let (state, _) = self.view_state(id);
5418        let hovered_subtree = pass.hovered_subtree(id);
5419        {
5420            let res = std::panic::catch_unwind(AssertUnwindSafe(|| {
5421                let mut ctx = PaintCtx {
5422                    list: &mut *list,
5423                    text: &mut *text,
5424                    environment: pass.env,
5425                    rect,
5426                    element: id,
5427                    state,
5428                    window: self.inner.window.get(),
5429                    hovered_subtree,
5430                    runtime: self,
5431                    shapes: &self.inner.shapes,
5432                    draw_offset: Vec2::ZERO,
5433                    offset_stack: Vec::new(),
5434                };
5435                v.paint_self_erased(&mut ctx);
5436            }));
5437            if res.is_err() {
5438                self.record_error(
5439                    id,
5440                    v.type_name().to_string(),
5441                    "paint_self panicked".to_string(),
5442                );
5443            }
5444        }
5445        self.paint_children(id, acc, scale, total_transform, list, text, cull, pass);
5446
5447        {
5448            let res = std::panic::catch_unwind(AssertUnwindSafe(|| {
5449                let mut ctx = PaintCtx {
5450                    list: &mut *list,
5451                    text: &mut *text,
5452                    environment: pass.env,
5453                    rect,
5454                    element: id,
5455                    state,
5456                    window: self.inner.window.get(),
5457                    hovered_subtree,
5458                    runtime: self,
5459                    shapes: &self.inner.shapes,
5460                    draw_offset: Vec2::ZERO,
5461                    offset_stack: Vec::new(),
5462                };
5463                v.paint_foreground_erased(&mut ctx);
5464            }));
5465            if res.is_err() {
5466                self.record_error(
5467                    id,
5468                    v.type_name().to_string(),
5469                    "paint_foreground panicked".to_string(),
5470                );
5471            }
5472        }
5473        if let Some(h) = filter_handle {
5474            list.push_filter_end(h);
5475        }
5476
5477        if let Some((key, stamp, mark, surfaces)) = cache
5478            && text.atlas().epoch() == stamp.text_epoch
5479            && self.inner.surfaces.borrow().len() == surfaces
5480            && let Some(captured) = list.suffix(mark)
5481        {
5482            let mut layers = self.inner.layers.borrow_mut();
5483            layers.invalidate_element(key.element_id);
5484            layers.insert_stamped(key, captured, stamp);
5485        }
5486
5487        list.truncate_clips(depth);
5488        if should_clip {
5489            list.pop_clip();
5490        }
5491        if pushes_opacity {
5492            list.pop_opacity();
5493        }
5494        if pushes_transform {
5495            list.pop_transform();
5496        }
5497    }
5498
5499    fn paint_children(
5500        &self,
5501        id: ElementId,
5502        acc: Vec2,
5503        scale: f32,
5504        transform: Transform,
5505        list: &mut RenderList,
5506        text: &mut TextSystem,
5507        cull: CullCtx,
5508        pass: PaintPass<'_>,
5509    ) {
5510        const INLINE: usize = 16;
5511        let mut stack = [ElementId {
5512            index: 0,
5513            generation: 0,
5514        }; INLINE];
5515        let n = {
5516            let arena = self.inner.arena.borrow();
5517            let Some(el) = arena.get(id) else { return };
5518            let n = el.children.len();
5519            if n <= INLINE {
5520                stack[..n].copy_from_slice(&el.children);
5521            }
5522            n
5523        };
5524        if n <= INLINE {
5525            for c in &stack[..n] {
5526                self.paint_rec(*c, acc, scale, transform, list, text, cull, pass);
5527            }
5528        } else {
5529            for c in self.children(id) {
5530                self.paint_rec(c, acc, scale, transform, list, text, cull, pass);
5531            }
5532        }
5533    }
5534
5535    fn view_rc(&self, id: ElementId) -> Option<Rc<dyn ErasedView>> {
5536        self.inner.arena.borrow().get(id)?.view.clone()
5537    }
5538
5539    fn invalidate_measure_chain(&self, id: ElementId) {
5540        self.inner.layout_dirty.set(true);
5541        self.inner
5542            .layout_epoch
5543            .set(self.inner.layout_epoch.get().wrapping_add(1));
5544        let mut measures = self.inner.measures.borrow_mut();
5545        let arena = self.inner.arena.borrow();
5546        let mut cur = Some(id);
5547        let mut n = 0u32;
5548        while let Some(c) = cur {
5549            if measures.remove(&c).is_some() {
5550                n += 1;
5551            }
5552            cur = arena.get(c).and_then(|e| e.parent);
5553            if n > 4096 {
5554                break;
5555            }
5556        }
5557        drop(arena);
5558        drop(measures);
5559        if n > 0 {
5560            let mut s = self.inner.stats.get();
5561            s.measure_invalidations += u64::from(n);
5562            self.inner.stats.set(s);
5563        }
5564    }
5565
5566    fn alloc(
5567        &self,
5568        value: Rc<dyn ErasedView>,
5569        parent: Option<ElementId>,
5570        index: usize,
5571        key: u64,
5572    ) -> ElementId {
5573        let (slot, generation) = { self.inner.arena.borrow_mut().alloc_slot() };
5574        let id = ElementId {
5575            index: slot,
5576            generation,
5577        };
5578        let weak: Weak<Inner> = Rc::downgrade(&self.inner);
5579        let watcher = Watcher::new(move || {
5580            if let Some(inner) = weak.upgrade() {
5581                inner.schedule(id);
5582            }
5583        });
5584        {
5585            let mut arena = self.inner.arena.borrow_mut();
5586            arena.slots[slot as usize] = Some(Element {
5587                parent,
5588                key,
5589                children: Vec::new(),
5590                view: Some(value),
5591                watcher: Some(watcher),
5592                reads: ReadSet::empty(),
5593                dirty: Dirty::empty(),
5594                revision: 0,
5595                rect: Cell::new(Rect::ZERO),
5596            });
5597            if let Some(p) = parent
5598                && let Some(pe) = arena.get_mut(p)
5599            {
5600                let at = index.min(pe.children.len());
5601                pe.children.insert(at, id);
5602            }
5603        }
5604        let mut s = self.inner.stats.get();
5605        s.mounts += 1;
5606        self.inner.stats.set(s);
5607        id
5608    }
5609
5610    fn build_with(&self, el: ElementId, value: Rc<dyn ErasedView>) {
5611        self.inner.bump_revision(el);
5612        self.invalidate_measure_chain(el);
5613        let v = value;
5614
5615        let type_name = v.type_name().to_string();
5616        let fail = |rt: &Self| {
5617            rt.rebind_reads(el, sequoia_reactive::ReadSet::empty());
5618            rt.truncate_children(el, 0);
5619            rt.record_error(el, type_name.clone(), "body panicked".to_string());
5620        };
5621
5622        let overridden_res =
5623            std::panic::catch_unwind(AssertUnwindSafe(|| capture_reads(|| v.children_override())));
5624        let Ok((overridden, override_reads)) = overridden_res else {
5625            fail(self);
5626            return;
5627        };
5628        if let Some(children) = overridden {
5629            let reads = match std::panic::catch_unwind(AssertUnwindSafe(|| {
5630                capture_reads(|| v.call_body())
5631            })) {
5632                Ok((_, body_reads)) => override_reads.union(body_reads),
5633                Err(_) => override_reads,
5634            };
5635            self.rebind_reads(el, reads);
5636            let list = children
5637                .into_iter()
5638                .enumerate()
5639                .map(|(i, av)| (i as u64, av.into_inner()))
5640                .collect();
5641            self.reconcile_keyed(el, list);
5642            return;
5643        }
5644
5645        {
5646            let keyed_res =
5647                std::panic::catch_unwind(AssertUnwindSafe(|| capture_reads(|| v.keyed_children())));
5648            let Ok((keyed, reads)) = keyed_res else {
5649                fail(self);
5650                return;
5651            };
5652            if let Some(children) = keyed {
5653                let reads = match std::panic::catch_unwind(AssertUnwindSafe(|| {
5654                    capture_reads(|| v.call_body())
5655                })) {
5656                    Ok((_, body_reads)) => reads.union(body_reads),
5657                    Err(_) => reads,
5658                };
5659                self.rebind_reads(el, reads);
5660                let list = children
5661                    .into_iter()
5662                    .map(|(k, av)| (k, av.into_inner()))
5663                    .collect();
5664                self.reconcile_keyed(el, list);
5665                return;
5666            }
5667        }
5668
5669        let is_leaf = match std::panic::catch_unwind(AssertUnwindSafe(|| v.is_leaf())) {
5670            Ok(b) => b,
5671            Err(_) => {
5672                self.record_error(el, type_name.clone(), "is_leaf panicked".to_string());
5673                false
5674            }
5675        };
5676        if is_leaf {
5677            let leaf_res =
5678                std::panic::catch_unwind(AssertUnwindSafe(|| capture_reads(|| v.call_body())));
5679            let Ok((_, reads)) = leaf_res else {
5680                fail(self);
5681                return;
5682            };
5683            self.rebind_reads(el, reads);
5684            self.truncate_children(el, 0);
5685            return;
5686        }
5687
5688        let body_res =
5689            std::panic::catch_unwind(AssertUnwindSafe(|| capture_reads(|| v.call_body())));
5690        let Ok((body, reads)) = body_res else {
5691            fail(self);
5692            return;
5693        };
5694        self.rebind_reads(el, reads);
5695        self.reconcile_keyed(el, vec![(0, body.into_inner())]);
5696    }
5697
5698    fn reconcile_keyed(&self, parent: ElementId, new: Vec<(u64, Rc<dyn ErasedView>)>) {
5699        let old: Vec<(u64, ElementId)> = {
5700            let arena = self.inner.arena.borrow();
5701            arena
5702                .get(parent)
5703                .map(|p| {
5704                    p.children
5705                        .iter()
5706                        .filter_map(|&c| arena.get(c).map(|e| (e.key, c)))
5707                        .collect()
5708                })
5709                .unwrap_or_default()
5710        };
5711
5712        let new_keys: HashSet<u64> = new.iter().map(|(k, _)| *k).collect();
5713
5714        for (k, id) in &old {
5715            if !new_keys.contains(k) {
5716                self.unmount_subtree(*id);
5717            }
5718        }
5719
5720        let mut buckets: HashMap<u64, Vec<ElementId>> = HashMap::new();
5721        for (k, id) in old {
5722            if new_keys.contains(&k) {
5723                buckets.entry(k).or_default().push(id);
5724            }
5725        }
5726        for ids in buckets.values_mut() {
5727            ids.reverse();
5728        }
5729
5730        let mut order: Vec<ElementId> = Vec::with_capacity(new.len());
5731        for (key, view) in new {
5732            let view = unwrap_any(view);
5733            let reused = buckets.get_mut(&key).and_then(|ids| ids.pop());
5734            match reused {
5735                Some(el) => {
5736                    let same_type = {
5737                        let arena = self.inner.arena.borrow();
5738                        arena
5739                            .get(el)
5740                            .and_then(|e| e.view.as_ref())
5741                            .map(|v| v.view_type())
5742                            == Some(view.view_type())
5743                    };
5744                    if same_type {
5745                        let old_view = self
5746                            .inner
5747                            .arena
5748                            .borrow()
5749                            .get(el)
5750                            .and_then(|e| e.view.clone());
5751                        if let Some(old) = old_view {
5752                            view.reuse_state_erased(&*old);
5753                        }
5754                        if let Some(e) = self.inner.arena.borrow_mut().get_mut(el) {
5755                            e.key = key;
5756                            e.view = Some(view);
5757                        }
5758                        self.invalidate_measure_chain(el);
5759                        if let Some(v) = self.view_rc(el) {
5760                            self.build_with(el, v);
5761                        }
5762                        order.push(el);
5763                    } else {
5764                        self.unmount_subtree(el);
5765                        order.push(self.mount_fresh_detached(view, key));
5766                    }
5767                }
5768                None => order.push(self.mount_fresh_detached(view, key)),
5769            }
5770        }
5771
5772        for (_, leftover) in buckets {
5773            for id in leftover {
5774                self.unmount_subtree(id);
5775            }
5776        }
5777
5778        self.invalidate_measure_chain(parent);
5779        self.set_children(parent, order);
5780    }
5781
5782    fn mount_fresh_detached(&self, value: Rc<dyn ErasedView>, key: u64) -> ElementId {
5783        let id = self.alloc(value, None, 0, key);
5784        if let Some(v) = self.view_rc(id) {
5785            self.build_with(id, v);
5786        }
5787        if let Some(v) = self.view_rc(id) {
5788            let type_name = v.type_name().to_string();
5789            if std::panic::catch_unwind(AssertUnwindSafe(|| {
5790                v.on_mount();
5791            }))
5792            .is_err()
5793            {
5794                self.record_error(id, type_name, "on_mount panicked".to_string());
5795            }
5796        }
5797        id
5798    }
5799
5800    fn set_children(&self, parent: ElementId, order: Vec<ElementId>) {
5801        {
5802            let mut arena = self.inner.arena.borrow_mut();
5803            for &c in &order {
5804                if let Some(e) = arena.get_mut(c) {
5805                    e.parent = Some(parent);
5806                }
5807            }
5808            if let Some(p) = arena.get_mut(parent) {
5809                p.children = order;
5810            }
5811        }
5812        self.invalidate_measure_chain(parent);
5813    }
5814
5815    fn truncate_children(&self, el: ElementId, keep: usize) {
5816        let doomed: Vec<ElementId> = {
5817            let arena = self.inner.arena.borrow();
5818            arena
5819                .get(el)
5820                .map(|e| e.children.iter().skip(keep).copied().collect())
5821                .unwrap_or_default()
5822        };
5823        for d in doomed {
5824            self.unmount_subtree(d);
5825        }
5826        if let Some(e) = self.inner.arena.borrow_mut().get_mut(el) {
5827            e.children.truncate(keep);
5828        }
5829    }
5830
5831    fn rebind_reads(&self, el: ElementId, reads: ReadSet) {
5832        if let Some(e) = self.inner.arena.borrow_mut().get_mut(el) {
5833            if let Some(w) = e.watcher.clone() {
5834                e.reads.unbind(&w);
5835            }
5836            e.reads = reads;
5837            if let Some(w) = e.watcher.clone() {
5838                e.reads.bind(&w);
5839            }
5840        }
5841    }
5842
5843    fn unmount_subtree(&self, id: ElementId) {
5844        self.invalidate_measure_chain(id);
5845        let children: Vec<ElementId> = {
5846            let arena = self.inner.arena.borrow();
5847            arena
5848                .get(id)
5849                .map(|e| e.children.clone())
5850                .unwrap_or_default()
5851        };
5852        for c in children {
5853            self.unmount_subtree(c);
5854        }
5855        let (parent, slot, view) = {
5856            let mut arena = self.inner.arena.borrow_mut();
5857            let Some(e) = arena.get_mut(id) else { return };
5858            if let Some(w) = e.watcher.take() {
5859                e.reads.unbind(&w);
5860            }
5861            (e.parent, id.index, e.view.take())
5862        };
5863        if let Some(v) = view {
5864            let type_name = v.type_name().to_string();
5865            if std::panic::catch_unwind(AssertUnwindSafe(|| {
5866                v.on_unmount();
5867            }))
5868            .is_err()
5869            {
5870                self.record_error(id, type_name, "on_unmount panicked".to_string());
5871            }
5872        }
5873        if let Some(p) = parent {
5874            let mut arena = self.inner.arena.borrow_mut();
5875            if let Some(pe) = arena.get_mut(p)
5876                && let Some(pos) = pe.children.iter().position(|&c| c == id)
5877            {
5878                pe.children.remove(pos);
5879            }
5880        }
5881        self.inner.measures.borrow_mut().remove(&id);
5882        self.inner
5883            .live_anims
5884            .borrow_mut()
5885            .retain(|(eid, _), _| *eid != id);
5886
5887        self.inner.states.borrow_mut().remove(&id);
5888        if let Some(cancels) = self.inner.scoped_cancels.borrow_mut().remove(&id) {
5889            for cancel in cancels {
5890                let _ = std::panic::catch_unwind(AssertUnwindSafe(cancel));
5891            }
5892        }
5893        self.inner.layers.borrow_mut().invalidate_element(id.key());
5894        self.inner.arena.borrow_mut().free_slot(slot);
5895
5896        let anchored = self.inner.overlays.borrow().anchored_to(id);
5897        for oid in anchored {
5898            self.overlay_hide(oid);
5899        }
5900
5901        if self
5902            .inner
5903            .pending_drag
5904            .borrow()
5905            .as_ref()
5906            .is_some_and(|p| p.source == id)
5907        {
5908            self.inner.pending_drag.borrow_mut().take();
5909        }
5910        {
5911            let mut guard = self.inner.active_drag.borrow_mut();
5912            if let Some(act) = guard.as_mut() {
5913                if act.source == Some(id) {
5914                    act.source = None;
5915                }
5916                if act.target == Some(id) {
5917                    act.target = None;
5918                }
5919            }
5920        }
5921        let mut s = self.inner.stats.get();
5922        s.unmounts += 1;
5923        self.inner.stats.set(s);
5924    }
5925}
5926
5927fn shrink_dock_axis(remaining: f32, used: f32) -> f32 {
5928    if remaining.is_finite() {
5929        (remaining - used).max(0.0)
5930    } else {
5931        remaining
5932    }
5933}
5934
5935fn filled_root_size(style: LayoutStyle, bounds: Size, measured: Size) -> Size {
5936    Size::new(
5937        if style.width.is_fit() {
5938            style.clamp_width(bounds.width)
5939        } else {
5940            measured.width
5941        },
5942        if style.height.is_fit() {
5943            style.clamp_height(bounds.height)
5944        } else {
5945            measured.height
5946        },
5947    )
5948}
5949
5950fn unwrap_any(mut v: Rc<dyn ErasedView>) -> Rc<dyn ErasedView> {
5951    for _ in 0..64 {
5952        let any: Rc<dyn Any> = v.clone().as_any_rc();
5953        match any.downcast::<AnyView>() {
5954            Ok(av) => {
5955                let inner = av.clone_inner();
5956                if Rc::ptr_eq(&inner, &v) {
5957                    return v;
5958                }
5959                v = inner;
5960            }
5961            Err(_) => return v,
5962        }
5963    }
5964    v
5965}
5966
5967#[cfg(test)]
5968mod tests {
5969    use super::*;
5970
5971    fn id(index: u32) -> ElementId {
5972        ElementId {
5973            index,
5974            generation: 0,
5975        }
5976    }
5977
5978    #[test]
5979    fn filled_root_size_stretches_only_fit_axes() {
5980        let bounds = Size::new(800.0, 600.0);
5981        let measured = Size::new(120.0, 40.0);
5982        assert_eq!(
5983            filled_root_size(LayoutStyle::default(), bounds, measured),
5984            bounds
5985        );
5986
5987        let style = LayoutStyle {
5988            width: Length::px(120.0),
5989            height: Length::percent(50.0),
5990            ..LayoutStyle::default()
5991        };
5992        assert_eq!(filled_root_size(style, bounds, measured), measured);
5993
5994        let capped = LayoutStyle {
5995            max_width: 400.0,
5996            max_height: 300.0,
5997            ..LayoutStyle::default()
5998        };
5999        assert_eq!(
6000            filled_root_size(capped, bounds, measured),
6001            Size::new(400.0, 300.0)
6002        );
6003    }
6004
6005    #[test]
6006    fn paint_dirty_does_not_rebuild_but_layout_dirty_does() {
6007        let rt = Runtime::new();
6008        rt.mount(crate::Unit);
6009        let root = rt.root().unwrap();
6010
6011        rt.mark_damage(root, Dirty::PAINT);
6012        assert!(rt.damage_flags().contains(Dirty::PAINT));
6013        assert!(!rt.damage_flags().needs_layout());
6014        rt.tick();
6015        assert_eq!(
6016            rt.stats().rebuilds,
6017            0,
6018            "paint-only damage skips settle rebuild"
6019        );
6020
6021        rt.request_rebuild(root);
6022        rt.tick();
6023        assert_eq!(rt.stats().rebuilds, 1, "layout damage rebuilds the element");
6024    }
6025
6026    #[test]
6027    fn layout_is_skipped_when_clean_and_reruns_when_dirty() {
6028        #[derive(Clone)]
6029        struct Counted(Rc<Cell<u32>>);
6030
6031        impl View for Counted {
6032            fn body(&self) -> impl View + 'static {
6033                crate::Unit
6034            }
6035
6036            fn layout_style(&self) -> LayoutStyle {
6037                self.0.set(self.0.get() + 1);
6038                LayoutStyle::default()
6039            }
6040        }
6041
6042        let calls = Rc::new(Cell::new(0));
6043        let rt = Runtime::new();
6044        rt.mount(Counted(calls.clone()));
6045        let root = rt.root().unwrap();
6046        let mut text = TextSystem::new();
6047        let bounds = Rect::from_xywh(0.0, 0.0, 200.0, 120.0);
6048
6049        rt.layout(bounds, &mut text);
6050        let first = calls.get();
6051        assert!(first > 0);
6052
6053        rt.layout(bounds, &mut text);
6054        assert_eq!(calls.get(), first, "clean layout is skipped");
6055
6056        rt.mark_damage(root, Dirty::PAINT);
6057        rt.layout(bounds, &mut text);
6058        assert_eq!(calls.get(), first, "paint damage does not relayout");
6059
6060        rt.request_rebuild(root);
6061        rt.tick();
6062        rt.layout(bounds, &mut text);
6063        assert!(calls.get() > first, "layout dirty rebuilds and relayouts");
6064
6065        let second = calls.get();
6066        rt.layout(Rect::from_xywh(0.0, 0.0, 320.0, 180.0), &mut text);
6067        assert!(calls.get() > second, "viewport change relayouts");
6068    }
6069
6070    #[test]
6071    fn animated_layout_props_relayout_on_clock_advance() {
6072        use crate::layout_live::AnimatedSize;
6073        use crate::live::SignalLiveExt;
6074
6075        #[derive(Clone)]
6076        struct AnimatedRoot {
6077            width: sequoia_reactive::Signal<f32>,
6078        }
6079
6080        impl View for AnimatedRoot {
6081            fn body(&self) -> impl View + 'static {
6082                AnimatedSize::width(
6083                    crate::Unit,
6084                    self.width
6085                        .clone()
6086                        .animate(AnimSpec::linear(std::time::Duration::from_secs(10))),
6087                )
6088            }
6089        }
6090
6091        let width = sequoia_reactive::Signal::new(80.0f32);
6092        let rt = Runtime::new();
6093        rt.set_manual_clock(true);
6094        rt.mount(AnimatedRoot {
6095            width: width.clone(),
6096        });
6097        let root = rt.root().unwrap();
6098        let child = rt.children(root)[0];
6099        let mut text = TextSystem::new();
6100        let bounds = Rect::from_xywh(0.0, 0.0, 400.0, 200.0);
6101
6102        rt.layout(bounds, &mut text);
6103        assert!((rt.rect_of(child).unwrap().size.width - 80.0).abs() < 0.01);
6104
6105        width.set(200.0);
6106        rt.tick();
6107        rt.layout(bounds, &mut text);
6108        let start = rt.rect_of(child).unwrap().size.width;
6109
6110        rt.advance_for_test(std::time::Duration::from_secs(5));
6111        rt.layout(bounds, &mut text);
6112        let mid = rt.rect_of(child).unwrap().size.width;
6113        assert!(mid > start, "animated width should grow: {start} -> {mid}");
6114        assert!(mid < 200.0, "animation should still be in flight: {mid}");
6115    }
6116
6117    #[test]
6118    fn cached_layer_invalidates_when_child_rebuilds() {
6119        use crate::views::{Cached, Panel};
6120        use sequoia_core::Color;
6121
6122        #[derive(Clone)]
6123        struct ColorBox(Signal<Color>);
6124
6125        impl View for ColorBox {
6126            fn body(&self) -> impl View + 'static {
6127                Panel::new().background(self.0.get()).size(40.0, 30.0)
6128            }
6129        }
6130
6131        let color = Signal::new(Color::RED);
6132        let rt = Runtime::new();
6133        rt.mount(Cached::new(ColorBox(color.clone())));
6134        let mut text = TextSystem::new();
6135        let bounds = Rect::from_xywh(0.0, 0.0, 200.0, 200.0);
6136        rt.layout(bounds, &mut text);
6137
6138        let mut first = RenderList::new();
6139        rt.paint(&mut first, &mut text);
6140        assert_eq!(first.quads().next().unwrap().color, Color::RED);
6141
6142        let mut second = RenderList::new();
6143        rt.paint(&mut second, &mut text);
6144        assert_eq!(rt.layer_cache_stats(), (1, 1));
6145
6146        color.set(Color::BLUE);
6147        rt.tick();
6148        rt.layout(bounds, &mut text);
6149        let mut third = RenderList::new();
6150        rt.paint(&mut third, &mut text);
6151        assert_eq!(third.quads().next().unwrap().color, Color::BLUE);
6152        assert_eq!(rt.layer_cache_stats().1, 2, "signal change misses");
6153    }
6154
6155    #[test]
6156    fn cached_layer_misses_for_another_text_system() {
6157        use crate::views::{Cached, Panel};
6158
6159        let rt = Runtime::new();
6160        rt.mount(Cached::new(
6161            Panel::new()
6162                .background(sequoia_core::Color::RED)
6163                .size(40.0, 30.0),
6164        ));
6165        let bounds = Rect::from_xywh(0.0, 0.0, 200.0, 200.0);
6166        let mut first_text = TextSystem::new();
6167        rt.layout(bounds, &mut first_text);
6168        let mut first = RenderList::new();
6169        rt.paint(&mut first, &mut first_text);
6170        let mut second = RenderList::new();
6171        rt.paint(&mut second, &mut first_text);
6172        assert_eq!(rt.layer_cache_stats(), (1, 1));
6173
6174        let mut second_text = TextSystem::new();
6175        let mut third = RenderList::new();
6176        rt.paint(&mut third, &mut second_text);
6177        assert_eq!(
6178            rt.layer_cache_stats(),
6179            (1, 2),
6180            "different atlas ids cannot replay cached glyph UVs"
6181        );
6182    }
6183
6184    #[test]
6185    fn cached_layer_replays_filtered_subtrees() {
6186        use crate::filter_view::Filtered;
6187        use crate::views::{Cached, Panel};
6188        use sequoia_core::Color;
6189        use sequoia_style::Filter;
6190
6191        let rt = Runtime::new();
6192        rt.mount(Cached::new(Filtered::new(
6193            Panel::new().background(Color::RED).size(40.0, 30.0),
6194            Filter::blur(8.0),
6195        )));
6196        let mut text = TextSystem::new();
6197        let bounds = Rect::from_xywh(0.0, 0.0, 200.0, 200.0);
6198        rt.layout(bounds, &mut text);
6199
6200        let mut first = RenderList::new();
6201        rt.paint(&mut first, &mut text);
6202        assert_eq!(rt.layer_cache_stats(), (0, 1), "first pass fills the cache");
6203        assert_eq!(first.filter_spans().len(), 1);
6204
6205        let mut second = RenderList::new();
6206        rt.paint(&mut second, &mut text);
6207        assert_eq!(
6208            rt.layer_cache_stats(),
6209            (1, 1),
6210            "filtered subtree replays from the layer cache"
6211        );
6212        assert_eq!(
6213            second.filter_spans(),
6214            first.filter_spans(),
6215            "filter span survives append with the same absolute range"
6216        );
6217    }
6218
6219    #[test]
6220    fn cached_layer_replays_until_revision_changes() {
6221        use crate::views::{Cached, Panel};
6222
6223        let rt = Runtime::new();
6224        rt.mount(Cached::new(
6225            Panel::new()
6226                .background(sequoia_core::Color::RED)
6227                .size(40.0, 30.0),
6228        ));
6229        let mut text = TextSystem::new();
6230        rt.layout(Rect::from_xywh(0.0, 0.0, 200.0, 200.0), &mut text);
6231
6232        let mut first = RenderList::new();
6233        rt.paint(&mut first, &mut text);
6234        let (hits, misses) = rt.layer_cache_stats();
6235        assert_eq!((hits, misses), (0, 1), "first pass fills the cache");
6236
6237        let mut second = RenderList::new();
6238        rt.paint(&mut second, &mut text);
6239        assert_eq!(second.len(), first.len(), "cached output is replayed");
6240        assert_eq!(rt.layer_cache_stats(), (1, 1), "second pass hits");
6241
6242        let child = rt.children(rt.root().unwrap())[0];
6243        rt.mark_damage(child, Dirty::PAINT);
6244        let mut third = RenderList::new();
6245        rt.paint(&mut third, &mut text);
6246        assert_eq!(
6247            rt.layer_cache_stats(),
6248            (1, 2),
6249            "child revision bump invalidates the ancestor layer"
6250        );
6251        assert_eq!(third.len(), first.len());
6252    }
6253
6254    #[test]
6255    fn root_fill_stretches_root_to_viewport() {
6256        let rt = Runtime::new();
6257        rt.set_root_fill(true);
6258        rt.mount(crate::Unit);
6259        let mut text = TextSystem::new();
6260        let bounds = Rect::from_xywh(0.0, 0.0, 800.0, 600.0);
6261        rt.layout(bounds, &mut text);
6262        assert_eq!(rt.rect_of(rt.root().unwrap()).unwrap().size, bounds.size);
6263    }
6264
6265    #[test]
6266    fn text_auto_wrap_uses_own_width_line_height_and_max_lines() {
6267        use crate::views::Text;
6268
6269        let mut text = TextSystem::new();
6270        if text.shape("Hg", 16.0).glyph_count() == 0 {
6271            eprintln!("skip: no fonts in environment");
6272            return;
6273        }
6274        let content = "one two three four five six seven eight nine ten";
6275
6276        let mut measure = |view: Text| {
6277            let rt = Runtime::new();
6278            rt.mount(view);
6279            rt.layout(Rect::from_xywh(0.0, 0.0, 400.0, 400.0), &mut text);
6280            let rect = rt.rect_of(rt.root().unwrap()).unwrap();
6281            rect.size
6282        };
6283
6284        let normal = measure(Text::new(content).size(16.0).width(80.0).wrap());
6285        let tall = measure(
6286            Text::new(content)
6287                .size(16.0)
6288                .width(80.0)
6289                .wrap()
6290                .line_height(2.0),
6291        );
6292        let limited = measure(
6293            Text::new(content)
6294                .size(16.0)
6295                .width(80.0)
6296                .wrap()
6297                .line_height(2.0)
6298                .max_lines(2),
6299        );
6300
6301        assert!(normal.width <= 80.5, "width: {}", normal.width);
6302        assert!(normal.height > 16.0, "wrapped: {}", normal.height);
6303        assert!(tall.height > normal.height + 8.0);
6304        assert!(limited.height < tall.height);
6305        assert!(limited.height <= 64.5, "limited: {}", limited.height);
6306    }
6307
6308    #[test]
6309    fn rich_text_auto_wrap_uses_own_width_line_height_and_max_lines() {
6310        use crate::rich_text::RichText;
6311        use sequoia_text::TextSpan;
6312
6313        let mut text = TextSystem::new();
6314        if text.shape("Hg", 16.0).glyph_count() == 0 {
6315            eprintln!("skip: no fonts in environment");
6316            return;
6317        }
6318        let content = "one two three four five six seven eight nine ten";
6319        let spans = || vec![TextSpan::new(content)];
6320
6321        let mut measure = |view: RichText| {
6322            let rt = Runtime::new();
6323            rt.mount(view);
6324            rt.layout(Rect::from_xywh(0.0, 0.0, 400.0, 400.0), &mut text);
6325            let rect = rt.rect_of(rt.root().unwrap()).unwrap();
6326            rect.size
6327        };
6328
6329        let normal = measure(RichText::new(spans()).size(16.0).width(80.0).wrap());
6330        let tall = measure(
6331            RichText::new(spans())
6332                .size(16.0)
6333                .width(80.0)
6334                .wrap()
6335                .line_height(2.0),
6336        );
6337        let limited = measure(
6338            RichText::new(spans())
6339                .size(16.0)
6340                .width(80.0)
6341                .wrap()
6342                .line_height(2.0)
6343                .max_lines(2),
6344        );
6345
6346        assert!(normal.width <= 80.5, "width: {}", normal.width);
6347        assert!(normal.height > 16.0, "wrapped: {}", normal.height);
6348        assert!(tall.height > normal.height + 8.0);
6349        assert!(limited.height < tall.height);
6350        assert!(limited.height <= 64.5, "limited: {}", limited.height);
6351    }
6352
6353    #[test]
6354    fn row_baseline_aligns_text_first_baselines() {
6355        use crate::views::{Row, Text};
6356
6357        let rt = Runtime::new();
6358        rt.mount(
6359            Row::new()
6360                .align_items(Align::Baseline)
6361                .child(Text::new("Big").size(40.0))
6362                .child(Text::new("small").size(12.0)),
6363        );
6364        let mut text = TextSystem::new();
6365        rt.layout(Rect::from_xywh(0.0, 0.0, 400.0, 200.0), &mut text);
6366        let root = rt.root().unwrap();
6367        let children = rt.children(root);
6368        let big = rt.rect_of(children[0]).unwrap();
6369        let small = rt.rect_of(children[1]).unwrap();
6370        let big_baseline = big.top() + text.shape("Big", 40.0).first_baseline();
6371        let small_baseline = small.top() + text.shape("small", 12.0).first_baseline();
6372        assert!(
6373            (big_baseline - small_baseline).abs() < 0.5,
6374            "baselines: {big_baseline} vs {small_baseline}"
6375        );
6376        assert!(small.top() > big.top(), "smaller text is pushed down");
6377    }
6378
6379    #[test]
6380    fn clock_advances_exactly_in_manual_mode() {
6381        let rt = Runtime::new();
6382        rt.set_manual_clock(true);
6383        rt.advance_for_test(std::time::Duration::from_millis(500));
6384        assert!((rt.elapsed_secs() - 0.5).abs() < 1e-6);
6385        assert!((rt.frame_dt_secs() - 0.5).abs() < 1e-6);
6386
6387        rt.set_animation_speed(2.0);
6388        rt.advance_for_test(std::time::Duration::from_millis(250));
6389        assert!((rt.elapsed_secs() - 1.0).abs() < 1e-6);
6390        assert!((rt.frame_dt_secs() - 0.5).abs() < 1e-6);
6391    }
6392
6393    #[test]
6394    fn reduced_motion_freezes_the_shared_clock() {
6395        let rt = Runtime::new();
6396        rt.set_manual_clock(true);
6397        rt.set_reduced_motion(true);
6398        rt.advance_for_test(std::time::Duration::from_millis(500));
6399        assert_eq!(rt.elapsed_secs(), 0.0);
6400        assert_eq!(rt.frame_dt_secs(), 0.0);
6401    }
6402
6403    #[test]
6404    fn live_anims_are_evicted_with_their_elements() {
6405        let rt = Runtime::new();
6406        rt.mount(crate::Unit);
6407        let root = rt.root().unwrap();
6408        let spec = AnimSpec::FAST;
6409        rt.live_f32(root, Prop::OPACITY, 1.0, Some(&spec), None);
6410        assert_eq!(rt.live_anim_count(), 1);
6411
6412        rt.live_f32(id(9999), Prop::OPACITY, 1.0, Some(&spec), None);
6413        assert_eq!(rt.live_anim_count(), 2);
6414        let mut list = RenderList::default();
6415        let mut text = TextSystem::new();
6416        rt.paint(&mut list, &mut text);
6417        assert_eq!(rt.live_anim_count(), 1, "detached element evicted on paint");
6418
6419        rt.mount(crate::Unit);
6420        assert_eq!(rt.live_anim_count(), 0, "unmount evicts animations");
6421    }
6422
6423    #[test]
6424    fn with_state_allows_reentrant_access() {
6425        let rt = Runtime::new();
6426        let (a, b) = (id(0), id(1));
6427        rt.with_state::<u32, _>(a, |x| *x += 1);
6428        let out = rt.with_state::<u32, _>(a, |x| {
6429            *x += 10;
6430            let inner = rt.with_state::<String, _>(b, |s| {
6431                s.push_str("inner");
6432                s.clone()
6433            });
6434            format!("{x}:{inner}")
6435        });
6436        assert_eq!(out, "11:inner");
6437        assert_eq!(rt.with_state::<u32, _>(a, |x| *x), 11);
6438        assert_eq!(rt.state_count(), 2);
6439        assert!(rt.has_state(a) && rt.has_state(b));
6440    }
6441
6442    #[test]
6443    fn with_state_survives_panic_and_keeps_slot() {
6444        let rt = Runtime::new();
6445        let a = id(0);
6446        rt.with_state::<u32, _>(a, |x| *x = 7);
6447        let hook = std::panic::take_hook();
6448        std::panic::set_hook(Box::new(|_| {}));
6449        let r = std::panic::catch_unwind(AssertUnwindSafe(|| {
6450            rt.with_state::<u32, _>(a, |x| {
6451                *x = 99;
6452                panic!("boom");
6453            });
6454        }));
6455        std::panic::set_hook(hook);
6456        assert!(r.is_err(), "panic went through with_state");
6457        assert_eq!(
6458            rt.with_state::<u32, _>(a, |x| *x),
6459            99,
6460            "the slot is back after the panic"
6461        );
6462        assert_eq!(rt.state_count(), 1);
6463    }
6464}