Skip to main content

floem/style/
cx.rs

1//! Style context for computing view styles.
2//!
3//! This module contains the context types used during the style phase:
4//! - [`StyleCx`] - Context for computing and propagating styles through the view tree
5//! - [`InteractionState`] - Captures current user interaction state for style resolution
6//! - [`StyleRecalcChange`] - Graduated change tracking for optimized style propagation
7
8use floem_reactive::Scope;
9use smallvec::SmallVec;
10use understory_box_tree::NodeFlags;
11
12#[cfg(not(target_arch = "wasm32"))]
13use std::time::Instant;
14#[cfg(target_arch = "wasm32")]
15use web_time::Instant;
16
17use crate::{
18    ElementId,
19    animate::{AnimStateKind, RepeatMode},
20    inspector::CaptureState,
21    style::{
22        StyleClassRef,
23        recalc::{StyleReason, StyleReasonFlags},
24        resolve_nested_maps,
25    },
26    view::ViewId,
27    window::state::WindowState,
28};
29
30use super::{Style, StyleProp};
31
32/// The interaction state of a view, used to determine which style selectors apply.
33///
34/// This struct captures the current state of user interaction with a view,
35/// such as whether it's hovered, focused, being clicked, etc. This state is
36/// used during style computation to apply conditional styles like `:hover`,
37/// `:active`, `:focus`, etc.
38#[derive(Default, Debug, Clone, Copy)]
39pub struct InteractionState {
40    /// Whether the pointer is currently over this element.
41    pub is_hovered: bool,
42    /// Whether this element is in a selected state.
43    pub is_selected: bool,
44    /// Whether this element is disabled.
45    pub is_disabled: bool,
46    /// Whether this element has keyboard focus.
47    pub is_focused: bool,
48    /// Whether this element or a descendant currently has keyboard focus.
49    pub is_focus_within: bool,
50    /// Whether the element has been hidden
51    pub is_hidden: bool,
52    /// Whether an element is currently in the "active" state
53    /// active: pointer down and not up with the pointer in the element either by
54    ///   1: remaining in or
55    ///   2: returning into the element
56    /// or keyboard trigger is down.
57    pub is_active: bool,
58    /// Whether dark mode is enabled.
59    pub is_dark_mode: bool,
60    /// Whether a file is being dragged over this element.
61    pub is_file_hover: bool,
62    /// Whether keyboard navigation is active.
63    pub using_keyboard_navigation: bool,
64    /// 1-based child index within parent, if this view has a parent.
65    pub child_index: Option<usize>,
66    /// Number of siblings under this view's parent.
67    pub sibling_count: usize,
68    /// Current window width in px for responsive selector matching.
69    pub window_width: f64,
70}
71
72/// Inherited interaction context that is propagated from parent to children.
73///
74/// These states can be set by parent views and are inherited by children,
75/// allowing parents to control the disabled or selected state of entire subtrees.
76#[derive(Default, Debug, Clone, Copy)]
77pub struct InheritedInteractionCx {
78    /// Whether this view (or an ancestor) is disabled.
79    pub disabled: bool,
80    /// Whether this view (or an ancestor) is selected.
81    pub selected: bool,
82    /// Whether this view (or an ancestor) is hidden.
83    pub hidden: bool,
84}
85
86pub struct StyleCx<'a> {
87    pub window_state: &'a mut WindowState,
88
89    pub(crate) current_view: ViewId,
90
91    /// Inherited properties context from ancestors.
92    /// Contains only properties marked as `inherited` (font-size, color, etc.).
93    ///
94    /// Built from parent's computed_style, this provides the inherited baseline that
95    /// will be merged with this view's combined_style to produce computed_style.
96    pub(crate) inherited: Style,
97
98    /// Class definitions context from ancestors.
99    /// Contains `.class(SomeClass, ...)` nested maps from ancestor views.
100    ///
101    /// Built from parent's combined_style, this provides class definitions that
102    /// descendants can apply when computing their combined_style. Separate from
103    /// inherited to allow independent propagation and caching.
104    pub(crate) class_context: Style,
105
106    /// The resolved style for this view (will become combined_style).
107    /// Computed from base + selectors + classes during style resolution.
108    ///
109    /// This is an intermediate value set during style_view and represents only
110    /// what this view explicitly defines, without inherited properties.
111    pub(crate) direct: Style,
112
113    pub(crate) now: Instant,
114
115    view_interact_state: InteractionState,
116
117    /// The reason this view was marked dirty. Available to `style_pass`
118    /// implementations so views can make informed decisions about how much
119    /// work to do.
120    pub(crate) reason: StyleReason,
121
122    /// Targeted sub-element invalidations. Populated when one or more specific
123    /// `ElementId`s owned by this view need updating without a full view restyle.
124    /// Empty means the full view is being restyled.
125    ///
126    /// `style_pass` implementations should walk this list and update only the
127    /// affected elements when non-empty, skipping the full cascade for untouched
128    /// sub-elements.
129    pub(crate) targeted_elements: SmallVec<[(ElementId, StyleReason); 2]>,
130}
131
132impl<'a> StyleCx<'a> {
133    pub fn new(window_state: &'a mut WindowState, view_id: ViewId, reason: StyleReason) -> Self {
134        // Get the style parent: either custom style_cx_parent or DOM parent
135        let style_parent = view_id
136            .state()
137            .borrow()
138            .style_cx_parent
139            .or_else(|| view_id.parent());
140
141        // Initialize inherited and class contexts separately
142        let (inherited, class_context) = if let Some(parent_id) = style_parent {
143            let parent_state = parent_id.state();
144            let parent_state = parent_state.borrow();
145
146            let inherited_style = parent_state.style_cx.clone();
147            let class_ctx = parent_state.class_cx.clone();
148
149            (inherited_style, class_ctx)
150        } else {
151            (
152                window_state.default_theme_inherited.clone(),
153                window_state.default_theme.clone(),
154            )
155        };
156
157        let view = view_id.view();
158        let view_state = view_id.state();
159
160        let mut reason_for_children = reason.for_children();
161        if let Some(selectors) = reason_for_children.selectors {
162            if selectors.has_responsive() {
163                window_state.mark_descendants_with_responsive_selector_dirty(view_id);
164                reason_for_children.selectors =
165                    Some(selectors.difference(crate::style::StyleSelectors::RESPONSIVE));
166            }
167
168            if reason_for_children
169                .selectors
170                .is_some_and(|s| s.has(crate::style::StyleSelector::Disabled))
171            {
172                window_state.mark_descendants_with_selector_dirty(
173                    view_id,
174                    crate::style::StyleSelector::Disabled,
175                );
176                reason_for_children.selectors = reason_for_children
177                    .selectors
178                    .map(|s| s.set_selector(crate::style::StyleSelector::Disabled, false));
179            }
180
181            if reason_for_children.selectors.is_some_and(|s| s.is_empty()) {
182                reason_for_children.selectors = None;
183                reason_for_children.clear_flag(StyleReasonFlags::SELECTOR);
184            }
185        }
186        if !reason_for_children.is_empty() {
187            for child in view_id.children() {
188                window_state
189                    .mark_style_dirty_with(child.get_element_id(), reason_for_children.clone());
190            }
191        }
192
193        let targeted_elements = reason
194            .targets
195            .iter()
196            .map(|(id, r)| (*id, r.clone()))
197            .collect();
198
199        // ─────────────────────────────────────────────────────────────────────
200        // Phase 1: Clear dirty flags, update view style, propagate to children
201        // ─────────────────────────────────────────────────────────────────────
202
203        {
204            if reason.flags.contains(StyleReasonFlags::VIEW_STYLE)
205                && let Some(view_style) = view.borrow().view_style()
206            {
207                let mut vs = view_state.borrow_mut();
208                let offset = vs.view_style_offset;
209                vs.style.set(offset, view_style);
210            }
211        }
212
213        // ─────────────────────────────────────────────────────────────────────
214        // Phase 2: Build interaction state for selector matching
215        // ─────────────────────────────────────────────────────────────────────
216        let view_interact_state = Self::get_interact_state(window_state, view_id);
217        let now = window_state.frame_start;
218
219        Self {
220            window_state,
221            current_view: view_id,
222            inherited,
223            class_context,
224            direct: Default::default(),
225            now,
226            view_interact_state,
227            reason,
228            targeted_elements,
229        }
230    }
231
232    /// Compute styles for a view with graduated change propagation.
233    ///
234    /// The `change` parameter describes what kind of recalculation is needed,
235    /// enabling optimizations like:
236    /// - Skipping views that don't need recalc
237    /// - Using the inherited-only fast path when only inherited props changed
238    /// - Limiting recalc to immediate children vs entire subtrees
239    ///
240    /// See [`StyleRecalcChange`] for details on the propagation model.
241    pub fn style_view(&mut self) {
242        let view_id = self.current_view;
243        let view = view_id.view();
244        let view_state = view_id.state();
245        let view_class = view.borrow().view_class();
246
247        let (active_selectors, classes) = {
248            let vs = view_state.borrow();
249            let selectors = vs.has_style_selectors;
250
251            // Build the full class list: view's classes + view type class
252            let mut all_classes =
253                Vec::with_capacity(vs.classes.len() + usize::from(view_class.is_some()));
254            all_classes.extend(vs.classes.iter().copied());
255            if let Some(vc) = view_class {
256                all_classes.push(vc);
257            }
258            (selectors, all_classes)
259        };
260
261        if let Some(selectors) = self.reason.selectors {
262            let intersection = active_selectors.map(|s| s.intersection(selectors));
263
264            if intersection.is_some_and(|i| i.is_empty()) {
265                self.reason.clear_flag(StyleReasonFlags::SELECTOR);
266            }
267        }
268
269        if let Some(changed) = &self.reason.classes_changed
270            && !changed.iter().any(|c| classes.contains(c))
271        {
272            self.reason
273                .clear_flag(StyleReasonFlags::CLASS_CONTEXT_CHANGE);
274        }
275
276        if self.reason.is_empty() {
277            return;
278        }
279
280        // ─────────────────────────────────────────────────────────────────────
281        // Phase 4: Resolve combined style
282        // ─────────────────────────────────────────────────────────────────────
283        let did_refresh_style =
284            self.reason.needs_resolve_nested_maps() || self.reason.needs_animation();
285
286        if self.reason.needs_resolve_nested_maps() {
287            use super::cache::StyleCacheKey;
288
289            // Get metadata without cloning the full style
290            let (style_hash, cacheable) = {
291                let mut vs = view_state.borrow_mut();
292                let style_hash = vs.style_content_hash();
293                let cacheable =
294                    vs.style_is_cacheable() && !self.class_context.has_structural_selectors();
295                (style_hash, cacheable)
296            };
297
298            let cache_key = if cacheable {
299                Some(StyleCacheKey::new_from_hash(
300                    style_hash,
301                    &self.view_interact_state,
302                    self.window_state.screen_size_bp,
303                    &classes,
304                    &self.class_context,
305                ))
306            } else {
307                None
308            };
309
310            let cache_hit = cache_key
311                .as_ref()
312                .and_then(|key| self.window_state.style_cache.get(key, &self.inherited));
313
314            if let Some(hit) = cache_hit {
315                // Cache hit — restore all compute_combined() outputs, no Style clone needed
316                let mut vs = view_state.borrow_mut();
317                vs.combined_pre_animation_style = hit.combined_style.clone();
318                vs.combined_style = hit.combined_style;
319                vs.has_style_selectors = hit.has_style_selectors;
320                vs.post_compute_combined_interaction = hit.post_interact;
321                self.view_interact_state.is_hidden |= hit.post_interact.hidden;
322                self.view_interact_state.is_selected |= hit.post_interact.selected;
323                self.view_interact_state.is_disabled |= hit.post_interact.disabled;
324            } else {
325                // Cache miss — compute normally (style() clone happens inside compute_combined)
326                view_state.borrow_mut().compute_combined(
327                    &mut self.view_interact_state,
328                    self.window_state.screen_size_bp,
329                    view_class,
330                    &self.inherited,
331                    &self.class_context,
332                );
333
334                // Insert into cache
335                if let Some(key) = cache_key {
336                    let vs = view_state.borrow();
337                    self.window_state.style_cache.insert(
338                        key,
339                        &vs.combined_style,
340                        vs.has_style_selectors,
341                        vs.post_compute_combined_interaction,
342                        &self.inherited,
343                    );
344                }
345            }
346        } else {
347            // Fast path: nested-map resolution was skipped, so reapply the view-local
348            // interaction state saved from the last combined-style computation.
349            let cached = view_state.borrow().post_compute_combined_interaction;
350            self.view_interact_state.is_hidden |= cached.hidden;
351            self.view_interact_state.is_selected |= cached.selected;
352            self.view_interact_state.is_disabled |= cached.disabled;
353        }
354
355        if self.reason.needs_animation() {
356            let has_active_animation = view_state
357                .borrow_mut()
358                .apply_animations(&mut self.view_interact_state);
359            if has_active_animation {
360                self.window_state
361                    .schedule_style(view_id, StyleReason::animation());
362            }
363        }
364
365        self.window_state
366            .update_selector_interest(view_id, view_state.borrow().has_style_selectors);
367
368        let old_interact_state = {
369            let vs = view_state.borrow();
370            vs.style_interaction_cx
371        };
372
373        let mut need_paint = false;
374        // ─────────────────────────────────────────────────────────────────────
375        // Phase 5: Compute final style and propagate contexts to children
376        // ─────────────────────────────────────────────────────────────────────
377        if did_refresh_style {
378            self.direct = view_state.borrow().combined_style.clone();
379
380            // Capture the inner map pointer before updating so we can detect whether
381            // inherited properties actually changed.
382            let old_inherited_map = view_state.borrow().style_cx.clone();
383            // Propagate inherited properties to children (separate from class context)
384            Style::apply_only_inherited(&mut self.inherited, &self.direct);
385            let inherited_changed = self.inherited.merge_id() != old_inherited_map.merge_id();
386
387            let old_class_context = view_state.borrow().class_cx.clone();
388            Style::apply_only_class_maps(&mut self.class_context, &self.direct);
389            let changed_classes = self.class_context.class_maps_eq(&old_class_context);
390            let class_context_changed = !changed_classes.is_empty();
391
392            // ─────────────────────────────────────────────────────────────────────
393            // Phase 5.5: Propagate changes to children if needed
394            // ─────────────────────────────────────────────────────────────────────
395            if inherited_changed || class_context_changed {
396                for child in view_id.children() {
397                    let element_id = child.get_element_id();
398                    if inherited_changed {
399                        self.window_state
400                            .mark_style_dirty_with(element_id, StyleReason::inherited());
401                    }
402                    if class_context_changed {
403                        self.window_state.mark_style_dirty_with(
404                            element_id,
405                            StyleReason::class_cx(changed_classes.clone()),
406                        );
407                    }
408                }
409            }
410
411            // Compute the final style by merging inherited context with direct style
412            let mut computed_style = self.inherited.clone();
413            computed_style.apply_mut(&self.direct);
414            computed_style = computed_style.with_inherited_context(&self.inherited);
415
416            // ─────────────────────────────────────────────────────────────────────
417            // Phase 6: Update window and view state.
418            // ─────────────────────────────────────────────────────────────────────
419
420            let new_is_fixed = computed_style.builtin().is_fixed();
421            if new_is_fixed {
422                self.window_state.register_fixed_element(view_id);
423            } else {
424                self.window_state.unregister_fixed_element(view_id);
425            }
426
427            {
428                let mut vs = view_state.borrow_mut();
429
430                vs.style_cx = self.inherited.clone();
431                vs.class_cx = self.class_context.clone();
432                vs.computed_style = computed_style;
433
434                vs.style_interaction_cx = InheritedInteractionCx {
435                    disabled: self.view_interact_state.is_disabled,
436                    selected: self.view_interact_state.is_selected,
437                    hidden: self.view_interact_state.is_hidden,
438                };
439            }
440        } else {
441            let mut vs = view_state.borrow_mut();
442            self.direct = vs.combined_style.clone();
443            self.inherited = vs.style_cx.clone();
444            self.class_context = vs.class_cx.clone();
445            vs.style_interaction_cx = InheritedInteractionCx {
446                disabled: self.view_interact_state.is_disabled,
447                selected: self.view_interact_state.is_selected,
448                hidden: self.view_interact_state.is_hidden,
449            };
450        }
451
452        if self.reason.needs_property_extraction() {
453            // ─────────────────────────────────────────────────────────────────────
454            // Phase 7: Extract layout and transform properties
455            // ─────────────────────────────────────────────────────────────────────
456            // We read from the computed_style (which includes animated values) rather than
457            // from the raw styles, so that animations affect layout and visual properties.
458            let mut transitioning = false;
459            {
460                let mut vs = view_state.borrow_mut();
461
462                // Clone the computed style to avoid borrow conflicts with the mutable
463                // borrow needed for the extractors. This includes animated values.
464                let computed = vs.computed_style.clone();
465
466                // Layout properties (padding, margin, size, etc.)
467                vs.layout_props
468                    .read_explicit(&computed, &self.now, &mut transitioning);
469
470                // View style properties (background, border, etc.)
471                need_paint |=
472                    vs.view_style_props
473                        .read_explicit(&computed, &self.now, &mut transitioning);
474
475                // Transform properties (translate, scale, rotation)
476                let mut box_tree_changed = false;
477                box_tree_changed |=
478                    vs.view_transform_props
479                        .read_explicit(&computed, &self.now, &mut transitioning);
480                if box_tree_changed {
481                    view_id.request_box_tree_update_for_view();
482                }
483
484                let old_cursor = vs.style_cursor;
485                if old_cursor != computed.builtin().cursor() {
486                    vs.style_cursor = computed.builtin().cursor();
487                    self.window_state.needs_cursor_resolution = true;
488                }
489            }
490
491            if transitioning {
492                self.window_state
493                    .schedule_style(view_id, StyleReason::transition());
494            }
495        }
496
497        if old_interact_state.hidden != self.view_interact_state.is_hidden {
498            for child in view_id.children() {
499                self.window_state
500                    .mark_style_dirty_with(child.get_element_id(), StyleReason::visibility());
501            }
502            view_id.request_layout();
503        }
504        if old_interact_state.selected != self.view_interact_state.is_selected
505            && !self
506                .reason
507                .selectors
508                .is_some_and(|s| s.has(super::StyleSelector::Selected))
509        {
510            self.window_state
511                .mark_descendants_with_selector_dirty(view_id, super::StyleSelector::Selected);
512        }
513        if old_interact_state.disabled != self.view_interact_state.is_disabled
514            && !self
515                .reason
516                .selectors
517                .is_some_and(|s| s.has(super::StyleSelector::Disabled))
518        {
519            self.window_state
520                .mark_descendants_with_selector_dirty(view_id, super::StyleSelector::Disabled);
521        }
522
523        CaptureState::capture_style(view_id, self, view_state.borrow().computed_style.clone());
524
525        if did_refresh_style || self.reason.has_transition() || self.reason.has_visiblity() {
526            // ─────────────────────────────────────────────────────────────────────
527            // Phase 8: visibility transitions and set taffy style
528            // ─────────────────────────────────────────────────────────────────────
529
530            let parent_set_hidden = {
531                let view_state = view_id.state();
532                let view_state = view_state.borrow();
533                view_state.parent_set_style_interaction.hidden
534            };
535            let display_override = if !parent_set_hidden {
536                let (old_phase, computed_display) = {
537                    let vs = view_state.borrow();
538                    (vs.visibility.phase, vs.combined_style.builtin().display())
539                };
540                let mut phase = old_phase;
541
542                phase.transition(
543                    computed_display,
544                    || {
545                        let count = animations_on_remove(view_id, Scope::current());
546                        view_state.borrow_mut().num_waiting_animations = count;
547                        count > 0
548                    },
549                    || animations_on_create(view_id),
550                    || stop_reset_remove_animations(view_id),
551                    || view_state.borrow().num_waiting_animations,
552                );
553
554                if old_phase != phase {
555                    view_state.borrow_mut().visibility.phase = phase;
556                }
557                phase.get_display_override()
558            } else {
559                // parent set hidden - no transition
560                Some(taffy::Display::None)
561            };
562
563            {
564                // ─────────────────────────────────────────────────────────────────────
565                // Phase 8.1: Get the final hidden state for view queries, layout, etc.
566                // Update taffy style if layout properties changed (must happen after visibility phase override)
567                // ─────────────────────────────────────────────────────────────────────
568                let mut vs = view_state.borrow_mut();
569                let is_hidden_final = self.view_interact_state.is_hidden
570                    || display_override.is_some_and(|d| d == taffy::Display::None);
571                let mut taffy_style = vs.combined_style.to_taffy_style();
572                vs.layout_props.apply_to_taffy_style(&mut taffy_style);
573                if let Some(display_override) = display_override {
574                    taffy_style.display = display_override;
575                }
576
577                if taffy_style != vs.taffy_style {
578                    let taffy_node = vs.layout_id;
579                    vs.taffy_style = taffy_style.clone();
580                    view_id
581                        .taffy()
582                        .borrow_mut()
583                        .set_style(taffy_node, taffy_style.clone())
584                        .unwrap();
585                    if !is_hidden_final {
586                        self.window_state.needs_layout = true;
587                    }
588                }
589                // ─────────────────────────────────────────────────────────────────────
590                // Phase 8.2: Update box tree visiblity dependent props  (must happen after visibility phase override)
591                // ─────────────────────────────────────────────────────────────────────
592                let focus_nav_changed;
593                {
594                    let box_tree = view_id.box_tree();
595                    let element_id = vs.element_id;
596                    let box_tree = &mut box_tree.borrow_mut();
597                    let old_flags = box_tree.flags(element_id.0).unwrap_or(NodeFlags::empty());
598                    let old_focus = box_tree.focus_nav_meta(element_id.0).unwrap_or_default();
599                    let mut flags = NodeFlags::empty();
600                    // need to update this after visibility.
601                    if (vs.computed_style.builtin().pointer_events()
602                        != Some(crate::style::PointerEvents::None))
603                        && !is_hidden_final
604                    {
605                        flags |= NodeFlags::PICKABLE;
606                    }
607                    if !is_hidden_final {
608                        flags |= NodeFlags::VISIBLE;
609                    }
610                    if old_flags != flags {
611                        box_tree.set_flags(element_id.0, flags);
612                    }
613                    let effective_focus = vs.computed_style.builtin().set_focus();
614                    let focusable = effective_focus.is_focusable()
615                        && !is_hidden_final
616                        && !self.view_interact_state.is_disabled;
617                    let keyboard_navigable = effective_focus.allows_keyboard_navigation()
618                        && !is_hidden_final
619                        && !self.view_interact_state.is_disabled;
620                    let mut new_focus = old_focus;
621                    new_focus.set_focusable(focusable);
622                    new_focus.set_keyboard_navigable(keyboard_navigable);
623                    focus_nav_changed = old_focus != new_focus;
624                    if focus_nav_changed {
625                        box_tree.set_focus_nav_meta(element_id.0, new_focus);
626                    }
627
628                    let new_z_index = vs.combined_style.builtin().z_index().unwrap_or(0);
629
630                    // Get old z-index from box tree
631                    let old_z_index = box_tree.z_index(element_id.0).unwrap_or(0);
632                    if old_z_index != new_z_index {
633                        box_tree.set_z_index(element_id.0, new_z_index);
634                    }
635                }
636                if focus_nav_changed {
637                    self.window_state.invalidate_focus_nav_cache();
638                }
639                // ─────────────────────────────────────────────────────────────────────
640                // Phase 8.3: request paint for view style changes if not hidden
641                // ─────────────────────────────────────────────────────────────────────
642                if !is_hidden_final && need_paint {
643                    self.window_state.request_paint(view_id);
644                }
645            }
646        }
647
648        // ─────────────────────────────────────────────────────────────────────
649        // Phase 9: Call view's style_pass
650        // ─────────────────────────────────────────────────────────────────────
651
652        view.borrow_mut().style_pass(self);
653    }
654
655    /// Resolve all nested maps in the base style for the given classes.
656    /// This will use the current style cx as context and get the interaction state for the given element
657    pub fn resolve_nested_maps(
658        &self,
659        base_style: Style,
660        classes: &[StyleClassRef],
661        element_id: impl Into<ElementId>,
662    ) -> Style {
663        let base_style_disabled = base_style.builtin().set_disabled();
664        let base_style_selected = base_style.builtin().set_selected();
665        let base_style_hidden = base_style.builtin().display() == taffy::Display::None;
666        let mut view_interact_state = Self::get_interact_state(self.window_state, element_id);
667        view_interact_state.is_disabled |= base_style_disabled;
668        view_interact_state.is_selected |= base_style_selected;
669        view_interact_state.is_hidden |= base_style_hidden;
670        resolve_nested_maps(
671            base_style,
672            &mut view_interact_state,
673            self.window_state.screen_size_bp,
674            classes,
675            &self.inherited,
676            &self.class_context,
677        )
678        .0
679    }
680
681    /// The base style the base style for this view that will override any inherited properties
682    pub fn resolve_nested_maps_with_state(
683        &self,
684        base_style: Style,
685        classes: &[StyleClassRef],
686        mut interact_state: InteractionState,
687    ) -> Style {
688        resolve_nested_maps(
689            base_style,
690            &mut interact_state,
691            self.window_state.screen_size_bp,
692            classes,
693            &self.inherited,
694            &self.class_context,
695        )
696        .0
697    }
698
699    pub fn now(&self) -> Instant {
700        self.now
701    }
702
703    pub fn current_view(&self) -> ViewId {
704        self.current_view
705    }
706
707    pub fn get_prop<P: StyleProp>(&self, _prop: P) -> Option<P::Type> {
708        self.style().get_prop::<P>()
709    }
710
711    pub fn style(&self) -> Style {
712        self.inherited
713            .clone()
714            .apply(self.direct.clone())
715            .with_inherited_context(&self.inherited)
716    }
717
718    pub fn direct_style(&self) -> &Style {
719        &self.direct
720    }
721
722    pub fn indirect_style(&self) -> &Style {
723        &self.inherited
724    }
725
726    pub fn request_transition(&mut self) {
727        self.request_transition_for(self.current_view.get_element_id());
728    }
729
730    /// Request a transition-driven style update for a specific style target.
731    ///
732    /// This allows transition requests to target sub-elements instead of always
733    /// assuming the owning view element.
734    pub fn request_transition_for(&mut self, target: impl Into<ElementId>) {
735        if !self.view_interact_state.is_hidden {
736            self.window_state
737                .schedule_style_with_target(target.into(), StyleReason::transition());
738        }
739    }
740
741    pub fn get_interact_state(
742        window_state: &WindowState,
743        id: impl Into<crate::ElementId>,
744    ) -> InteractionState {
745        let id: crate::ElementId = id.into();
746        let view_id = id.owning_id();
747        let style_parent = view_id
748            .state()
749            .borrow()
750            .style_cx_parent
751            .or_else(|| view_id.parent());
752        let parent_override = {
753            let view_state = view_id.state();
754            let view_state = view_state.borrow();
755            view_state.parent_set_style_interaction
756        };
757        let parent_cx = style_parent
758            .map(|p| p.state().borrow().style_interaction_cx)
759            .unwrap_or_default();
760        // TODO: use box tree child order instead
761        let (child_index, sibling_count) = if let Some(parent) = style_parent {
762            // When using a custom style parent, the styled view may not be a direct
763            // child of that parent (e.g. list item wrappers). Use the nearest
764            // ancestor that is directly under the style parent for structural index.
765            let indexed_child = parent.with_children(|siblings| {
766                if siblings.contains(&view_id) {
767                    Some(view_id)
768                } else {
769                    let mut cursor = view_id.parent();
770                    while let Some(ancestor) = cursor {
771                        if ancestor.parent() == Some(parent) {
772                            return Some(ancestor);
773                        }
774                        cursor = ancestor.parent();
775                    }
776                    None
777                }
778            });
779
780            parent.with_children(|siblings| {
781                (
782                    indexed_child
783                        .and_then(|id| siblings.iter().position(|child| *child == id))
784                        .map(|i| i + 1),
785                    siblings.len(),
786                )
787            })
788        } else {
789            (None, 0)
790        };
791
792        InteractionState {
793            is_selected: parent_override.selected | parent_cx.selected,
794            is_disabled: parent_override.disabled | parent_cx.disabled,
795            is_hidden: parent_override.hidden | parent_cx.hidden,
796            is_hovered: window_state.is_hovered(id),
797            is_focused: window_state.is_focused(id),
798            is_focus_within: window_state.is_focus_within(id),
799            is_active: window_state.is_active(id),
800            is_dark_mode: window_state.is_dark_mode(),
801            is_file_hover: window_state.is_file_hover(id),
802            using_keyboard_navigation: window_state.keyboard_navigation,
803            child_index,
804            sibling_count,
805            window_width: window_state.root_size.width,
806        }
807    }
808}
809
810// Animation helper functions used by StyleCx::style_view
811
812fn animations_on_remove(id: ViewId, scope: Scope) -> u16 {
813    let mut wait_for = 0;
814    let state = id.state();
815    let mut state = state.borrow_mut();
816    state.num_waiting_animations = 0;
817    let animations = &mut state.animations.stack;
818    let mut request_style = false;
819    for anim in animations {
820        if anim.run_on_remove && !matches!(anim.repeat_mode, RepeatMode::LoopForever) {
821            anim.reverse_mut();
822            request_style = true;
823            wait_for += 1;
824            let trigger = anim.on_visual_complete;
825            scope.create_updater(
826                move || trigger.track(),
827                move |_| {
828                    id.transition_anim_complete();
829                },
830            );
831        }
832    }
833    drop(state);
834    if request_style {
835        id.request_style(StyleReason::animation());
836    }
837
838    id.children()
839        .into_iter()
840        .fold(wait_for, |acc, id| acc + animations_on_remove(id, scope))
841}
842
843fn stop_reset_remove_animations(id: ViewId) {
844    let state = id.state();
845    let mut state = state.borrow_mut();
846    let animations = &mut state.animations.stack;
847    let mut request_style = false;
848    for anim in animations {
849        if anim.run_on_remove
850            && anim.state_kind() == AnimStateKind::PassInProgress
851            && !matches!(anim.repeat_mode, RepeatMode::LoopForever)
852        {
853            anim.start_mut();
854            request_style = true;
855        }
856    }
857    drop(state);
858    if request_style {
859        id.request_style(StyleReason::animation());
860    }
861
862    id.children()
863        .into_iter()
864        .for_each(stop_reset_remove_animations)
865}
866
867fn animations_on_create(id: ViewId) {
868    let state = id.state();
869    let mut state = state.borrow_mut();
870    state.num_waiting_animations = 0;
871    let animations = &mut state.animations.stack;
872    let mut request_style = false;
873    for anim in animations {
874        if anim.run_on_create && !matches!(anim.repeat_mode, RepeatMode::LoopForever) {
875            anim.start_mut();
876            request_style = true;
877        }
878    }
879    drop(state);
880    if request_style {
881        id.request_style(StyleReason::animation());
882    }
883
884    id.children().into_iter().for_each(animations_on_create);
885}