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