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