1use 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#[derive(Default, Debug, Clone, Copy)]
39pub struct InteractionState {
40 pub is_hovered: bool,
42 pub is_selected: bool,
44 pub is_disabled: bool,
46 pub is_focused: bool,
48 pub is_focus_within: bool,
50 pub is_hidden: bool,
52 pub is_active: bool,
58 pub is_dark_mode: bool,
60 pub is_file_hover: bool,
62 pub using_keyboard_navigation: bool,
64 pub child_index: Option<usize>,
66 pub sibling_count: usize,
68 pub window_width: f64,
70}
71
72#[derive(Default, Debug, Clone, Copy)]
77pub struct InheritedInteractionCx {
78 pub disabled: bool,
80 pub selected: bool,
82 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 pub(crate) inherited: Style,
97
98 pub(crate) class_context: Style,
105
106 pub(crate) direct: Style,
112
113 pub(crate) now: Instant,
114
115 view_interact_state: InteractionState,
116
117 pub(crate) reason: StyleReason,
121
122 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 let style_parent = view_id
136 .state()
137 .borrow()
138 .style_cx_parent
139 .or_else(|| view_id.parent());
140
141 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 {
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 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 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 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 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 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 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 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 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 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 if did_refresh_style {
378 self.direct = view_state.borrow().combined_style.clone();
379
380 let old_inherited_map = view_state.borrow().style_cx.clone();
383 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 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 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 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 let mut transitioning = false;
459 {
460 let mut vs = view_state.borrow_mut();
461
462 let computed = vs.computed_style.clone();
465
466 vs.layout_props
468 .read_explicit(&computed, &self.now, &mut transitioning);
469
470 need_paint |=
472 vs.view_style_props
473 .read_explicit(&computed, &self.now, &mut transitioning);
474
475 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 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 Some(taffy::Display::None)
561 };
562
563 {
564 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 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 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 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 if !is_hidden_final && need_paint {
643 self.window_state.request_paint(view_id);
644 }
645 }
646 }
647
648 view.borrow_mut().style_pass(self);
653 }
654
655 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 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 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 let (child_index, sibling_count) = if let Some(parent) = style_parent {
762 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
810fn 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}