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