floem/
context.rs

1use floem_reactive::Scope;
2use floem_renderer::Renderer as FloemRenderer;
3use floem_renderer::gpu_resources::{GpuResourceError, GpuResources};
4use peniko::kurbo::{Affine, Point, Rect, RoundedRect, Shape, Size, Vec2};
5use smallvec::SmallVec;
6use std::{
7    ops::{Deref, DerefMut},
8    rc::Rc,
9    sync::Arc,
10};
11use ui_events::keyboard::{Key, KeyState, KeyboardEvent, Modifiers, NamedKey};
12use ui_events::pointer::{PointerButton, PointerButtonEvent, PointerEvent, PointerUpdate};
13use winit::window::Window;
14
15#[cfg(not(target_arch = "wasm32"))]
16use std::time::{Duration, Instant};
17#[cfg(target_arch = "wasm32")]
18use web_time::{Duration, Instant};
19
20#[cfg(feature = "crossbeam")]
21use crossbeam::channel::Receiver;
22#[cfg(not(feature = "crossbeam"))]
23use std::sync::mpsc::Receiver;
24
25use taffy::prelude::NodeId;
26use taffy::style::Position;
27
28use crate::animate::{AnimStateKind, RepeatMode};
29use crate::dropped_file::FileDragEvent;
30use crate::easing::{Easing, Linear};
31use crate::menu::Menu;
32use crate::renderer::Renderer;
33use crate::responsive::ScreenSizeBp;
34use crate::style::{
35    Focusable, PointerEvents, PointerEventsProp, PositionProp, StyleClassRef, ZIndex,
36    resolve_nested_maps,
37};
38use crate::view_state::{IsHiddenState, StackingInfo};
39use crate::{
40    action::{exec_after, show_context_menu},
41    event::{Event, EventListener, EventPropagation},
42    id::ViewId,
43    inspector::CaptureState,
44    nav::view_arrow_navigation,
45    style::{Style, StyleProp, StyleSelector},
46    view::{View, paint_bg, paint_border, paint_outline, view_tab_navigation},
47    view_state::ChangeFlags,
48    window_state::WindowState,
49};
50
51pub type EventCallback = dyn FnMut(&Event) -> EventPropagation;
52pub type ResizeCallback = dyn Fn(Rect);
53pub type MenuCallback = dyn Fn() -> Menu;
54
55#[derive(Default)]
56pub(crate) struct ResizeListeners {
57    pub(crate) rect: Rect,
58    pub(crate) callbacks: Vec<Rc<ResizeCallback>>,
59}
60
61/// Listeners for when the view moves to a different position in the window
62#[derive(Default)]
63pub(crate) struct MoveListeners {
64    pub(crate) window_origin: Point,
65    pub(crate) callbacks: Vec<Rc<dyn Fn(Point)>>,
66}
67
68pub(crate) type CleanupListeners = Vec<Rc<dyn Fn()>>;
69
70pub struct DragState {
71    pub(crate) id: ViewId,
72    pub(crate) offset: Vec2,
73    pub(crate) released_at: Option<Instant>,
74    pub(crate) release_location: Option<Point>,
75}
76
77pub(crate) enum FrameUpdate {
78    Style(ViewId),
79    Layout(ViewId),
80    Paint(ViewId),
81}
82
83#[derive(Debug, PartialEq, Eq)]
84pub(crate) enum PointerEventConsumed {
85    Yes,
86    No,
87}
88
89/// Returns children sorted by z-index (paint order).
90/// For event dispatch, iterate in reverse to get top-first order.
91pub(crate) fn children_in_paint_order(parent_id: ViewId) -> Vec<ViewId> {
92    let children = parent_id.children();
93
94    if children.len() <= 1 {
95        return children;
96    }
97
98    // Quick check: if no child has non-zero z-index, return original order.
99    // This avoids SmallVec allocation in the common case where z-index isn't used.
100    let needs_sort = children
101        .iter()
102        .any(|&id| id.state().borrow().stacking_info.effective_z_index != 0);
103
104    if !needs_sort {
105        return children;
106    }
107
108    // Build sortable list with z-index and DOM order.
109    // Use SmallVec to avoid heap allocation for typical child counts (<=8).
110    let mut sortable: SmallVec<[(ViewId, i32, usize); 8]> = children
111        .iter()
112        .enumerate()
113        .map(|(dom_order, &child_id)| {
114            let z_index = child_id.state().borrow().stacking_info.effective_z_index;
115            (child_id, z_index, dom_order)
116        })
117        .collect();
118
119    // Stable sort by z-index (DOM order preserved for equal z-index)
120    sortable.sort_by(|a, b| a.1.cmp(&b.1));
121
122    sortable.into_iter().map(|(id, _, _)| id).collect()
123}
124
125/// A bundle of helper methods to be used by `View::event` handlers
126pub struct EventCx<'a> {
127    pub window_state: &'a mut WindowState,
128}
129
130impl EventCx<'_> {
131    pub fn update_active(&mut self, id: ViewId) {
132        self.window_state.update_active(id);
133    }
134
135    pub fn is_active(&self, id: ViewId) -> bool {
136        self.window_state.is_active(&id)
137    }
138
139    #[allow(unused)]
140    pub(crate) fn update_focus(&mut self, id: ViewId, keyboard_navigation: bool) {
141        self.window_state.update_focus(id, keyboard_navigation);
142    }
143
144    /// Internal method used by Floem. This can be called from parent `View`s to propagate an event to the child `View`.
145    pub(crate) fn unconditional_view_event(
146        &mut self,
147        view_id: ViewId,
148        event: Event,
149        directed: bool,
150    ) -> (EventPropagation, PointerEventConsumed) {
151        if view_id.is_hidden() {
152            // we don't process events for hidden view
153            return (EventPropagation::Continue, PointerEventConsumed::No);
154        }
155        if view_id.is_disabled() && !event.allow_disabled() {
156            // if the view is disabled and the event is not processed
157            // for disabled views
158            return (EventPropagation::Continue, PointerEventConsumed::No);
159        }
160
161        // TODO! Handle file hover
162
163        // offset the event positions if the event has positions
164        // e.g. pointer events, so that the position is relative
165        // to the view, taking into account of the layout location
166        // of the view and the viewport of the view if it's in a scroll.
167        let event = self.offset_event(view_id, event);
168
169        let view = view_id.view();
170        let view_state = view_id.state();
171
172        let disable_default = if let Some(listener) = event.listener() {
173            view_state
174                .borrow()
175                .disable_default_events
176                .contains(&listener)
177        } else {
178            false
179        };
180
181        let is_pointer_none = event.is_pointer()
182            && view_state.borrow().computed_style.get(PointerEventsProp)
183                == Some(PointerEvents::None);
184
185        if !disable_default
186            && !is_pointer_none
187            && view
188                .borrow_mut()
189                .event_before_children(self, &event)
190                .is_processed()
191        {
192            if let Event::Pointer(PointerEvent::Down(PointerButtonEvent { state, .. })) = &event {
193                if view_state.borrow().computed_style.get(Focusable) {
194                    let rect = view_id.get_size().unwrap_or_default().to_rect();
195                    let point = state.logical_point();
196                    let now_focused = rect.contains(point);
197                    if now_focused {
198                        self.window_state.update_focus(view_id, false);
199                    }
200                }
201            }
202            if let Event::Pointer(PointerEvent::Move(_)) = &event {
203                let view_state = view_state.borrow();
204                let style = view_state.combined_style.builtin();
205                if let Some(cursor) = style.cursor() {
206                    if self.window_state.cursor.is_none() {
207                        self.window_state.cursor = Some(cursor);
208                    }
209                }
210            }
211            return (EventPropagation::Stop, PointerEventConsumed::Yes);
212        }
213
214        let mut view_pointer_event_consumed = PointerEventConsumed::No;
215
216        if !directed {
217            let children = children_in_paint_order(view_id);
218            for child in children.into_iter().rev() {
219                if !self.should_send(child, &event) {
220                    continue;
221                }
222                let (event_propagation, pointer_event_consumed) =
223                    self.unconditional_view_event(child, event.clone(), false);
224                if event_propagation.is_processed() {
225                    return (EventPropagation::Stop, PointerEventConsumed::Yes);
226                }
227                if event.is_pointer() && pointer_event_consumed == PointerEventConsumed::Yes {
228                    // if a child's pointer event was consumed because pointer-events: auto
229                    // we don't pass the pointer event the next child
230                    // also, we mark pointer_event_consumed to be yes
231                    // so that it will be bublled up the parent
232                    view_pointer_event_consumed = PointerEventConsumed::Yes;
233                    break;
234                }
235            }
236        }
237
238        if !disable_default
239            && !is_pointer_none
240            && view
241                .borrow_mut()
242                .event_after_children(self, &event)
243                .is_processed()
244        {
245            return (EventPropagation::Stop, PointerEventConsumed::Yes);
246        }
247
248        if is_pointer_none {
249            // if pointer-events: none, we don't handle the pointer event
250            return (EventPropagation::Continue, view_pointer_event_consumed);
251        }
252
253        // CLARIFY: should this be disabled when disable_default?
254        if !disable_default {
255            let popout_menu = || {
256                let bottom_left = {
257                    let layout = view_state.borrow().layout_rect;
258                    Point::new(layout.x0, layout.y1)
259                };
260
261                let popout_menu = view_state.borrow().popout_menu.clone();
262                show_context_menu(popout_menu?(), Some(bottom_left));
263                Some((EventPropagation::Stop, PointerEventConsumed::Yes))
264            };
265
266            match &event {
267                Event::Pointer(PointerEvent::Down(PointerButtonEvent {
268                    pointer,
269                    state,
270                    button,
271                    ..
272                })) => {
273                    self.window_state.clicking.insert(view_id);
274                    let point = state.logical_point();
275                    if pointer.is_primary_pointer()
276                        && button.is_none_or(|b| b == PointerButton::Primary)
277                    {
278                        let rect = view_id.get_size().unwrap_or_default().to_rect();
279                        let on_view = rect.contains(point);
280
281                        if on_view {
282                            if view_state.borrow().computed_style.get(Focusable) {
283                                // if the view can be focused, we update the focus
284                                self.window_state.update_focus(view_id, false);
285                            }
286                            if state.count == 2
287                                && view_state
288                                    .borrow()
289                                    .event_listeners
290                                    .contains_key(&EventListener::DoubleClick)
291                            {
292                                view_state.borrow_mut().last_pointer_down = Some(state.clone());
293                            }
294                            if view_state
295                                .borrow()
296                                .event_listeners
297                                .contains_key(&EventListener::Click)
298                            {
299                                view_state.borrow_mut().last_pointer_down = Some(state.clone());
300                            }
301
302                            #[cfg(target_os = "macos")]
303                            if let Some((ep, pec)) = popout_menu() {
304                                return (ep, pec);
305                            };
306
307                            let bottom_left = {
308                                let layout = view_state.borrow().layout_rect;
309                                Point::new(layout.x0, layout.y1)
310                            };
311                            let popout_menu = view_state.borrow().popout_menu.clone();
312                            if let Some(menu) = popout_menu {
313                                show_context_menu(menu(), Some(bottom_left));
314                                return (EventPropagation::Stop, PointerEventConsumed::Yes);
315                            }
316                            if view_id.can_drag() && self.window_state.drag_start.is_none() {
317                                self.window_state.drag_start = Some((view_id, point));
318                            }
319                        }
320                    } else if button.is_some_and(|b| b == PointerButton::Secondary) {
321                        let rect = view_id.get_size().unwrap_or_default().to_rect();
322                        let on_view = rect.contains(point);
323
324                        if on_view {
325                            if view_state.borrow().computed_style.get(Focusable) {
326                                // if the view can be focused, we update the focus
327                                self.window_state.update_focus(view_id, false);
328                            }
329                            if view_state
330                                .borrow()
331                                .event_listeners
332                                .contains_key(&EventListener::SecondaryClick)
333                            {
334                                view_state.borrow_mut().last_pointer_down = Some(state.clone());
335                            }
336                        }
337                    }
338                }
339                Event::Pointer(PointerEvent::Move(PointerUpdate { current, .. })) => {
340                    let rect = view_id.get_size().unwrap_or_default().to_rect();
341                    if rect.contains(current.logical_point()) {
342                        if self.window_state.is_dragging() {
343                            self.window_state.dragging_over.insert(view_id);
344                            view_id.apply_event(&EventListener::DragOver, &event);
345                        } else {
346                            self.window_state.hovered.insert(view_id);
347                            let view_state = view_state.borrow();
348                            let style = view_state.combined_style.builtin();
349                            if let Some(cursor) = style.cursor() {
350                                if self.window_state.cursor.is_none() {
351                                    self.window_state.cursor = Some(cursor);
352                                }
353                            }
354                        }
355                    }
356                    if view_id.can_drag() {
357                        if let Some((_, drag_start)) = self
358                            .window_state
359                            .drag_start
360                            .as_ref()
361                            .filter(|(drag_id, _)| drag_id == &view_id)
362                        {
363                            let offset = current.logical_point() - *drag_start;
364                            if let Some(dragging) = self
365                                .window_state
366                                .dragging
367                                .as_mut()
368                                .filter(|d| d.id == view_id && d.released_at.is_none())
369                            {
370                                // update the mouse position if the view is dragging and not released
371                                dragging.offset = drag_start.to_vec2();
372                                self.window_state.request_paint(view_id);
373                            } else if offset.x.abs() + offset.y.abs() > 1.0 {
374                                // start dragging when moved 1 px
375                                self.window_state.active = None;
376                                self.window_state.dragging = Some(DragState {
377                                    id: view_id,
378                                    offset: drag_start.to_vec2(),
379                                    released_at: None,
380                                    release_location: None,
381                                });
382                                self.update_active(view_id);
383                                self.window_state.request_paint(view_id);
384                                view_id.apply_event(&EventListener::DragStart, &event);
385                            }
386                        }
387                    }
388                    if view_id
389                        .apply_event(&EventListener::PointerMove, &event)
390                        .is_some_and(|prop| prop.is_processed())
391                    {
392                        return (EventPropagation::Stop, PointerEventConsumed::Yes);
393                    }
394                }
395                Event::Pointer(PointerEvent::Up(PointerButtonEvent {
396                    button,
397                    pointer,
398                    state,
399                })) => {
400                    if pointer.is_primary_pointer()
401                        && button.is_none_or(|b| b == PointerButton::Primary)
402                    {
403                        let rect = view_id.get_size().unwrap_or_default().to_rect();
404                        let on_view = rect.contains(state.logical_point());
405
406                        #[cfg(not(target_os = "macos"))]
407                        if on_view {
408                            if let Some((ep, pec)) = popout_menu() {
409                                return (ep, pec);
410                            };
411                        }
412
413                        if !directed {
414                            if on_view {
415                                if let Some(dragging) = self.window_state.dragging.as_mut() {
416                                    let dragging_id = dragging.id;
417                                    if view_id
418                                        .apply_event(&EventListener::Drop, &event)
419                                        .is_some_and(|prop| prop.is_processed())
420                                    {
421                                        // if the drop is processed, we set dragging to none so that the animation
422                                        // for the dragged view back to its original position isn't played.
423                                        self.window_state.dragging = None;
424                                        self.window_state.request_paint(view_id);
425                                        dragging_id.apply_event(&EventListener::DragEnd, &event);
426                                    }
427                                }
428                            }
429                        } else if let Some(dragging) = self
430                            .window_state
431                            .dragging
432                            .as_mut()
433                            .filter(|d| d.id == view_id)
434                        {
435                            let dragging_id = dragging.id;
436                            dragging.released_at = Some(Instant::now());
437                            dragging.release_location = Some(state.logical_point());
438                            self.window_state.request_paint(view_id);
439                            dragging_id.apply_event(&EventListener::DragEnd, &event);
440                        }
441
442                        let last_pointer_down = view_state.borrow_mut().last_pointer_down.take();
443
444                        let event_listeners = view_state.borrow().event_listeners.clone();
445                        if let Some(handlers) = event_listeners.get(&EventListener::DoubleClick) {
446                            view_state.borrow_mut();
447                            if on_view
448                                && self.window_state.is_clicking(&view_id)
449                                && last_pointer_down
450                                    .as_ref()
451                                    .map(|s| s.count == 2)
452                                    .unwrap_or(false)
453                                && handlers.iter().fold(false, |handled, handler| {
454                                    handled | (handler.borrow_mut())(&event).is_processed()
455                                })
456                            {
457                                return (EventPropagation::Stop, PointerEventConsumed::Yes);
458                            }
459                        }
460
461                        if let Some(handlers) = event_listeners.get(&EventListener::Click) {
462                            if on_view
463                                && self.window_state.is_clicking(&view_id)
464                                && last_pointer_down.is_some()
465                                && handlers.iter().fold(false, |handled, handler| {
466                                    handled | (handler.borrow_mut())(&event).is_processed()
467                                })
468                            {
469                                return (EventPropagation::Stop, PointerEventConsumed::Yes);
470                            }
471                        }
472
473                        if view_id
474                            .apply_event(&EventListener::PointerUp, &event)
475                            .is_some_and(|prop| prop.is_processed())
476                        {
477                            return (EventPropagation::Stop, PointerEventConsumed::Yes);
478                        }
479                    } else if button.is_some_and(|b| b == PointerButton::Secondary) {
480                        let rect = view_id.get_size().unwrap_or_default().to_rect();
481                        let on_view = rect.contains(state.logical_point());
482
483                        let last_pointer_down = view_state.borrow_mut().last_pointer_down.take();
484                        let event_listeners = view_state.borrow().event_listeners.clone();
485                        if let Some(handlers) = event_listeners.get(&EventListener::SecondaryClick)
486                        {
487                            if on_view
488                                && last_pointer_down.is_some()
489                                && handlers.iter().fold(false, |handled, handler| {
490                                    handled | (handler.borrow_mut())(&event).is_processed()
491                                })
492                            {
493                                return (EventPropagation::Stop, PointerEventConsumed::Yes);
494                            }
495                        }
496
497                        let viewport_event_position = {
498                            let layout = view_state.borrow().layout_rect;
499                            Point::new(
500                                layout.x0 + state.logical_point().x,
501                                layout.y0 + state.logical_point().y,
502                            )
503                        };
504                        let context_menu = view_state.borrow().context_menu.clone();
505                        if let Some(menu) = context_menu {
506                            show_context_menu(menu(), Some(viewport_event_position));
507                            return (EventPropagation::Stop, PointerEventConsumed::Yes);
508                        }
509                    }
510                }
511                Event::Key(KeyboardEvent {
512                    state: KeyState::Down,
513                    ..
514                }) => {
515                    if self.window_state.is_focused(&view_id) && event.is_keyboard_trigger() {
516                        view_id.apply_event(&EventListener::Click, &event);
517                    }
518                }
519                Event::WindowResized(_) => {
520                    if view_state.borrow().has_style_selectors.has_responsive() {
521                        view_id.request_style();
522                    }
523                }
524                Event::FileDrag(e @ FileDragEvent::DragMoved { .. }) => {
525                    if let Some(point) = e.logical_point() {
526                        let rect = view_id.get_size().unwrap_or_default().to_rect();
527                        let on_view = rect.contains(point);
528                        if on_view {
529                            self.window_state.file_hovered.insert(view_id);
530                            view_id.request_style();
531                        }
532                    }
533                }
534                _ => (),
535            }
536        }
537
538        if !disable_default {
539            if let Some(listener) = event.listener() {
540                let event_listeners = view_state.borrow().event_listeners.clone();
541                if let Some(handlers) = event_listeners.get(&listener).cloned() {
542                    let should_run = if let Some(pos) = event.point() {
543                        let rect = view_id.get_size().unwrap_or_default().to_rect();
544                        rect.contains(pos)
545                    } else {
546                        true
547                    };
548                    if should_run
549                        && handlers.iter().fold(false, |handled, handler| {
550                            handled | (handler.borrow_mut())(&event).is_processed()
551                        })
552                    {
553                        return (EventPropagation::Stop, PointerEventConsumed::Yes);
554                    }
555                }
556            }
557        }
558
559        (EventPropagation::Continue, PointerEventConsumed::Yes)
560    }
561
562    /// translate a window-positioned event to the local coordinate system of a view
563    pub(crate) fn offset_event(&self, id: ViewId, event: Event) -> Event {
564        let state = id.state();
565        let viewport = state.borrow().viewport;
566        let transform = state.borrow().transform;
567
568        if let Some(layout) = id.get_layout() {
569            event.transform(
570                Affine::translate((
571                    layout.location.x as f64 - viewport.map(|rect| rect.x0).unwrap_or(0.0),
572                    layout.location.y as f64 - viewport.map(|rect| rect.y0).unwrap_or(0.0),
573                )) * transform,
574            )
575        } else {
576            event
577        }
578    }
579
580    /// Used to determine if you should send an event to another view. This is basically a check for pointer events to see if the pointer is inside a child view and to make sure the current view isn't hidden or disabled.
581    /// Usually this is used if you want to propagate an event to a child view
582    pub fn should_send(&mut self, id: ViewId, event: &Event) -> bool {
583        if id.is_hidden() || (id.is_disabled() && !event.allow_disabled()) {
584            return false;
585        }
586
587        let Some(point) = event.point() else {
588            return true;
589        };
590
591        let layout_rect = id.layout_rect();
592        let Some(layout) = id.get_layout() else {
593            return false;
594        };
595
596        // Check if point is within current view's bounds
597        let current_rect = layout_rect.with_origin(Point::new(
598            layout.location.x as f64,
599            layout.location.y as f64,
600        ));
601        // For that, we need to take any style transformations into account
602        let transform = id.state().borrow().transform;
603        let current_rect = transform.transform_rect_bbox(current_rect);
604
605        if !current_rect.contains(point) {
606            return false;
607        }
608
609        true
610    }
611
612    /// Dispatch an event through the view tree with proper state management.
613    ///
614    /// This is the core event processing logic shared between WindowHandle and TestHarness.
615    /// It handles:
616    /// - Scaling the event coordinates by the window scale factor
617    /// - Pre-dispatch state setup (clearing hover/clicking state as needed)
618    /// - Event dispatch to the appropriate views (focus-based, active-based, or unconditional)
619    /// - Post-dispatch state management (hover enter/leave, clicking state, focus changes)
620    ///
621    /// # Arguments
622    /// * `root_id` - The root view ID of the window
623    /// * `main_view_id` - The main content view ID (for keyboard event dispatch)
624    /// * `event` - The event to dispatch
625    ///
626    /// # Returns
627    /// The event propagation result
628    pub(crate) fn dispatch_event(
629        &mut self,
630        root_id: ViewId,
631        main_view_id: ViewId,
632        event: Event,
633    ) -> EventPropagation {
634        // Scale the event coordinates by the window scale factor
635        let event = event.transform(Affine::scale(self.window_state.scale));
636
637        // Handle pointer move: track cursor position and prepare for hover state changes
638        let is_pointer_move = if let Event::Pointer(PointerEvent::Move(pu)) = &event {
639            let pos = pu.current.logical_point();
640            self.window_state.last_cursor_location = pos;
641            Some(pu.pointer)
642        } else {
643            None
644        };
645
646        // On pointer move, save previous hover/dragging state and clear for rebuild
647        let (was_hovered, was_dragging_over) = if is_pointer_move.is_some() {
648            self.window_state.cursor = None;
649            let was_hovered = std::mem::take(&mut self.window_state.hovered);
650            let was_dragging_over = std::mem::take(&mut self.window_state.dragging_over);
651            (Some(was_hovered), Some(was_dragging_over))
652        } else {
653            (None, None)
654        };
655
656        // Track file hover changes
657        let was_file_hovered = if matches!(event, Event::FileDrag(FileDragEvent::DragMoved { .. }))
658            || is_pointer_move.is_some()
659        {
660            if !self.window_state.file_hovered.is_empty() {
661                Some(std::mem::take(&mut self.window_state.file_hovered))
662            } else {
663                None
664            }
665        } else {
666            None
667        };
668
669        // On pointer down, clear clicking state and save focus
670        let is_pointer_down = matches!(&event, Event::Pointer(PointerEvent::Down { .. }));
671        let was_focused = if is_pointer_down {
672            self.window_state.clicking.clear();
673            self.window_state.focus.take()
674        } else {
675            self.window_state.focus
676        };
677
678        // Dispatch the event based on its type and current state
679        if event.needs_focus() {
680            // Keyboard events: send to focused view first, then bubble up
681            let mut processed = false;
682
683            if let Some(id) = self.window_state.focus {
684                processed |= self
685                    .unconditional_view_event(id, event.clone(), true)
686                    .0
687                    .is_processed();
688            }
689
690            if !processed {
691                if let Some(listener) = event.listener() {
692                    processed |= main_view_id
693                        .apply_event(&listener, &event)
694                        .is_some_and(|prop| prop.is_processed());
695                }
696            }
697
698            if !processed {
699                // Handle Tab and arrow key navigation
700                if let Event::Key(KeyboardEvent {
701                    key,
702                    modifiers,
703                    state: KeyState::Down,
704                    ..
705                }) = &event
706                {
707                    if *key == Key::Named(NamedKey::Tab)
708                        && (modifiers.is_empty() || *modifiers == Modifiers::SHIFT)
709                    {
710                        let backwards = modifiers.contains(Modifiers::SHIFT);
711                        view_tab_navigation(root_id, self.window_state, backwards);
712                    } else if *modifiers == Modifiers::ALT {
713                        if let Key::Named(
714                            name @ (NamedKey::ArrowUp
715                            | NamedKey::ArrowDown
716                            | NamedKey::ArrowLeft
717                            | NamedKey::ArrowRight),
718                        ) = key
719                        {
720                            view_arrow_navigation(*name, self.window_state, root_id);
721                        }
722                    }
723                }
724
725                // Handle keyboard trigger end (space/enter key up on focused element)
726                let keyboard_trigger_end = self.window_state.keyboard_navigation
727                    && event.is_keyboard_trigger()
728                    && matches!(
729                        event,
730                        Event::Key(KeyboardEvent {
731                            state: KeyState::Up,
732                            ..
733                        })
734                    );
735                if keyboard_trigger_end {
736                    if let Some(id) = self.window_state.active {
737                        if self
738                            .window_state
739                            .has_style_for_sel(id, StyleSelector::Active)
740                        {
741                            id.request_style_recursive();
742                        }
743                        self.window_state.active = None;
744                    }
745                }
746            }
747        } else if self.window_state.active.is_some() && event.is_pointer() {
748            // Pointer events while dragging: send to active view
749            if self.window_state.is_dragging() {
750                self.unconditional_view_event(root_id, event.clone(), false);
751            }
752
753            let id = self.window_state.active.unwrap();
754
755            {
756                let window_origin = id.state().borrow().window_origin;
757                let layout = id.get_layout().unwrap_or_default();
758                let viewport = id.state().borrow().viewport.unwrap_or_default();
759                let transform = Affine::translate((
760                    window_origin.x - layout.location.x as f64 + viewport.x0,
761                    window_origin.y - layout.location.y as f64 + viewport.y0,
762                ));
763                self.unconditional_view_event(id, event.clone().transform(transform), true);
764            }
765
766            if let Event::Pointer(PointerEvent::Up { .. }) = &event {
767                if self
768                    .window_state
769                    .has_style_for_sel(id, StyleSelector::Active)
770                {
771                    id.request_style_recursive();
772                }
773                self.window_state.active = None;
774            }
775        } else {
776            // Normal event dispatch through view tree
777            self.unconditional_view_event(root_id, event.clone(), false);
778        }
779
780        // Clear drag_start on pointer up
781        if let Event::Pointer(PointerEvent::Up { .. }) = &event {
782            self.window_state.drag_start = None;
783        }
784
785        // Handle hover state changes - send PointerEnter/Leave events
786        if let Some(info) = is_pointer_move {
787            let hovered = &self.window_state.hovered.clone();
788            for id in was_hovered.unwrap().symmetric_difference(hovered) {
789                let view_state = id.state();
790                if view_state.borrow().has_active_animation()
791                    || view_state
792                        .borrow()
793                        .has_style_selectors
794                        .has(StyleSelector::Hover)
795                    || view_state
796                        .borrow()
797                        .has_style_selectors
798                        .has(StyleSelector::Active)
799                {
800                    id.request_style();
801                }
802                if hovered.contains(id) {
803                    id.apply_event(&EventListener::PointerEnter, &event);
804                } else {
805                    self.unconditional_view_event(
806                        *id,
807                        Event::Pointer(PointerEvent::Leave(info)),
808                        true,
809                    );
810                }
811            }
812
813            // Handle drag enter/leave events
814            let dragging_over = &self.window_state.dragging_over.clone();
815            for id in was_dragging_over
816                .unwrap()
817                .symmetric_difference(dragging_over)
818            {
819                if dragging_over.contains(id) {
820                    id.apply_event(&EventListener::DragEnter, &event);
821                } else {
822                    id.apply_event(&EventListener::DragLeave, &event);
823                }
824            }
825        }
826
827        // Handle file hover style changes
828        if let Some(was_file_hovered) = was_file_hovered {
829            for id in was_file_hovered.symmetric_difference(&self.window_state.file_hovered) {
830                id.request_style();
831            }
832        }
833
834        // Handle focus changes
835        if was_focused != self.window_state.focus {
836            self.window_state
837                .focus_changed(was_focused, self.window_state.focus);
838        }
839
840        // Request style updates for clicking views on pointer down
841        if is_pointer_down {
842            for id in self.window_state.clicking.clone() {
843                if self
844                    .window_state
845                    .has_style_for_sel(id, StyleSelector::Active)
846                {
847                    id.request_style_recursive();
848                }
849            }
850        }
851
852        // On pointer up, request style updates and clear clicking state
853        if matches!(&event, Event::Pointer(PointerEvent::Up { .. })) {
854            for id in self.window_state.clicking.clone() {
855                if self
856                    .window_state
857                    .has_style_for_sel(id, StyleSelector::Active)
858                {
859                    id.request_style_recursive();
860                }
861            }
862            self.window_state.clicking.clear();
863        }
864
865        EventPropagation::Continue
866    }
867}
868
869/// The interaction state of a view, used to determine which style selectors apply.
870///
871/// This struct captures the current state of user interaction with a view,
872/// such as whether it's hovered, focused, being clicked, etc. This state is
873/// used during style computation to apply conditional styles like `:hover`,
874/// `:active`, `:focus`, etc.
875#[derive(Default, Debug, Clone, Copy)]
876pub struct InteractionState {
877    /// Whether the pointer is currently over this view.
878    pub is_hovered: bool,
879    /// Whether this view is in a selected state.
880    pub is_selected: bool,
881    /// Whether this view is disabled.
882    pub is_disabled: bool,
883    /// Whether this view is hidden.
884    pub is_hidden: bool,
885    /// Whether this view has keyboard focus.
886    pub is_focused: bool,
887    /// Whether this view is being clicked (pointer down but not yet up).
888    pub is_clicking: bool,
889    /// Whether dark mode is enabled.
890    pub is_dark_mode: bool,
891    /// Whether a file is being dragged over this view.
892    pub is_file_hover: bool,
893    /// Whether keyboard navigation is active.
894    pub using_keyboard_navigation: bool,
895}
896
897#[derive(Debug, Default, Clone, Copy)]
898pub(crate) struct InheritedInteractionCx {
899    pub(crate) disabled: bool,
900    pub(crate) hidden: bool,
901    pub(crate) selected: bool,
902}
903
904pub struct StyleCx<'a> {
905    pub window_state: &'a mut WindowState,
906    pub(crate) current_view: ViewId,
907    /// current is used as context for carrying inherited properties between views
908    pub(crate) current: Style,
909    pub(crate) direct: Style,
910    pub(crate) now: Instant,
911    pub disabled: bool,
912    pub hidden: bool,
913    pub selected: bool,
914}
915impl StyleCx<'_> {
916    fn interaction_cx(&self) -> InheritedInteractionCx {
917        InheritedInteractionCx {
918            disabled: self.disabled,
919            hidden: self.hidden,
920            selected: self.selected,
921        }
922    }
923}
924
925impl<'a> StyleCx<'a> {
926    pub(crate) fn new(
927        window_state: &'a mut WindowState,
928        now: Instant,
929        view_id: ViewId,
930        default_style: impl FnOnce() -> Style,
931    ) -> Self {
932        // Use style_cx_parent if set, otherwise use tree parent
933        let style_parent = view_id
934            .state()
935            .borrow()
936            .style_cx_parent
937            .or_else(|| view_id.parent());
938
939        let parent_cx = if let Some(p) = style_parent {
940            // First check if style_cx is already set
941            if let Some(style_cx) = p.state().borrow().style_cx.clone() {
942                style_cx
943            } else if p.parent().is_some() {
944                // If not set and we're not at root, recursively compute it
945                let mut parent_style_cx = StyleCx::new(window_state, now, p, default_style);
946                parent_style_cx.style_view();
947                p.state()
948                    .borrow()
949                    .style_cx
950                    .clone()
951                    .expect("cached in style_target")
952            } else {
953                // We're at root, set it to default
954                let default = default_style();
955                p.state().borrow_mut().style_cx = Some(default.clone());
956                default
957            }
958        } else {
959            default_style()
960        };
961
962        // Use style_cx_parent for interaction context too, or fall back to tree parent
963        let parent_style_interaction_cx = style_parent
964            .map(|p| {
965                let parent_state = p.state();
966                let parent_state = parent_state.borrow();
967                parent_state.style_interaction_cx
968            })
969            .unwrap_or_default();
970
971        Self {
972            window_state,
973            current_view: view_id,
974            current: parent_cx,
975            direct: Default::default(),
976            now,
977            disabled: parent_style_interaction_cx.disabled,
978            hidden: parent_style_interaction_cx.hidden,
979            selected: parent_style_interaction_cx.selected,
980        }
981    }
982
983    pub fn get_interact_state(&self, id: &ViewId) -> InteractionState {
984        let view_state = id.state();
985        let view_state = view_state.borrow();
986        let icx = view_state.parent_set_style_interaction;
987        InteractionState {
988            is_selected: self.selected | icx.selected,
989            is_disabled: self.disabled | icx.disabled,
990            is_hidden: self.hidden | icx.hidden,
991            is_hovered: self.window_state.is_hovered(id),
992            is_focused: self.window_state.is_focused(id),
993            is_clicking: self.window_state.is_clicking(id),
994            is_dark_mode: self.window_state.is_dark_mode(),
995            is_file_hover: self.window_state.is_file_hover(id),
996            using_keyboard_navigation: self.window_state.keyboard_navigation,
997        }
998    }
999
1000    pub fn style_view(&mut self) {
1001        let view_id = self.current_view;
1002
1003        let view = view_id.view();
1004        let view_state = view_id.state();
1005
1006        {
1007            let mut view_state = view_state.borrow_mut();
1008            if self.window_state.view_style_dirty.contains(&view_id) {
1009                self.window_state.view_style_dirty.remove(&view_id);
1010                if let Some(view_style) = view.borrow().view_style() {
1011                    let offset = view_state.view_style_offset;
1012                    view_state.style.set(offset, view_style);
1013                }
1014            }
1015
1016            // Propagate style requests to children if needed.
1017            if view_state.request_style_recursive {
1018                view_state.request_style_recursive = false;
1019                let children = view_id.children();
1020                for child in children {
1021                    let view_state = child.state();
1022                    let mut state = view_state.borrow_mut();
1023                    state.request_style_recursive = true;
1024                    self.window_state.style_dirty.insert(child);
1025                }
1026            }
1027        }
1028
1029        let view_interact_state = self.get_interact_state(&view_id);
1030        let view_class = view.borrow().view_class();
1031
1032        let (mut new_frame, classes_applied) = self.compute_combined(
1033            view_interact_state,
1034            self.window_state.screen_size_bp,
1035            view_class,
1036            &self.current.clone(),
1037        );
1038
1039        if classes_applied {
1040            let children = view_id.children();
1041            for child in children {
1042                let view_state = child.state();
1043                let mut state = view_state.borrow_mut();
1044                state.request_style_recursive = true;
1045                self.window_state.style_dirty.insert(child);
1046            }
1047        }
1048
1049        self.direct = view_state.borrow().combined_style.clone();
1050        self.current.apply_mut(self.direct.inherited());
1051        let computed_style = self.current.clone().apply(self.direct.clone());
1052        CaptureState::capture_style(view_id, self, computed_style.clone());
1053        view_state.borrow_mut().computed_style = computed_style.clone();
1054        let builtin = computed_style.builtin();
1055        self.hidden |= builtin.set_hidden() | (builtin.display() == taffy::Display::None);
1056        self.disabled |= builtin.set_disabled();
1057        self.selected |= builtin.set_selected();
1058        view_state.borrow_mut().style_cx = Some(self.current.clone());
1059        view_state.borrow_mut().style_interaction_cx = self.interaction_cx();
1060
1061        if builtin.focusable()
1062            && !builtin.set_disabled()
1063            && !builtin.set_hidden()
1064            && builtin.display() != taffy::Display::None
1065        {
1066            self.window_state.focusable.insert(view_id);
1067        } else {
1068            self.window_state.focusable.remove(&view_id);
1069        }
1070
1071        {
1072            let mut view_state = view_state.borrow_mut();
1073            // Extract the relevant layout properties so the content rect can be calculated
1074            // when painting.
1075            view_state.layout_props.read_explicit(
1076                &self.direct,
1077                &self.current,
1078                &self.now,
1079                &mut new_frame,
1080            );
1081
1082            view_state.view_style_props.read_explicit(
1083                &self.direct,
1084                &self.current,
1085                &self.now,
1086                &mut new_frame,
1087            );
1088
1089            if view_state.view_transform_props.read_explicit(
1090                &self.direct,
1091                &self.current,
1092                &self.now,
1093                &mut new_frame,
1094            ) || new_frame
1095            {
1096                self.window_state.schedule_layout(view_id);
1097            }
1098        }
1099
1100        if new_frame {
1101            self.window_state.schedule_style(view_id);
1102        }
1103
1104        // If there's any changes to the Taffy style, request layout.
1105        let layout_style = view_state.borrow().layout_props.to_style();
1106        let taffy_style = self.direct.clone().apply(layout_style).to_taffy_style();
1107        if taffy_style != view_state.borrow().taffy_style {
1108            view_state.borrow_mut().taffy_style = taffy_style;
1109            self.window_state.schedule_layout(view_id);
1110        }
1111
1112        view.borrow_mut().style_pass(self);
1113
1114        let mut is_hidden_state = view_state.borrow().is_hidden_state;
1115        let computed_display = view_state.borrow().combined_style.builtin().display();
1116        is_hidden_state.transition(
1117            computed_display,
1118            || {
1119                let count = animations_on_remove(view_id, Scope::current());
1120                view_state.borrow_mut().num_waiting_animations = count;
1121                count > 0
1122            },
1123            || {
1124                animations_on_create(view_id);
1125            },
1126            || {
1127                stop_reset_remove_animations(view_id);
1128            },
1129            || view_state.borrow().num_waiting_animations,
1130        );
1131
1132        view_state.borrow_mut().is_hidden_state = is_hidden_state;
1133        let modified = view_state
1134            .borrow()
1135            .combined_style
1136            .clone()
1137            .apply_opt(is_hidden_state.get_display(), Style::display);
1138
1139        view_state.borrow_mut().combined_style = modified;
1140
1141        let size = view_id.layout_rect().size();
1142        let transform = view_state.borrow().view_transform_props.affine(size);
1143        view_state.borrow_mut().transform = transform;
1144
1145        // Compute stacking context info
1146        // A view creates a stacking context if it has:
1147        // - Any z-index value (including 0, since None means "auto" in web terms)
1148        // - position: absolute
1149        // - Any non-identity transform
1150        let z_index = view_state.borrow().combined_style.get(ZIndex);
1151        let position = view_state.borrow().combined_style.get(PositionProp);
1152        let has_transform = transform != Affine::IDENTITY;
1153
1154        let creates_context = z_index.is_some() || position == Position::Absolute || has_transform; // or has clip set in box tree
1155
1156        view_state.borrow_mut().stacking_info = StackingInfo {
1157            creates_context,
1158            effective_z_index: z_index.unwrap_or(0),
1159        };
1160    }
1161
1162    /// the first returned bool is new_frame. the second is classes_applied
1163    /// Returns `true` if a new frame is requested.
1164    ///
1165    // The context has the nested maps of classes and inherited properties
1166    fn compute_combined(
1167        &mut self,
1168        interact_state: InteractionState,
1169        screen_size_bp: ScreenSizeBp,
1170        view_class: Option<StyleClassRef>,
1171        context: &Style,
1172    ) -> (bool, bool) {
1173        let mut new_frame = false;
1174
1175        // Build the initial combined style
1176        let mut combined_style = Style::new();
1177
1178        let mut classes: SmallVec<[_; 4]> = SmallVec::new();
1179
1180        // Apply view class if provided
1181        if let Some(view_class) = view_class {
1182            classes.insert(0, view_class);
1183        }
1184
1185        let state = self.current_view.state();
1186        let mut state = state.borrow_mut();
1187
1188        for class in &state.classes {
1189            classes.push(*class);
1190        }
1191
1192        let mut new_context = context.clone();
1193        let mut new_classes = false;
1194
1195        // Capture selectors BEFORE any resolution.
1196        // This must be done early because resolve_nested_maps removes selector nested maps
1197        // after applying them, which would cause selectors() to miss them.
1198        let self_style = state.style();
1199
1200        let mut selectors = self_style.selectors();
1201
1202        // Also capture selectors from class styles
1203        for class in &classes {
1204            if let Some(class_style) = context.get_nested_map(class.key) {
1205                selectors = selectors.union(class_style.selectors());
1206            }
1207        }
1208
1209        state.has_style_selectors = selectors;
1210
1211        let (resolved_style, classes_applied) = resolve_nested_maps(
1212            combined_style,
1213            &interact_state,
1214            screen_size_bp,
1215            &classes,
1216            &mut new_context,
1217        );
1218        combined_style = resolved_style;
1219        new_classes |= classes_applied;
1220
1221        let self_style = state.style();
1222
1223        combined_style.apply_mut(self_style.clone());
1224
1225        let (resolved_style, classes_applied) = resolve_nested_maps(
1226            combined_style,
1227            &interact_state,
1228            screen_size_bp,
1229            &classes,
1230            &mut new_context,
1231        );
1232        combined_style = resolved_style;
1233        new_classes |= classes_applied;
1234
1235        // Process animations
1236        for animation in state
1237            .animations
1238            .stack
1239            .iter_mut()
1240            .filter(|anim| anim.can_advance() || anim.should_apply_folded())
1241        {
1242            if animation.can_advance() {
1243                new_frame = true;
1244                animation.animate_into(&mut combined_style);
1245                animation.advance();
1246            } else {
1247                animation.apply_folded(&mut combined_style)
1248            }
1249            debug_assert!(!animation.is_idle());
1250        }
1251
1252        // Apply visibility
1253        if interact_state.is_hidden {
1254            combined_style = combined_style.hide();
1255        }
1256
1257        state.combined_style = combined_style;
1258        (new_frame, new_classes)
1259    }
1260
1261    pub fn now(&self) -> Instant {
1262        self.now
1263    }
1264
1265    pub fn get_prop<P: StyleProp>(&self, _prop: P) -> Option<P::Type> {
1266        self.direct
1267            .get_prop::<P>()
1268            .or_else(|| self.current.get_prop::<P>())
1269    }
1270
1271    pub fn style(&self) -> Style {
1272        self.current.clone().apply(self.direct.clone())
1273    }
1274
1275    pub fn direct_style(&self) -> &Style {
1276        &self.direct
1277    }
1278
1279    pub fn indirect_style(&self) -> &Style {
1280        &self.current
1281    }
1282
1283    pub fn request_transition(&mut self) {
1284        let id = self.current_view;
1285        self.window_state.schedule_style(id);
1286    }
1287}
1288
1289pub struct ComputeLayoutCx<'a> {
1290    pub window_state: &'a mut WindowState,
1291    pub(crate) viewport: Rect,
1292    pub(crate) window_origin: Point,
1293    pub(crate) saved_viewports: Vec<Rect>,
1294    pub(crate) saved_window_origins: Vec<Point>,
1295}
1296
1297impl<'a> ComputeLayoutCx<'a> {
1298    pub(crate) fn new(window_state: &'a mut WindowState, viewport: Rect) -> Self {
1299        Self {
1300            window_state,
1301            viewport,
1302            window_origin: Point::ZERO,
1303            saved_viewports: Vec::new(),
1304            saved_window_origins: Vec::new(),
1305        }
1306    }
1307
1308    pub fn window_origin(&self) -> Point {
1309        self.window_origin
1310    }
1311
1312    pub fn save(&mut self) {
1313        self.saved_viewports.push(self.viewport);
1314        self.saved_window_origins.push(self.window_origin);
1315    }
1316
1317    pub fn restore(&mut self) {
1318        self.viewport = self.saved_viewports.pop().unwrap_or_default();
1319        self.window_origin = self.saved_window_origins.pop().unwrap_or_default();
1320    }
1321
1322    pub fn current_viewport(&self) -> Rect {
1323        self.viewport
1324    }
1325
1326    /// Internal method used by Floem. This method derives its calculations based on the [Taffy Node](taffy::tree::NodeId) returned by the `View::layout` method.
1327    ///
1328    /// It's responsible for:
1329    /// - calculating and setting the view's origin (local coordinates and window coordinates)
1330    /// - calculating and setting the view's viewport
1331    /// - invoking any attached `context::ResizeListener`s
1332    ///
1333    /// Returns the bounding rect that encompasses this view and its children
1334    pub fn compute_view_layout(&mut self, id: ViewId) -> Option<Rect> {
1335        let view_state = id.state();
1336
1337        if view_state.borrow().is_hidden_state == IsHiddenState::Hidden {
1338            view_state.borrow_mut().layout_rect = Rect::ZERO;
1339            return None;
1340        }
1341
1342        self.save();
1343
1344        let layout = id.get_layout().unwrap_or_default();
1345        let origin = Point::new(layout.location.x as f64, layout.location.y as f64);
1346        let this_viewport = view_state.borrow().viewport;
1347        let this_viewport_origin = this_viewport.unwrap_or_default().origin().to_vec2();
1348        let size = Size::new(layout.size.width as f64, layout.size.height as f64);
1349        let parent_viewport = self.viewport.with_origin(
1350            Point::new(
1351                self.viewport.x0 - layout.location.x as f64,
1352                self.viewport.y0 - layout.location.y as f64,
1353            ) + this_viewport_origin,
1354        );
1355        self.viewport = parent_viewport.intersect(size.to_rect());
1356        if let Some(this_viewport) = this_viewport {
1357            self.viewport = self.viewport.intersect(this_viewport);
1358        }
1359
1360        let window_origin = origin + self.window_origin.to_vec2() - this_viewport_origin;
1361        self.window_origin = window_origin;
1362        {
1363            view_state.borrow_mut().window_origin = window_origin;
1364        }
1365
1366        {
1367            let view_state = view_state.borrow();
1368            let mut resize_listeners = view_state.resize_listeners.borrow_mut();
1369
1370            let new_rect = size.to_rect().with_origin(origin);
1371            if new_rect != resize_listeners.rect {
1372                resize_listeners.rect = new_rect;
1373
1374                let callbacks = resize_listeners.callbacks.clone();
1375
1376                // explicitly dropping borrows before using callbacks
1377                std::mem::drop(resize_listeners);
1378                std::mem::drop(view_state);
1379
1380                for callback in callbacks {
1381                    (*callback)(new_rect);
1382                }
1383            }
1384        }
1385
1386        {
1387            let view_state = view_state.borrow();
1388            let mut move_listeners = view_state.move_listeners.borrow_mut();
1389
1390            if window_origin != move_listeners.window_origin {
1391                move_listeners.window_origin = window_origin;
1392
1393                let callbacks = move_listeners.callbacks.clone();
1394
1395                // explicitly dropping borrows before using callbacks
1396                std::mem::drop(move_listeners);
1397                std::mem::drop(view_state);
1398
1399                for callback in callbacks {
1400                    (*callback)(window_origin);
1401                }
1402            }
1403        }
1404
1405        let view = id.view();
1406        let child_layout_rect = view.borrow_mut().compute_layout(self);
1407
1408        let layout_rect = size.to_rect().with_origin(self.window_origin);
1409        let layout_rect = if let Some(child_layout_rect) = child_layout_rect {
1410            layout_rect.union(child_layout_rect)
1411        } else {
1412            layout_rect
1413        };
1414
1415        let transform = view_state.borrow().transform;
1416        let layout_rect = transform.transform_rect_bbox(layout_rect);
1417
1418        view_state.borrow_mut().layout_rect = layout_rect;
1419
1420        self.restore();
1421
1422        Some(layout_rect)
1423    }
1424}
1425
1426/// Holds current layout state for given position in the tree.
1427/// You'll use this in the `View::layout` implementation to call `layout_node` on children and to access any font
1428pub struct LayoutCx<'a> {
1429    pub window_state: &'a mut WindowState,
1430}
1431
1432impl<'a> LayoutCx<'a> {
1433    pub(crate) fn new(window_state: &'a mut WindowState) -> Self {
1434        Self { window_state }
1435    }
1436
1437    /// Responsible for invoking the recalculation of style and thus the layout and
1438    /// creating or updating the layout of child nodes within the closure.
1439    ///
1440    /// You should ensure that all children are laid out within the closure and/or whatever
1441    /// other work you need to do to ensure that the layout for the returned nodes is correct.
1442    pub fn layout_node(
1443        &mut self,
1444        id: ViewId,
1445        has_children: bool,
1446        mut children: impl FnMut(&mut LayoutCx) -> Vec<NodeId>,
1447    ) -> NodeId {
1448        let view_state = id.state();
1449        let node = view_state.borrow().node;
1450        if !view_state
1451            .borrow()
1452            .requested_changes
1453            .contains(ChangeFlags::LAYOUT)
1454        {
1455            return node;
1456        }
1457        view_state
1458            .borrow_mut()
1459            .requested_changes
1460            .remove(ChangeFlags::LAYOUT);
1461        let layout_style = view_state.borrow().layout_props.to_style();
1462        let animate_out_display = view_state.borrow().is_hidden_state.get_display();
1463        let style = view_state
1464            .borrow()
1465            .combined_style
1466            .clone()
1467            .apply(layout_style)
1468            .apply_opt(animate_out_display, Style::display)
1469            .to_taffy_style();
1470        let _ = id.taffy().borrow_mut().set_style(node, style);
1471
1472        if has_children {
1473            let nodes = children(self);
1474            let _ = id.taffy().borrow_mut().set_children(node, &nodes);
1475        }
1476
1477        node
1478    }
1479
1480    /// Internal method used by Floem to invoke the user-defined `View::layout` method.
1481    pub fn layout_view(&mut self, view: &mut dyn View) -> NodeId {
1482        view.layout(self)
1483    }
1484}
1485
1486std::thread_local! {
1487    /// Holds the ID of a View being painted very briefly if it is being rendered as
1488    /// a moving drag image.  Since that is a relatively unusual thing to need, it
1489    /// makes more sense to use a thread local for it and avoid cluttering the fields
1490    /// and memory footprint of PaintCx or PaintState or ViewId with a field for it.
1491    /// This is ephemerally set before paint calls that are painting the view in a
1492    /// location other than its natural one for purposes of drag and drop.
1493    static CURRENT_DRAG_PAINTING_ID : std::cell::Cell<Option<ViewId>> = const { std::cell::Cell::new(None) };
1494}
1495
1496/// Information needed to paint a dragged view overlay after the main tree painting.
1497/// This ensures the drag overlay always appears on top of all other content.
1498pub(crate) struct PendingDragPaint {
1499    pub id: ViewId,
1500    pub base_transform: Affine,
1501}
1502
1503pub struct PaintCx<'a> {
1504    pub window_state: &'a mut WindowState,
1505    pub(crate) paint_state: &'a mut PaintState,
1506    pub(crate) transform: Affine,
1507    pub(crate) clip: Option<RoundedRect>,
1508    pub(crate) saved_transforms: Vec<Affine>,
1509    pub(crate) saved_clips: Vec<Option<RoundedRect>>,
1510    /// Pending drag paint info, to be painted after the main tree.
1511    pub(crate) pending_drag_paint: Option<PendingDragPaint>,
1512    pub gpu_resources: Option<GpuResources>,
1513    pub window: Arc<dyn Window>,
1514    #[cfg(feature = "vello")]
1515    pub layer_count: usize,
1516    #[cfg(feature = "vello")]
1517    pub saved_layer_counts: Vec<usize>,
1518}
1519
1520impl PaintCx<'_> {
1521    pub fn save(&mut self) {
1522        self.saved_transforms.push(self.transform);
1523        self.saved_clips.push(self.clip);
1524        #[cfg(feature = "vello")]
1525        self.saved_layer_counts.push(self.layer_count);
1526    }
1527
1528    pub fn restore(&mut self) {
1529        #[cfg(feature = "vello")]
1530        {
1531            let saved_count = self.saved_layer_counts.pop().unwrap_or_default();
1532            while self.layer_count > saved_count {
1533                self.pop_layer();
1534                self.layer_count -= 1;
1535            }
1536        }
1537
1538        self.transform = self.saved_transforms.pop().unwrap_or_default();
1539        self.clip = self.saved_clips.pop().unwrap_or_default();
1540        self.paint_state
1541            .renderer_mut()
1542            .set_transform(self.transform);
1543
1544        #[cfg(not(feature = "vello"))]
1545        {
1546            if let Some(rect) = self.clip {
1547                self.paint_state.renderer_mut().clip(&rect);
1548            } else {
1549                self.paint_state.renderer_mut().clear_clip();
1550            }
1551        }
1552    }
1553
1554    /// Allows a `View` to determine if it is being called in order to
1555    /// paint a *draggable* image of itself during a drag (likely
1556    /// `draggable()` was called on the `View` or `ViewId`) as opposed
1557    /// to a normal paint in order to alter the way it renders itself.
1558    pub fn is_drag_paint(&self, id: ViewId) -> bool {
1559        // This could be an associated function, but it is likely
1560        // a Good Thing to restrict access to cases when the caller actually
1561        // has a PaintCx, and that doesn't make it a breaking change to
1562        // use instance methods in the future.
1563        if let Some(dragging) = CURRENT_DRAG_PAINTING_ID.get() {
1564            return dragging == id;
1565        }
1566        false
1567    }
1568
1569    /// paint the children of this view
1570    pub fn paint_children(&mut self, id: ViewId) {
1571        let children = children_in_paint_order(id);
1572        for child in children {
1573            self.paint_view(child);
1574        }
1575    }
1576
1577    /// The entry point for painting a view. You shouldn't need to implement this yourself. Instead, implement [`View::paint`].
1578    /// It handles the internal work before and after painting [`View::paint`] implementations.
1579    /// It is responsible for
1580    /// - managing hidden status
1581    /// - clipping
1582    /// - painting computed styles like background color, border, font-styles, and z-index and handling painting requirements of drag and drop
1583    pub fn paint_view(&mut self, id: ViewId) {
1584        if id.is_hidden() {
1585            return;
1586        }
1587        let view = id.view();
1588        let view_state = id.state();
1589
1590        self.save();
1591        let size = self.transform(id);
1592        let is_empty = self
1593            .clip
1594            .map(|rect| rect.rect().intersect(size.to_rect()).is_zero_area())
1595            .unwrap_or(false);
1596        if !is_empty {
1597            let view_style_props = view_state.borrow().view_style_props.clone();
1598            let layout_props = view_state.borrow().layout_props.clone();
1599
1600            paint_bg(self, &view_style_props, size);
1601
1602            view.borrow_mut().paint(self);
1603            paint_border(self, &layout_props, &view_style_props, size);
1604            paint_outline(self, &view_style_props, size)
1605        }
1606        // Check if this view is being dragged and needs deferred painting
1607        if let Some(dragging) = self.window_state.dragging.as_ref() {
1608            if dragging.id == id {
1609                // Store the pending drag paint info - actual painting happens after tree traversal
1610                self.pending_drag_paint = Some(PendingDragPaint {
1611                    id,
1612                    base_transform: self.transform,
1613                });
1614            }
1615        }
1616
1617        self.restore();
1618    }
1619
1620    /// Paint the drag overlay after the main tree has been painted.
1621    /// This ensures the dragged view always appears on top of all other content.
1622    pub fn paint_pending_drag(&mut self) {
1623        let Some(pending) = self.pending_drag_paint.take() else {
1624            return;
1625        };
1626
1627        let id = pending.id;
1628        let base_transform = pending.base_transform;
1629
1630        let Some(dragging) = self.window_state.dragging.as_ref() else {
1631            return;
1632        };
1633
1634        if dragging.id != id {
1635            return;
1636        }
1637
1638        let mut drag_set_to_none = false;
1639
1640        let transform = if let Some((released_at, release_location)) =
1641            dragging.released_at.zip(dragging.release_location)
1642        {
1643            let easing = Linear;
1644            const ANIMATION_DURATION_MS: f64 = 300.0;
1645            let elapsed = released_at.elapsed().as_millis() as f64;
1646            let progress = elapsed / ANIMATION_DURATION_MS;
1647
1648            if !(easing.finished(progress)) {
1649                let offset_scale = 1.0 - easing.eval(progress);
1650                let release_offset = release_location.to_vec2() - dragging.offset;
1651
1652                // Schedule next animation frame
1653                exec_after(Duration::from_millis(8), move |_| {
1654                    id.request_paint();
1655                });
1656
1657                Some(base_transform * Affine::translate(release_offset * offset_scale))
1658            } else {
1659                drag_set_to_none = true;
1660                None
1661            }
1662        } else {
1663            // Handle active dragging
1664            let translation = self.window_state.last_cursor_location.to_vec2() - dragging.offset;
1665            Some(base_transform.with_translation(translation))
1666        };
1667
1668        if let Some(transform) = transform {
1669            let view = id.view();
1670            let view_state = id.state();
1671
1672            self.save();
1673            self.transform = transform;
1674            self.paint_state
1675                .renderer_mut()
1676                .set_transform(self.transform);
1677            self.clear_clip();
1678
1679            // Get size from layout
1680            let size = if let Some(layout) = id.get_layout() {
1681                Size::new(layout.size.width as f64, layout.size.height as f64)
1682            } else {
1683                Size::ZERO
1684            };
1685
1686            // Apply styles
1687            let style = view_state.borrow().combined_style.clone();
1688            let mut view_style_props = view_state.borrow().view_style_props.clone();
1689
1690            if let Some(dragging_style) = view_state.borrow().dragging_style.clone() {
1691                let style = style.apply(dragging_style);
1692                let mut _new_frame = false;
1693                view_style_props.read_explicit(&style, &style, &Instant::now(), &mut _new_frame);
1694            }
1695
1696            // Paint with drag styling
1697            let layout_props = view_state.borrow().layout_props.clone();
1698
1699            // Important: If any method early exit points are added in this
1700            // code block, they MUST call CURRENT_DRAG_PAINTING_ID.take() before
1701            // returning.
1702
1703            CURRENT_DRAG_PAINTING_ID.set(Some(id));
1704
1705            paint_bg(self, &view_style_props, size);
1706            view.borrow_mut().paint(self);
1707            paint_border(self, &layout_props, &view_style_props, size);
1708            paint_outline(self, &view_style_props, size);
1709
1710            self.restore();
1711
1712            CURRENT_DRAG_PAINTING_ID.take();
1713        }
1714
1715        if drag_set_to_none {
1716            self.window_state.dragging = None;
1717        }
1718    }
1719
1720    /// Clip the drawing area to the given shape.
1721    pub fn clip(&mut self, shape: &impl Shape) {
1722        #[cfg(feature = "vello")]
1723        {
1724            use peniko::Mix;
1725
1726            self.push_layer(Mix::Normal, 1.0, Affine::IDENTITY, shape);
1727            self.layer_count += 1;
1728            self.clip = Some(shape.bounding_box().to_rounded_rect(0.0));
1729        }
1730
1731        #[cfg(not(feature = "vello"))]
1732        {
1733            let rect = if let Some(rect) = shape.as_rect() {
1734                rect.to_rounded_rect(0.0)
1735            } else if let Some(rect) = shape.as_rounded_rect() {
1736                rect
1737            } else {
1738                let rect = shape.bounding_box();
1739                rect.to_rounded_rect(0.0)
1740            };
1741
1742            let rect = if let Some(existing) = self.clip {
1743                let rect = existing.rect().intersect(rect.rect());
1744                self.paint_state.renderer_mut().clip(&rect);
1745                rect.to_rounded_rect(0.0)
1746            } else {
1747                self.paint_state.renderer_mut().clip(&shape);
1748                rect
1749            };
1750
1751            self.clip = Some(rect);
1752        }
1753    }
1754
1755    /// Remove clipping so the entire window can be rendered to.
1756    pub fn clear_clip(&mut self) {
1757        self.clip = None;
1758        self.paint_state.renderer_mut().clear_clip();
1759    }
1760
1761    pub fn offset(&mut self, offset: (f64, f64)) {
1762        let mut new = self.transform.as_coeffs();
1763        new[4] += offset.0;
1764        new[5] += offset.1;
1765        self.transform = Affine::new(new);
1766        self.paint_state
1767            .renderer_mut()
1768            .set_transform(self.transform);
1769        if let Some(rect) = self.clip.as_mut() {
1770            let raidus = rect.radii();
1771            *rect = rect
1772                .rect()
1773                .with_origin(rect.origin() - Vec2::new(offset.0, offset.1))
1774                .to_rounded_rect(raidus);
1775        }
1776    }
1777
1778    pub fn transform(&mut self, id: ViewId) -> Size {
1779        if let Some(layout) = id.get_layout() {
1780            let offset = layout.location;
1781            self.transform *= Affine::translate(Vec2 {
1782                x: offset.x as f64,
1783                y: offset.y as f64,
1784            });
1785            self.transform *= id.state().borrow().transform;
1786
1787            self.paint_state
1788                .renderer_mut()
1789                .set_transform(self.transform);
1790
1791            if let Some(rect) = self.clip.as_mut() {
1792                let raidus = rect.radii();
1793                *rect = rect
1794                    .rect()
1795                    .with_origin(rect.origin() - Vec2::new(offset.x as f64, offset.y as f64))
1796                    .to_rounded_rect(raidus);
1797            }
1798
1799            Size::new(layout.size.width as f64, layout.size.height as f64)
1800        } else {
1801            Size::ZERO
1802        }
1803    }
1804
1805    pub fn is_focused(&self, id: ViewId) -> bool {
1806        self.window_state.is_focused(&id)
1807    }
1808}
1809
1810// TODO: should this be private?
1811pub enum PaintState {
1812    /// The renderer is not yet initialized. This state is used to wait for the GPU resources to be acquired.
1813    PendingGpuResources {
1814        window: Arc<dyn Window>,
1815        rx: Receiver<Result<(GpuResources, wgpu::Surface<'static>), GpuResourceError>>,
1816        font_embolden: f32,
1817        /// This field holds an instance of `Renderer::Uninitialized` until the GPU resources are acquired,
1818        /// which will be returned in `PaintState::renderer` and `PaintState::renderer_mut`.
1819        /// All calls to renderer methods will be no-ops until the renderer is initialized.
1820        ///
1821        /// Previously, `PaintState::renderer` and `PaintState::renderer_mut` would panic if called when the renderer was uninitialized.
1822        /// However, this turned out to be hard to handle properly and led to panics, especially since the rest of the application code can't control when the renderer is initialized.
1823        renderer: crate::renderer::Renderer,
1824    },
1825    /// The renderer is initialized and ready to paint.
1826    Initialized { renderer: crate::renderer::Renderer },
1827}
1828
1829impl PaintState {
1830    pub fn new_pending(
1831        window: Arc<dyn Window>,
1832        rx: Receiver<Result<(GpuResources, wgpu::Surface<'static>), GpuResourceError>>,
1833        scale: f64,
1834        size: Size,
1835        font_embolden: f32,
1836    ) -> Self {
1837        Self::PendingGpuResources {
1838            window,
1839            rx,
1840            font_embolden,
1841            renderer: Renderer::Uninitialized { scale, size },
1842        }
1843    }
1844
1845    pub fn new(
1846        window: Arc<dyn Window>,
1847        surface: wgpu::Surface<'static>,
1848        gpu_resources: GpuResources,
1849        scale: f64,
1850        size: Size,
1851        font_embolden: f32,
1852    ) -> Self {
1853        let renderer = crate::renderer::Renderer::new(
1854            window.clone(),
1855            gpu_resources,
1856            surface,
1857            scale,
1858            size,
1859            font_embolden,
1860        );
1861        Self::Initialized { renderer }
1862    }
1863
1864    pub(crate) fn renderer(&self) -> &crate::renderer::Renderer {
1865        match self {
1866            PaintState::PendingGpuResources { renderer, .. } => renderer,
1867            PaintState::Initialized { renderer } => renderer,
1868        }
1869    }
1870
1871    pub(crate) fn renderer_mut(&mut self) -> &mut crate::renderer::Renderer {
1872        match self {
1873            PaintState::PendingGpuResources { renderer, .. } => renderer,
1874            PaintState::Initialized { renderer } => renderer,
1875        }
1876    }
1877
1878    pub(crate) fn resize(&mut self, scale: f64, size: Size) {
1879        self.renderer_mut().resize(scale, size);
1880    }
1881
1882    pub(crate) fn set_scale(&mut self, scale: f64) {
1883        self.renderer_mut().set_scale(scale);
1884    }
1885}
1886
1887pub struct UpdateCx<'a> {
1888    pub window_state: &'a mut WindowState,
1889}
1890
1891impl Deref for PaintCx<'_> {
1892    type Target = crate::renderer::Renderer;
1893
1894    fn deref(&self) -> &Self::Target {
1895        self.paint_state.renderer()
1896    }
1897}
1898
1899impl DerefMut for PaintCx<'_> {
1900    fn deref_mut(&mut self) -> &mut Self::Target {
1901        self.paint_state.renderer_mut()
1902    }
1903}
1904
1905fn animations_on_remove(id: ViewId, scope: Scope) -> u16 {
1906    let mut wait_for = 0;
1907    let state = id.state();
1908    let mut state = state.borrow_mut();
1909    state.num_waiting_animations = 0;
1910    let animations = &mut state.animations.stack;
1911    let mut request_style = false;
1912    for anim in animations {
1913        if anim.run_on_remove && !matches!(anim.repeat_mode, RepeatMode::LoopForever) {
1914            anim.reverse_mut();
1915            request_style = true;
1916            wait_for += 1;
1917            let trigger = anim.on_visual_complete;
1918            scope.create_updater(
1919                move || trigger.track(),
1920                move |_| {
1921                    id.transition_anim_complete();
1922                },
1923            );
1924        }
1925    }
1926    drop(state);
1927    if request_style {
1928        id.request_style();
1929    }
1930
1931    id.children()
1932        .into_iter()
1933        .fold(wait_for, |acc, id| acc + animations_on_remove(id, scope))
1934}
1935fn stop_reset_remove_animations(id: ViewId) {
1936    let state = id.state();
1937    let mut state = state.borrow_mut();
1938    let animations = &mut state.animations.stack;
1939    let mut request_style = false;
1940    for anim in animations {
1941        if anim.run_on_remove
1942            && anim.state_kind() == AnimStateKind::PassInProgress
1943            && !matches!(anim.repeat_mode, RepeatMode::LoopForever)
1944        {
1945            anim.start_mut();
1946            request_style = true;
1947        }
1948    }
1949    drop(state);
1950    if request_style {
1951        id.request_style();
1952    }
1953
1954    id.children()
1955        .into_iter()
1956        .for_each(stop_reset_remove_animations)
1957}
1958
1959fn animations_on_create(id: ViewId) {
1960    let state = id.state();
1961    let mut state = state.borrow_mut();
1962    state.num_waiting_animations = 0;
1963    let animations = &mut state.animations.stack;
1964    let mut request_style = false;
1965    for anim in animations {
1966        if anim.run_on_create && !matches!(anim.repeat_mode, RepeatMode::LoopForever) {
1967            anim.start_mut();
1968            request_style = true;
1969        }
1970    }
1971    drop(state);
1972    if request_style {
1973        id.request_style();
1974    }
1975
1976    id.children().into_iter().for_each(animations_on_create);
1977}
1978
1979#[cfg(test)]
1980mod tests {
1981    use super::*;
1982
1983    /// Helper to create a ViewId and set its z-index
1984    fn create_view_with_z_index(z_index: Option<i32>) -> ViewId {
1985        let id = ViewId::new();
1986        // Access state to initialize it
1987        let state = id.state();
1988        state.borrow_mut().stacking_info = StackingInfo {
1989            creates_context: z_index.is_some(),
1990            effective_z_index: z_index.unwrap_or(0),
1991        };
1992        id
1993    }
1994
1995    /// Helper to set up parent with children
1996    fn setup_parent_with_children(children: Vec<ViewId>) -> ViewId {
1997        let parent = ViewId::new();
1998        parent.set_children_ids(children);
1999        parent
2000    }
2001
2002    /// Helper to extract z-indices from sorted children for easier assertion
2003    fn get_z_indices(children: &[ViewId]) -> Vec<i32> {
2004        children
2005            .iter()
2006            .map(|id| id.state().borrow().stacking_info.effective_z_index)
2007            .collect()
2008    }
2009
2010    #[test]
2011    fn test_no_children() {
2012        let parent = ViewId::new();
2013        let result = children_in_paint_order(parent);
2014        assert!(result.is_empty());
2015    }
2016
2017    #[test]
2018    fn test_single_child() {
2019        let child = create_view_with_z_index(Some(5));
2020        let parent = setup_parent_with_children(vec![child]);
2021
2022        let result = children_in_paint_order(parent);
2023        assert_eq!(result.len(), 1);
2024        assert_eq!(result[0], child);
2025    }
2026
2027    #[test]
2028    fn test_children_no_z_index_preserves_dom_order() {
2029        // All children with default z-index (0) should preserve DOM order
2030        let child1 = create_view_with_z_index(None);
2031        let child2 = create_view_with_z_index(None);
2032        let child3 = create_view_with_z_index(None);
2033        let parent = setup_parent_with_children(vec![child1, child2, child3]);
2034
2035        let result = children_in_paint_order(parent);
2036        assert_eq!(result, vec![child1, child2, child3]);
2037    }
2038
2039    #[test]
2040    fn test_basic_z_index_sorting() {
2041        // Children with different z-indices should be sorted ascending
2042        let child_z10 = create_view_with_z_index(Some(10));
2043        let child_z1 = create_view_with_z_index(Some(1));
2044        let child_z5 = create_view_with_z_index(Some(5));
2045        // DOM order: z10, z1, z5
2046        let parent = setup_parent_with_children(vec![child_z10, child_z1, child_z5]);
2047
2048        let result = children_in_paint_order(parent);
2049        // Paint order should be: z1, z5, z10 (ascending)
2050        assert_eq!(get_z_indices(&result), vec![1, 5, 10]);
2051        assert_eq!(result, vec![child_z1, child_z5, child_z10]);
2052    }
2053
2054    #[test]
2055    fn test_negative_z_index() {
2056        // Negative z-index should sort before positive
2057        let child_pos = create_view_with_z_index(Some(1));
2058        let child_neg = create_view_with_z_index(Some(-1));
2059        let child_zero = create_view_with_z_index(Some(0));
2060        // DOM order: pos, neg, zero
2061        let parent = setup_parent_with_children(vec![child_pos, child_neg, child_zero]);
2062
2063        let result = children_in_paint_order(parent);
2064        // Paint order: -1, 0, 1
2065        assert_eq!(get_z_indices(&result), vec![-1, 0, 1]);
2066    }
2067
2068    #[test]
2069    fn test_equal_z_index_preserves_dom_order() {
2070        // Children with same z-index should preserve DOM order (stable sort)
2071        let child1 = create_view_with_z_index(Some(5));
2072        let child2 = create_view_with_z_index(Some(5));
2073        let child3 = create_view_with_z_index(Some(5));
2074        let parent = setup_parent_with_children(vec![child1, child2, child3]);
2075
2076        let result = children_in_paint_order(parent);
2077        // Same z-index, so DOM order preserved
2078        assert_eq!(result, vec![child1, child2, child3]);
2079    }
2080
2081    #[test]
2082    fn test_mixed_z_index_and_default() {
2083        // Mix of explicit z-index and default (None = 0)
2084        let child_default = create_view_with_z_index(None); // effective 0
2085        let child_z5 = create_view_with_z_index(Some(5));
2086        let child_z_neg = create_view_with_z_index(Some(-1));
2087        // DOM order: default, z5, z_neg
2088        let parent = setup_parent_with_children(vec![child_default, child_z5, child_z_neg]);
2089
2090        let result = children_in_paint_order(parent);
2091        // Paint order: -1, 0, 5
2092        assert_eq!(get_z_indices(&result), vec![-1, 0, 5]);
2093    }
2094
2095    #[test]
2096    fn test_optimization_skips_sort_when_all_zero() {
2097        // When all z-indices are 0, sorting should be skipped (optimization)
2098        // This test verifies the result is correct; actual optimization is internal
2099        let child1 = create_view_with_z_index(Some(0));
2100        let child2 = create_view_with_z_index(Some(0));
2101        let child3 = create_view_with_z_index(Some(0));
2102        let parent = setup_parent_with_children(vec![child1, child2, child3]);
2103
2104        let result = children_in_paint_order(parent);
2105        // All zero, DOM order preserved
2106        assert_eq!(result, vec![child1, child2, child3]);
2107    }
2108
2109    #[test]
2110    fn test_large_z_index_values() {
2111        // Test with large z-index values
2112        let child_max = create_view_with_z_index(Some(i32::MAX));
2113        let child_min = create_view_with_z_index(Some(i32::MIN));
2114        let child_zero = create_view_with_z_index(Some(0));
2115        let parent = setup_parent_with_children(vec![child_max, child_min, child_zero]);
2116
2117        let result = children_in_paint_order(parent);
2118        assert_eq!(get_z_indices(&result), vec![i32::MIN, 0, i32::MAX]);
2119    }
2120
2121    #[test]
2122    fn test_event_dispatch_order_is_reverse_of_paint() {
2123        // Event dispatch iterates in reverse, so highest z-index receives events first
2124        let child_z1 = create_view_with_z_index(Some(1));
2125        let child_z10 = create_view_with_z_index(Some(10));
2126        let child_z5 = create_view_with_z_index(Some(5));
2127        let parent = setup_parent_with_children(vec![child_z1, child_z10, child_z5]);
2128
2129        let paint_order = children_in_paint_order(parent);
2130        // Paint order: 1, 5, 10 (ascending)
2131        assert_eq!(get_z_indices(&paint_order), vec![1, 5, 10]);
2132
2133        // Event dispatch order (reverse): 10, 5, 1
2134        let event_order: Vec<_> = paint_order.into_iter().rev().collect();
2135        assert_eq!(get_z_indices(&event_order), vec![10, 5, 1]);
2136    }
2137
2138    #[test]
2139    fn test_many_children_sorting() {
2140        // Test with many children to ensure sorting is stable and correct
2141        let children: Vec<_> = (0..10)
2142            .map(|i| create_view_with_z_index(Some(9 - i))) // z-indices: 9, 8, 7, ..., 0
2143            .collect();
2144        let parent = setup_parent_with_children(children.clone());
2145
2146        let result = children_in_paint_order(parent);
2147        // Should be sorted ascending: 0, 1, 2, ..., 9
2148        let z_indices = get_z_indices(&result);
2149        assert_eq!(z_indices, (0..10).collect::<Vec<_>>());
2150    }
2151}