From d9e78e5f606edb6bfcebb25b02274b1d14b04410 Mon Sep 17 00:00:00 2001 From: Dongdong Zhou Date: Thu, 16 Apr 2026 20:14:23 +0100 Subject: [PATCH 01/85] Decouple style engine --- src/headless.rs | 2 +- src/inspector/mod.rs | 4 +- src/paint/mod.rs | 10 +- src/style/components.rs | 161 ++++++------ src/style/cx.rs | 129 +++++----- src/style/debug_view.rs | 24 ++ src/style/mod.rs | 6 + src/style/sink.rs | 145 +++++++++++ src/style/storage.rs | 76 ++++++ src/style/theme.rs | 66 ++--- src/style/values.rs | 378 ++++++++++++++++------------ src/view/id.rs | 40 +-- src/view/state.rs | 95 ++----- src/views/editor/mod.rs | 11 +- src/views/localization.rs | 14 +- src/views/text_input.rs | 2 +- src/window/handle.rs | 4 +- src/window/state.rs | 18 +- test/tests/context_selectors.rs | 1 + test/tests/with_context_layout.rs | 1 + test/tests/with_context_reactive.rs | 1 + 21 files changed, 736 insertions(+), 452 deletions(-) create mode 100644 src/style/debug_view.rs create mode 100644 src/style/sink.rs create mode 100644 src/style/storage.rs diff --git a/src/headless.rs b/src/headless.rs index a757d067f..befacd4eb 100644 --- a/src/headless.rs +++ b/src/headless.rs @@ -398,7 +398,7 @@ impl HeadlessHarness { /// assert!(bg.is_some(), "Background should be set when :active"); /// ``` pub fn get_computed_style(&self, id: ViewId) -> Style { - id.state().borrow().computed_style.clone() + id.state().borrow().style_storage.computed_style.clone() } /// Trigger a style recalculation pass. diff --git a/src/inspector/mod.rs b/src/inspector/mod.rs index 0e652a000..92c885ea7 100644 --- a/src/inspector/mod.rs +++ b/src/inspector/mod.rs @@ -515,8 +515,8 @@ impl CapturedView { let taffy = id.get_layout().unwrap_or_default(); let view_state = id.state(); let view_state = view_state.borrow(); - let combined_style = view_state.combined_style.clone(); - let focus = view_state.combined_style.builtin().set_focus(); + let combined_style = view_state.style_storage.combined_style.clone(); + let focus = view_state.style_storage.combined_style.builtin().set_focus(); let focused = window_state.focus_state.current_path().last() == Some(&id.get_element_id()); let custom_name = &view_state.debug_name; let view = id.view(); diff --git a/src/paint/mod.rs b/src/paint/mod.rs index d70ae9037..c0971d125 100644 --- a/src/paint/mod.rs +++ b/src/paint/mod.rs @@ -300,17 +300,17 @@ impl GlobalPaintCx<'_> { world_transform, layout_rect_local, clip, - font_size_cx: view_state.borrow().layout_props.font_size_cx(), + font_size_cx: view_state.borrow().style_storage.layout_props.font_size_cx(), }; if !is_post { if element_id.is_view() { let state = view_state.borrow(); - paint_bg(&mut cx, &state.view_style_props, layout_rect); + paint_bg(&mut cx, &state.style_storage.view_style_props, layout_rect); paint_border( &mut cx, - &state.layout_props, - &state.view_style_props, + &state.style_storage.layout_props, + &state.style_storage.view_style_props, layout_rect, ); drop(state); @@ -327,7 +327,7 @@ impl GlobalPaintCx<'_> { view.borrow_mut().post_paint(&mut cx); if element_id.is_view() { let state = view_state.borrow(); - paint_outline(&mut cx, &state.view_style_props, layout_rect); + paint_outline(&mut cx, &state.style_storage.view_style_props, layout_rect); } } } diff --git a/src/style/components.rs b/src/style/components.rs index 522243ef5..7086005b7 100644 --- a/src/style/components.rs +++ b/src/style/components.rs @@ -14,6 +14,7 @@ use crate::unit::{FontSizeCx, Length, LengthAuto}; use crate::view::{IntoView, View}; use crate::views::{ContainerExt, Decorators, Stack, TooltipExt}; +use super::PropDebugView; use super::values::{StrokeWrap, StylePropValue}; /// Pointer event handling mode @@ -231,6 +232,17 @@ impl StylePropValue for Border { self.bottom.content_hash().hash(&mut h); h.finish() } + + fn interpolate(&self, other: &Self, value: f64) -> Option { + Some(Self { + left: self.left.interpolate(&other.left, value)?, + top: self.top.interpolate(&other.top, value)?, + right: self.right.interpolate(&other.right, value)?, + bottom: self.bottom.interpolate(&other.bottom, value)?, + }) + } +} +impl PropDebugView for Border { fn debug_view(&self) -> Option> { let border = self.clone(); let details_view = move || { @@ -258,15 +270,6 @@ impl StylePropValue for Border { }; Some(details_view().into_any()) } - - fn interpolate(&self, other: &Self, value: f64) -> Option { - Some(Self { - left: self.left.interpolate(&other.left, value)?, - top: self.top.interpolate(&other.top, value)?, - right: self.right.interpolate(&other.right, value)?, - bottom: self.bottom.interpolate(&other.bottom, value)?, - }) - } } /// Structure holding border colors for all four sides @@ -338,6 +341,17 @@ impl StylePropValue for BorderColor { self.bottom.content_hash().hash(&mut h); h.finish() } + + fn interpolate(&self, other: &Self, value: f64) -> Option { + Some(Self { + left: self.left.interpolate(&other.left, value)?, + top: self.top.interpolate(&other.top, value)?, + right: self.right.interpolate(&other.right, value)?, + bottom: self.bottom.interpolate(&other.bottom, value)?, + }) + } +} +impl PropDebugView for BorderColor { fn debug_view(&self) -> Option> { let border_color = self.clone(); let details_view = move || { @@ -364,15 +378,6 @@ impl StylePropValue for BorderColor { }; Some(details_view().into_any()) } - - fn interpolate(&self, other: &Self, value: f64) -> Option { - Some(Self { - left: self.left.interpolate(&other.left, value)?, - top: self.top.interpolate(&other.top, value)?, - right: self.right.interpolate(&other.right, value)?, - bottom: self.bottom.interpolate(&other.bottom, value)?, - }) - } } /// Structure holding border radius for all four corners @@ -477,6 +482,17 @@ impl StylePropValue for BorderRadius { self.bottom_right.content_hash().hash(&mut h); h.finish() } + + fn interpolate(&self, other: &Self, value: f64) -> Option { + Some(Self { + top_left: self.top_left.interpolate(&other.top_left, value)?, + top_right: self.top_right.interpolate(&other.top_right, value)?, + bottom_left: self.bottom_left.interpolate(&other.bottom_left, value)?, + bottom_right: self.bottom_right.interpolate(&other.bottom_right, value)?, + }) + } +} +impl PropDebugView for BorderRadius { fn debug_view(&self) -> Option> { let border_radius = *self; let details_view = move || { @@ -503,15 +519,6 @@ impl StylePropValue for BorderRadius { }; Some(details_view().into_any()) } - - fn interpolate(&self, other: &Self, value: f64) -> Option { - Some(Self { - top_left: self.top_left.interpolate(&other.top_left, value)?, - top_right: self.top_right.interpolate(&other.top_right, value)?, - bottom_left: self.bottom_left.interpolate(&other.bottom_left, value)?, - bottom_right: self.bottom_right.interpolate(&other.bottom_right, value)?, - }) - } } /// Structure holding padding values for all four sides @@ -583,6 +590,17 @@ impl StylePropValue for Padding { self.bottom.content_hash().hash(&mut h); h.finish() } + + fn interpolate(&self, other: &Self, value: f64) -> Option { + Some(Self { + left: self.left.interpolate(&other.left, value)?, + top: self.top.interpolate(&other.top, value)?, + right: self.right.interpolate(&other.right, value)?, + bottom: self.bottom.interpolate(&other.bottom, value)?, + }) + } +} +impl PropDebugView for Padding { fn debug_view(&self) -> Option> { let padding = *self; let details_view = move || { @@ -609,15 +627,6 @@ impl StylePropValue for Padding { }; Some(details_view().into_any()) } - - fn interpolate(&self, other: &Self, value: f64) -> Option { - Some(Self { - left: self.left.interpolate(&other.left, value)?, - top: self.top.interpolate(&other.top, value)?, - right: self.right.interpolate(&other.right, value)?, - bottom: self.bottom.interpolate(&other.bottom, value)?, - }) - } } /// Structure holding margin values for all four sides @@ -689,6 +698,17 @@ impl StylePropValue for Margin { self.bottom.content_hash().hash(&mut h); h.finish() } + + fn interpolate(&self, other: &Self, value: f64) -> Option { + Some(Self { + left: self.left.interpolate(&other.left, value)?, + top: self.top.interpolate(&other.top, value)?, + right: self.right.interpolate(&other.right, value)?, + bottom: self.bottom.interpolate(&other.bottom, value)?, + }) + } +} +impl PropDebugView for Margin { fn debug_view(&self) -> Option> { let margin = *self; let details_view = move || { @@ -715,15 +735,6 @@ impl StylePropValue for Margin { }; Some(details_view().into_any()) } - - fn interpolate(&self, other: &Self, value: f64) -> Option { - Some(Self { - left: self.left.interpolate(&other.left, value)?, - top: self.top.interpolate(&other.top, value)?, - right: self.right.interpolate(&other.right, value)?, - bottom: self.bottom.interpolate(&other.bottom, value)?, - }) - } } // Simple StylePropValue implementations for enums @@ -735,6 +746,7 @@ impl StylePropValue for CursorStyle { h.finish() } } +impl PropDebugView for CursorStyle {} impl StylePropValue for TextOverflow { fn content_hash(&self) -> u64 { use std::hash::{Hash, Hasher}; @@ -743,6 +755,7 @@ impl StylePropValue for TextOverflow { h.finish() } } +impl PropDebugView for TextOverflow {} impl StylePropValue for PointerEvents { fn content_hash(&self) -> u64 { use std::hash::{Hash, Hasher}; @@ -751,6 +764,7 @@ impl StylePropValue for PointerEvents { h.finish() } } +impl PropDebugView for PointerEvents {} impl StylePropValue for BoxShadow { fn content_hash(&self) -> u64 { @@ -765,6 +779,35 @@ impl StylePropValue for BoxShadow { self.bottom_offset.content_hash().hash(&mut h); h.finish() } + + fn interpolate(&self, other: &Self, value: f64) -> Option { + Some(Self { + blur_radius: self + .blur_radius + .interpolate(&other.blur_radius, value) + .unwrap(), + color: self.color.interpolate(&other.color, value).unwrap(), + spread: self.spread.interpolate(&other.spread, value).unwrap(), + left_offset: self + .left_offset + .interpolate(&other.left_offset, value) + .unwrap(), + right_offset: self + .right_offset + .interpolate(&other.right_offset, value) + .unwrap(), + top_offset: self + .top_offset + .interpolate(&other.top_offset, value) + .unwrap(), + bottom_offset: self + .bottom_offset + .interpolate(&other.bottom_offset, value) + .unwrap(), + }) + } +} +impl PropDebugView for BoxShadow { fn debug_view(&self) -> Option> { // Create a preview container that shows a visual representation of the shadow let shadow = *self; @@ -821,33 +864,6 @@ impl StylePropValue for BoxShadow { Some(view.into_any()) } - - fn interpolate(&self, other: &Self, value: f64) -> Option { - Some(Self { - blur_radius: self - .blur_radius - .interpolate(&other.blur_radius, value) - .unwrap(), - color: self.color.interpolate(&other.color, value).unwrap(), - spread: self.spread.interpolate(&other.spread, value).unwrap(), - left_offset: self - .left_offset - .interpolate(&other.left_offset, value) - .unwrap(), - right_offset: self - .right_offset - .interpolate(&other.right_offset, value) - .unwrap(), - top_offset: self - .top_offset - .interpolate(&other.top_offset, value) - .unwrap(), - bottom_offset: self - .bottom_offset - .interpolate(&other.bottom_offset, value) - .unwrap(), - }) - } } /// Controls whether and how a view can receive focus. @@ -959,3 +975,4 @@ impl StylePropValue for Focus { h.finish() } } +impl PropDebugView for Focus {} diff --git a/src/style/cx.rs b/src/style/cx.rs index 859fd4820..63eee8d82 100644 --- a/src/style/cx.rs +++ b/src/style/cx.rs @@ -22,6 +22,7 @@ use crate::{ StyleClassRef, recalc::{StyleReason, StyleReasonFlags}, resolve_nested_maps, + sink::StyleSink, }, view::ViewId, window::state::WindowState, @@ -143,14 +144,14 @@ impl<'a> StyleCx<'a> { let parent_state = parent_id.state(); let parent_state = parent_state.borrow(); - let inherited_style = parent_state.style_cx.clone(); - let class_ctx = parent_state.class_cx.clone(); + let inherited_style = parent_state.style_storage.style_cx.clone(); + let class_ctx = parent_state.style_storage.class_cx.clone(); (inherited_style, class_ctx) } else { ( - window_state.default_theme_inherited.clone(), - window_state.default_theme.clone(), + window_state.default_theme_inherited().clone(), + window_state.default_theme_classes().clone(), ) }; @@ -214,7 +215,7 @@ impl<'a> StyleCx<'a> { // Phase 2: Build interaction state for selector matching // ───────────────────────────────────────────────────────────────────── let view_interact_state = Self::get_interact_state(window_state, view_id); - let now = window_state.frame_start; + let now = window_state.frame_start(); Self { window_state, @@ -246,7 +247,7 @@ impl<'a> StyleCx<'a> { let (active_selectors, classes) = { let vs = view_state.borrow(); - let selectors = vs.has_style_selectors; + let selectors = vs.style_storage.has_style_selectors; // Build the full class list: view's classes + view type class let mut all_classes = @@ -299,7 +300,7 @@ impl<'a> StyleCx<'a> { Some(StyleCacheKey::new_from_hash( style_hash, &self.view_interact_state, - self.window_state.screen_size_bp, + self.window_state.screen_size_bp(), &classes, &self.class_context, )) @@ -309,15 +310,15 @@ impl<'a> StyleCx<'a> { let cache_hit = cache_key .as_ref() - .and_then(|key| self.window_state.style_cache.get(key, &self.inherited)); + .and_then(|key| self.window_state.style_cache_mut().get(key, &self.inherited)); if let Some(hit) = cache_hit { // Cache hit — restore all compute_combined() outputs, no Style clone needed let mut vs = view_state.borrow_mut(); - vs.combined_pre_animation_style = hit.combined_style.clone(); - vs.combined_style = hit.combined_style; - vs.has_style_selectors = hit.has_style_selectors; - vs.post_compute_combined_interaction = hit.post_interact; + vs.style_storage.combined_pre_animation_style = hit.combined_style.clone(); + vs.style_storage.combined_style = hit.combined_style; + vs.style_storage.has_style_selectors = hit.has_style_selectors; + vs.style_storage.post_compute_combined_interaction = hit.post_interact; self.view_interact_state.is_hidden |= hit.post_interact.hidden; self.view_interact_state.is_selected |= hit.post_interact.selected; self.view_interact_state.is_disabled |= hit.post_interact.disabled; @@ -325,7 +326,7 @@ impl<'a> StyleCx<'a> { // Cache miss — compute normally (style() clone happens inside compute_combined) view_state.borrow_mut().compute_combined( &mut self.view_interact_state, - self.window_state.screen_size_bp, + self.window_state.screen_size_bp(), view_class, &self.inherited, &self.class_context, @@ -334,11 +335,11 @@ impl<'a> StyleCx<'a> { // Insert into cache if let Some(key) = cache_key { let vs = view_state.borrow(); - self.window_state.style_cache.insert( + self.window_state.style_cache_mut().insert( key, - &vs.combined_style, - vs.has_style_selectors, - vs.post_compute_combined_interaction, + &vs.style_storage.combined_style, + vs.style_storage.has_style_selectors, + vs.style_storage.post_compute_combined_interaction, &self.inherited, ); } @@ -346,7 +347,7 @@ impl<'a> StyleCx<'a> { } else { // Fast path: nested-map resolution was skipped, so reapply the view-local // interaction state saved from the last combined-style computation. - let cached = view_state.borrow().post_compute_combined_interaction; + let cached = view_state.borrow().style_storage.post_compute_combined_interaction; self.view_interact_state.is_hidden |= cached.hidden; self.view_interact_state.is_selected |= cached.selected; self.view_interact_state.is_disabled |= cached.disabled; @@ -363,11 +364,11 @@ impl<'a> StyleCx<'a> { } self.window_state - .update_selector_interest(view_id, view_state.borrow().has_style_selectors); + .update_selector_interest(view_id, view_state.borrow().style_storage.has_style_selectors); let old_interact_state = { let vs = view_state.borrow(); - vs.style_interaction_cx + vs.style_storage.style_interaction_cx }; let mut need_paint = false; @@ -375,16 +376,16 @@ impl<'a> StyleCx<'a> { // Phase 5: Compute final style and propagate contexts to children // ───────────────────────────────────────────────────────────────────── if did_refresh_style { - self.direct = view_state.borrow().combined_style.clone(); + self.direct = view_state.borrow().style_storage.combined_style.clone(); // Capture the inner map pointer before updating so we can detect whether // inherited properties actually changed. - let old_inherited_map = view_state.borrow().style_cx.clone(); + let old_inherited_map = view_state.borrow().style_storage.style_cx.clone(); // Propagate inherited properties to children (separate from class context) Style::apply_only_inherited(&mut self.inherited, &self.direct); let inherited_changed = self.inherited.merge_id() != old_inherited_map.merge_id(); - let old_class_context = view_state.borrow().class_cx.clone(); + let old_class_context = view_state.borrow().style_storage.class_cx.clone(); Style::apply_only_class_maps(&mut self.class_context, &self.direct); let changed_classes = self.class_context.class_maps_eq(&old_class_context); let class_context_changed = !changed_classes.is_empty(); @@ -427,11 +428,11 @@ impl<'a> StyleCx<'a> { { let mut vs = view_state.borrow_mut(); - vs.style_cx = self.inherited.clone(); - vs.class_cx = self.class_context.clone(); - vs.computed_style = computed_style; + vs.style_storage.style_cx = self.inherited.clone(); + vs.style_storage.class_cx = self.class_context.clone(); + vs.style_storage.computed_style = computed_style; - vs.style_interaction_cx = InheritedInteractionCx { + vs.style_storage.style_interaction_cx = InheritedInteractionCx { disabled: self.view_interact_state.is_disabled, selected: self.view_interact_state.is_selected, hidden: self.view_interact_state.is_hidden, @@ -439,10 +440,10 @@ impl<'a> StyleCx<'a> { } } else { let mut vs = view_state.borrow_mut(); - self.direct = vs.combined_style.clone(); - self.inherited = vs.style_cx.clone(); - self.class_context = vs.class_cx.clone(); - vs.style_interaction_cx = InheritedInteractionCx { + self.direct = vs.style_storage.combined_style.clone(); + self.inherited = vs.style_storage.style_cx.clone(); + self.class_context = vs.style_storage.class_cx.clone(); + vs.style_storage.style_interaction_cx = InheritedInteractionCx { disabled: self.view_interact_state.is_disabled, selected: self.view_interact_state.is_selected, hidden: self.view_interact_state.is_hidden, @@ -461,30 +462,30 @@ impl<'a> StyleCx<'a> { // Clone the computed style to avoid borrow conflicts with the mutable // borrow needed for the extractors. This includes animated values. - let computed = vs.computed_style.clone(); + let computed = vs.style_storage.computed_style.clone(); // Layout properties (padding, margin, size, etc.) - vs.layout_props + vs.style_storage.layout_props .read_explicit(&computed, &self.now, &mut transitioning); // View style properties (background, border, etc.) need_paint |= - vs.view_style_props + vs.style_storage.view_style_props .read_explicit(&computed, &self.now, &mut transitioning); // Transform properties (translate, scale, rotation) let mut box_tree_changed = false; box_tree_changed |= - vs.view_transform_props + vs.style_storage.view_transform_props .read_explicit(&computed, &self.now, &mut transitioning); if box_tree_changed { view_id.request_box_tree_update_for_view(); } - let old_cursor = vs.style_cursor; + let old_cursor = vs.style_storage.style_cursor; if old_cursor != computed.builtin().cursor() { - vs.style_cursor = computed.builtin().cursor(); - self.window_state.needs_cursor_resolution = true; + vs.style_storage.style_cursor = computed.builtin().cursor(); + self.window_state.mark_needs_cursor_resolution(); } } @@ -520,7 +521,7 @@ impl<'a> StyleCx<'a> { .mark_descendants_with_selector_dirty(view_id, super::StyleSelector::Disabled); } - CaptureState::capture_style(view_id, self, view_state.borrow().computed_style.clone()); + CaptureState::capture_style(view_id, self, view_state.borrow().style_storage.computed_style.clone()); if did_refresh_style || self.reason.has_transition() || self.reason.has_visiblity() { // ───────────────────────────────────────────────────────────────────── @@ -530,12 +531,12 @@ impl<'a> StyleCx<'a> { let parent_set_hidden = { let view_state = view_id.state(); let view_state = view_state.borrow(); - view_state.parent_set_style_interaction.hidden + view_state.style_storage.parent_set_style_interaction.hidden }; let display_override = if !parent_set_hidden { let (old_phase, computed_display) = { let vs = view_state.borrow(); - (vs.visibility.phase, vs.combined_style.builtin().display()) + (vs.style_storage.visibility.phase, vs.style_storage.combined_style.builtin().display()) }; let mut phase = old_phase; @@ -543,16 +544,16 @@ impl<'a> StyleCx<'a> { computed_display, || { let count = animations_on_remove(view_id, Scope::current()); - view_state.borrow_mut().num_waiting_animations = count; + view_state.borrow_mut().style_storage.num_waiting_animations = count; count > 0 }, || animations_on_create(view_id), || stop_reset_remove_animations(view_id), - || view_state.borrow().num_waiting_animations, + || view_state.borrow().style_storage.num_waiting_animations, ); if old_phase != phase { - view_state.borrow_mut().visibility.phase = phase; + view_state.borrow_mut().style_storage.visibility.phase = phase; } phase.get_display_override() } else { @@ -568,8 +569,8 @@ impl<'a> StyleCx<'a> { let mut vs = view_state.borrow_mut(); let is_hidden_final = self.view_interact_state.is_hidden || display_override.is_some_and(|d| d == taffy::Display::None); - let mut taffy_style = vs.combined_style.to_taffy_style(); - vs.layout_props.apply_to_taffy_style(&mut taffy_style); + let mut taffy_style = vs.style_storage.combined_style.to_taffy_style(); + vs.style_storage.layout_props.apply_to_taffy_style(&mut taffy_style); if let Some(display_override) = display_override { taffy_style.display = display_override; } @@ -583,7 +584,7 @@ impl<'a> StyleCx<'a> { .set_style(taffy_node, taffy_style.clone()) .unwrap(); if !is_hidden_final { - self.window_state.needs_layout = true; + self.window_state.mark_needs_layout(); } } // ───────────────────────────────────────────────────────────────────── @@ -598,7 +599,7 @@ impl<'a> StyleCx<'a> { let old_focus = box_tree.focus_nav_meta(element_id.0).unwrap_or_default(); let mut flags = NodeFlags::empty(); // need to update this after visibility. - if (vs.computed_style.builtin().pointer_events() + if (vs.style_storage.computed_style.builtin().pointer_events() != Some(crate::style::PointerEvents::None)) && !is_hidden_final { @@ -610,7 +611,7 @@ impl<'a> StyleCx<'a> { if old_flags != flags { box_tree.set_flags(element_id.0, flags); } - let effective_focus = vs.computed_style.builtin().set_focus(); + let effective_focus = vs.style_storage.computed_style.builtin().set_focus(); let focusable = effective_focus.is_focusable() && !is_hidden_final && !self.view_interact_state.is_disabled; @@ -625,7 +626,7 @@ impl<'a> StyleCx<'a> { box_tree.set_focus_nav_meta(element_id.0, new_focus); } - let new_z_index = vs.combined_style.builtin().z_index().unwrap_or(0); + let new_z_index = vs.style_storage.combined_style.builtin().z_index().unwrap_or(0); // Get old z-index from box tree let old_z_index = box_tree.z_index(element_id.0).unwrap_or(0); @@ -670,7 +671,7 @@ impl<'a> StyleCx<'a> { resolve_nested_maps( base_style, &mut view_interact_state, - self.window_state.screen_size_bp, + self.window_state.screen_size_bp(), classes, &self.inherited, &self.class_context, @@ -688,7 +689,7 @@ impl<'a> StyleCx<'a> { resolve_nested_maps( base_style, &mut interact_state, - self.window_state.screen_size_bp, + self.window_state.screen_size_bp(), classes, &self.inherited, &self.class_context, @@ -752,10 +753,10 @@ impl<'a> StyleCx<'a> { let parent_override = { let view_state = view_id.state(); let view_state = view_state.borrow(); - view_state.parent_set_style_interaction + view_state.style_storage.parent_set_style_interaction }; let parent_cx = style_parent - .map(|p| p.state().borrow().style_interaction_cx) + .map(|p| p.state().borrow().style_storage.style_interaction_cx) .unwrap_or_default(); // TODO: use box tree child order instead let (child_index, sibling_count) = if let Some(parent) = style_parent { @@ -793,16 +794,16 @@ impl<'a> StyleCx<'a> { is_selected: parent_override.selected | parent_cx.selected, is_disabled: parent_override.disabled | parent_cx.disabled, is_hidden: parent_override.hidden | parent_cx.hidden, - is_hovered: window_state.is_hovered(id), - is_focused: window_state.is_focused(id), - is_focus_within: window_state.is_focus_within(id), - is_active: window_state.is_active(id), - is_dark_mode: window_state.is_dark_mode(), - is_file_hover: window_state.is_file_hover(id), - using_keyboard_navigation: window_state.keyboard_navigation, + is_hovered: StyleSink::is_hovered(window_state, id), + is_focused: StyleSink::is_focused(window_state, id), + is_focus_within: StyleSink::is_focus_within(window_state, id), + is_active: StyleSink::is_active(window_state, id), + is_dark_mode: StyleSink::is_dark_mode(window_state), + is_file_hover: StyleSink::is_file_hover(window_state, id), + using_keyboard_navigation: StyleSink::keyboard_navigation(window_state), child_index, sibling_count, - window_width: window_state.root_size.width, + window_width: StyleSink::root_size_width(window_state), } } } @@ -813,7 +814,7 @@ fn animations_on_remove(id: ViewId, scope: Scope) -> u16 { let mut wait_for = 0; let state = id.state(); let mut state = state.borrow_mut(); - state.num_waiting_animations = 0; + state.style_storage.num_waiting_animations = 0; let animations = &mut state.animations.stack; let mut request_style = false; for anim in animations { @@ -867,7 +868,7 @@ fn stop_reset_remove_animations(id: ViewId) { fn animations_on_create(id: ViewId) { let state = id.state(); let mut state = state.borrow_mut(); - state.num_waiting_animations = 0; + state.style_storage.num_waiting_animations = 0; let animations = &mut state.animations.stack; let mut request_style = false; for anim in animations { diff --git a/src/style/debug_view.rs b/src/style/debug_view.rs new file mode 100644 index 000000000..d43e55d5b --- /dev/null +++ b/src/style/debug_view.rs @@ -0,0 +1,24 @@ +//! A side-trait that types opt into when they can render an inspector +//! preview of their value. Kept separate from `StylePropValue` so the +//! engine's value types don't depend on `crate::view::View`; when +//! `floem-style` is extracted, `PropDebugView` stays in floem proper +//! while the value types and `StylePropValue` move out. + +use crate::view::View; + +pub trait PropDebugView { + fn debug_view(&self) -> Option> { + None + } +} + +/// Shorthand for types that don't provide an inspector preview. +/// Emits `impl PropDebugView for T {}` using the default `None` return. +#[macro_export] +macro_rules! no_debug_view { + ($($t:ty),* $(,)?) => { + $( + impl $crate::style::PropDebugView for $t {} + )* + }; +} diff --git a/src/style/mod.rs b/src/style/mod.rs index e7989fda2..e26ce513d 100644 --- a/src/style/mod.rs +++ b/src/style/mod.rs @@ -166,9 +166,12 @@ mod cache; mod components; mod custom; mod cx; +mod debug_view; mod props; pub mod recalc; mod selectors; +mod sink; +mod storage; #[cfg(test)] mod tests; pub mod theme; @@ -182,6 +185,7 @@ pub use components::{ }; pub use custom::{CustomStylable, CustomStyle}; pub use cx::{InheritedInteractionCx, InteractionState, StyleCx}; +pub use debug_view::PropDebugView; pub use props::{ ExtractorField, StyleClass, StyleClassInfo, StyleClassRef, StyleDebugGroup, StyleDebugGroupInfo, StyleDebugGroupRef, StyleKey, StyleKeyInfo, StyleProp, StylePropInfo, @@ -200,6 +204,8 @@ pub use values::{ pub use cache::{StyleCache, StyleCacheKey}; +pub(crate) use storage::StyleStorage; + pub(crate) use props::{RESPONSIVE_SELECTORS_INFO, STRUCTURAL_SELECTORS_INFO, style_key_selector}; static NEXT_STYLE_MERGE_ID: AtomicU64 = AtomicU64::new(1); diff --git a/src/style/sink.rs b/src/style/sink.rs new file mode 100644 index 000000000..68341fb25 --- /dev/null +++ b/src/style/sink.rs @@ -0,0 +1,145 @@ +//! The interface the style engine uses to talk to the host. +//! +//! `StyleCx` interacts with the host (today: [`WindowState`]) exclusively through +//! this trait. Keeping the engine's outbound surface explicit lets us later +//! generalize `StyleCx` over any sink implementor, which is how a second +//! consumer such as `floem-native` will plug into the same style engine +//! without depending on `WindowState` or `ViewId`. + +use crate::ElementId; +use crate::layout::responsive::ScreenSizeBp; +use crate::style::cache::StyleCache; +use crate::style::recalc::StyleReason; +use crate::style::{Style, StyleSelector, StyleSelectors}; +use crate::view::ViewId; +use crate::window::state::WindowState; + +#[cfg(not(target_arch = "wasm32"))] +use std::time::Instant; +#[cfg(target_arch = "wasm32")] +use web_time::Instant; + +#[allow(dead_code)] // Phase 2 will generalize StyleCx over this trait; until then a +// subset of methods are dispatched via WindowState's inherent impls. +pub(crate) trait StyleSink { + // --- Frame / root state --- + fn frame_start(&self) -> Instant; + fn screen_size_bp(&self) -> ScreenSizeBp; + fn keyboard_navigation(&self) -> bool; + fn root_size_width(&self) -> f64; + fn is_dark_mode(&self) -> bool; + + // --- Theme defaults --- + fn default_theme_classes(&self) -> &Style; + fn default_theme_inherited(&self) -> &Style; + + // --- Style cache --- + fn style_cache_mut(&mut self) -> &mut StyleCache; + + // --- Per-element interaction reads --- + fn is_hovered(&self, id: ElementId) -> bool; + fn is_focused(&self, id: ElementId) -> bool; + fn is_focus_within(&self, id: ElementId) -> bool; + fn is_active(&self, id: ElementId) -> bool; + fn is_file_hover(&self, id: ElementId) -> bool; + + // --- Dirty / schedule / invalidate --- + fn mark_style_dirty_with(&mut self, id: ElementId, reason: StyleReason); + fn schedule_style(&mut self, id: ViewId, reason: StyleReason); + fn schedule_style_with_target(&mut self, target: ElementId, reason: StyleReason); + fn mark_descendants_with_selector_dirty(&mut self, ancestor: ViewId, selector: StyleSelector); + fn mark_descendants_with_responsive_selector_dirty(&mut self, ancestor: ViewId); + fn update_selector_interest(&mut self, id: ViewId, selectors: Option); + + // --- Host side-effects --- + fn register_fixed_element(&mut self, id: ViewId); + fn unregister_fixed_element(&mut self, id: ViewId); + fn invalidate_focus_nav_cache(&mut self); + fn request_paint(&mut self, id: ElementId); + fn mark_needs_cursor_resolution(&mut self); + fn mark_needs_layout(&mut self); +} + +impl StyleSink for WindowState { + fn frame_start(&self) -> Instant { + self.frame_start + } + fn screen_size_bp(&self) -> ScreenSizeBp { + self.screen_size_bp + } + fn keyboard_navigation(&self) -> bool { + self.keyboard_navigation + } + fn root_size_width(&self) -> f64 { + self.root_size.width + } + fn is_dark_mode(&self) -> bool { + WindowState::is_dark_mode(self) + } + + fn default_theme_classes(&self) -> &Style { + &self.default_theme + } + fn default_theme_inherited(&self) -> &Style { + &self.default_theme_inherited + } + + fn style_cache_mut(&mut self) -> &mut StyleCache { + &mut self.style_cache + } + + fn is_hovered(&self, id: ElementId) -> bool { + WindowState::is_hovered(self, id) + } + fn is_focused(&self, id: ElementId) -> bool { + WindowState::is_focused(self, id) + } + fn is_focus_within(&self, id: ElementId) -> bool { + WindowState::is_focus_within(self, id) + } + fn is_active(&self, id: ElementId) -> bool { + WindowState::is_active(self, id) + } + fn is_file_hover(&self, id: ElementId) -> bool { + WindowState::is_file_hover(self, id) + } + + fn mark_style_dirty_with(&mut self, id: ElementId, reason: StyleReason) { + WindowState::mark_style_dirty_with(self, id, reason) + } + fn schedule_style(&mut self, id: ViewId, reason: StyleReason) { + WindowState::schedule_style(self, id, reason) + } + fn schedule_style_with_target(&mut self, target: ElementId, reason: StyleReason) { + WindowState::schedule_style_with_target(self, target, reason) + } + fn mark_descendants_with_selector_dirty(&mut self, ancestor: ViewId, selector: StyleSelector) { + WindowState::mark_descendants_with_selector_dirty(self, ancestor, selector) + } + fn mark_descendants_with_responsive_selector_dirty(&mut self, ancestor: ViewId) { + WindowState::mark_descendants_with_responsive_selector_dirty(self, ancestor) + } + fn update_selector_interest(&mut self, id: ViewId, selectors: Option) { + WindowState::update_selector_interest(self, id, selectors) + } + + fn register_fixed_element(&mut self, id: ViewId) { + WindowState::register_fixed_element(self, id) + } + fn unregister_fixed_element(&mut self, id: ViewId) { + WindowState::unregister_fixed_element(self, id) + } + fn invalidate_focus_nav_cache(&mut self) { + WindowState::invalidate_focus_nav_cache(self) + } + fn request_paint(&mut self, id: ElementId) { + WindowState::request_paint(self, id) + } + + fn mark_needs_cursor_resolution(&mut self) { + self.needs_cursor_resolution = true; + } + fn mark_needs_layout(&mut self) { + self.needs_layout = true; + } +} diff --git a/src/style/storage.rs b/src/style/storage.rs new file mode 100644 index 000000000..c47f7a17b --- /dev/null +++ b/src/style/storage.rs @@ -0,0 +1,76 @@ +//! Per-node storage for the style engine. +//! +//! Fields the style engine reads and writes for each node live here, separate +//! from `ViewState`'s view-layer concerns (event listeners, menus, layout-id +//! bookkeeping, user cursor, etc.). In Phase 2 this becomes the state exposed +//! by the `StyleNode` trait, letting a second consumer such as `floem-native` +//! run the same engine over its own node type. + +use crate::style::{ + CursorStyle, InheritedInteractionCx, LayoutProps, Style, StyleSelectors, TransformProps, +}; +use crate::view::state::{ViewStyleProps, Visibility}; + +/// Engine-owned per-node state produced and consumed by the style pass. +#[derive(Default)] +pub(crate) struct StyleStorage { + pub has_style_selectors: Option, + pub layout_props: LayoutProps, + pub view_style_props: ViewStyleProps, + pub view_transform_props: TransformProps, + /// Pre-animation snapshot of `combined_style`; animations re-derive + /// `combined_style` from this each frame so animated values don't feed back. + pub combined_pre_animation_style: Style, + /// The resolved style for this view (base + selectors + classes). + /// Does NOT include inherited properties from ancestors. + /// + /// Use for style resolution logic (what did this view define?): + /// - Checking if a property is explicitly set on this view + /// - Computing class context propagation to children + /// - Building style cache keys + pub combined_style: Style, + /// The final computed style including inherited properties from ancestors. + /// This is combined_style merged with inherited context (font_size, color, etc.). + /// + /// Use for rendering and layout (what will the user see?): + /// - Layout calculations via prop extractors + /// - Visual properties (background, border, transform) + /// - Anything that affects what gets rendered + /// - Converting to taffy style for layout engine + /// + /// This DOES NOT have final interpolated values and it DOES NOT resolve properties into points. + pub computed_style: Style, + /// The inherited properties context for children. + /// Contains only properties marked as `inherited` (font-size, color, etc.). + /// + /// Derived from this view's computed_style (which includes inherited properties + /// from ancestors). Children will merge this with their combined_style to produce + /// their computed_style. + pub style_cx: Style, + /// The class context containing class definitions for descendants. + /// Contains `.class(SomeClass, ...)` nested maps that flow down the tree. + /// + /// Derived from this view's combined_style (only explicitly set class definitions). + /// Children will use this to resolve their class references when computing their + /// combined_style. + pub class_cx: Style, + /// Interaction cx saved after computing the final style; becomes the + /// inherited interaction for this view's children. + pub style_interaction_cx: InheritedInteractionCx, + /// View-local interaction flags derived from this view's resolved combined style. + /// + /// This excludes inherited parent interaction and is OR'ed onto StyleCx interaction + /// state during fast-path style passes that skip `compute_combined`. + pub post_compute_combined_interaction: InheritedInteractionCx, + /// Interaction cx set by a parent on this view; consumed when building the + /// StyleCx for this view. + pub parent_set_style_interaction: InheritedInteractionCx, + /// Controls view visibility for phase transitions. + pub visibility: Visibility, + /// The cursor style set by the style pass on the view. + /// There is also a user-set cursor (on `ViewState`) which takes precedence. + pub style_cursor: Option, + /// Number of enter/exit animations still running for this view; when this + /// hits zero the visibility phase can transition to its final state. + pub num_waiting_animations: u16, +} diff --git a/src/style/theme.rs b/src/style/theme.rs index a94bbfcae..be5cc4f14 100644 --- a/src/style/theme.rs +++ b/src/style/theme.rs @@ -248,6 +248,40 @@ impl DesignSystem { } impl StylePropValue for DesignSystem { + fn interpolate(&self, other: &Self, value: f64) -> Option { + use peniko::color::HueDirection; + let t = value as f32; + let inv_t = 1.0 - t; + let t64 = value; + let inv_t64 = 1.0 - t64; + + Some(DesignSystem { + bg_base: self.bg_base.lerp(other.bg_base, t, HueDirection::default()), + text_base: self + .text_base + .lerp(other.text_base, t, HueDirection::default()), + text_lightness: self.text_lightness * inv_t + other.text_lightness * t, + primary_base: self + .primary_base + .lerp(other.primary_base, t, HueDirection::default()), + success_base: self + .success_base + .lerp(other.success_base, t, HueDirection::default()), + warning_base: self + .warning_base + .lerp(other.warning_base, t, HueDirection::default()), + danger_base: self + .danger_base + .lerp(other.danger_base, t, HueDirection::default()), + is_dark: if t < 0.5 { self.is_dark } else { other.is_dark }, + padding: self.padding * inv_t + other.padding * t, + border_radius: self.border_radius * inv_t + other.border_radius * t, + font_size: self.font_size * inv_t64 + other.font_size * t64, + }) + } +} + +impl PropDebugView for DesignSystem { fn debug_view(&self) -> Option { use crate::prelude::*; use crate::views::Stack; @@ -340,38 +374,6 @@ impl StylePropValue for DesignSystem { Some(content.into_any()) } - - fn interpolate(&self, other: &Self, value: f64) -> Option { - use peniko::color::HueDirection; - let t = value as f32; - let inv_t = 1.0 - t; - let t64 = value; - let inv_t64 = 1.0 - t64; - - Some(DesignSystem { - bg_base: self.bg_base.lerp(other.bg_base, t, HueDirection::default()), - text_base: self - .text_base - .lerp(other.text_base, t, HueDirection::default()), - text_lightness: self.text_lightness * inv_t + other.text_lightness * t, - primary_base: self - .primary_base - .lerp(other.primary_base, t, HueDirection::default()), - success_base: self - .success_base - .lerp(other.success_base, t, HueDirection::default()), - warning_base: self - .warning_base - .lerp(other.warning_base, t, HueDirection::default()), - danger_base: self - .danger_base - .lerp(other.danger_base, t, HueDirection::default()), - is_dark: if t < 0.5 { self.is_dark } else { other.is_dark }, - padding: self.padding * inv_t + other.padding * t, - border_radius: self.border_radius * inv_t + other.border_radius * t, - font_size: self.font_size * inv_t64 + other.font_size * t64, - }) - } } prop!( diff --git a/src/style/values.rs b/src/style/values.rs index 99b9140ec..6b4e465b4 100644 --- a/src/style/values.rs +++ b/src/style/values.rs @@ -44,10 +44,40 @@ use crate::views::{ }; use super::{ - FontSize, ResponsiveSelectors, StructuralSelectors, Style, StyleDebugGroupInfo, StyleKey, - StyleKeyInfo, StylePropRef, Transition, + FontSize, PropDebugView, ResponsiveSelectors, StructuralSelectors, Style, StyleDebugGroupInfo, + StyleKey, StyleKeyInfo, StylePropRef, Transition, }; +use crate::no_debug_view; + +no_debug_view!( + i32, + bool, + f32, + u16, + usize, + f64, + Overflow, + Display, + Position, + FlexDirection, + FlexWrap, + AlignItems, + BoxSizing, + AlignContent, + GridTemplateComponent, + MinTrackSizingFunction, + MaxTrackSizingFunction, + taffy::GridAutoFlow, + GridPlacement, + String, + crate::text::Alignment, + LineHeightValue, + Size, + super::Angle, + super::AnchorAbout, +); + pub struct ContextValue { pub(crate) eval: Rc T>, } @@ -97,11 +127,7 @@ impl ContextValue { } } -pub trait StylePropValue: Clone + PartialEq + Debug { - fn debug_view(&self) -> Option> { - None - } - +pub trait StylePropValue: Clone + PartialEq + Debug + crate::style::PropDebugView { fn interpolate(&self, _other: &Self, _value: f64) -> Option { None } @@ -245,11 +271,13 @@ impl StylePropValue for MinMax { hash_value(&(self.min.content_hash(), self.max.content_hash())) } } +impl PropDebugView for MinMax {} impl StylePropValue for Line { fn content_hash(&self) -> u64 { hash_value(&(self.start.content_hash(), self.end.content_hash())) } } +impl PropDebugView for Line {} impl StylePropValue for taffy::GridAutoFlow { fn content_hash(&self) -> u64 { hash_value(&std::mem::discriminant(self)) @@ -318,6 +346,8 @@ impl StylePropValue for ObjectFit { fn content_hash(&self) -> u64 { hash_value(self) } +} +impl PropDebugView for ObjectFit { fn debug_view(&self) -> Option> { use peniko::kurbo::RoundedRect; @@ -466,6 +496,8 @@ impl StylePropValue for ObjectPosition { fn content_hash(&self) -> u64 { hash_value(&std::mem::discriminant(self)) } +} +impl PropDebugView for ObjectPosition { fn debug_view(&self) -> Option> { use peniko::kurbo::{Circle, RoundedRect}; @@ -606,6 +638,34 @@ impl StylePropValue for ObjectPosition { } impl StylePropValue for SmallVec +where + ::Item: StylePropValue, +{ + fn interpolate(&self, other: &Self, value: f64) -> Option { + self.iter().zip(other.iter()).try_fold( + SmallVec::with_capacity(self.len()), + |mut acc, (v1, v2)| { + if let Some(interpolated) = v1.interpolate(v2, value) { + acc.push(interpolated); + Some(acc) + } else { + None + } + }, + ) + } + + fn content_hash(&self) -> u64 { + use std::hash::{Hash, Hasher}; + let mut h = rustc_hash::FxHasher::default(); + self.len().hash(&mut h); + for item in self.iter() { + item.content_hash().hash(&mut h); + } + h.finish() + } +} +impl PropDebugView for SmallVec where ::Item: StylePropValue, { @@ -669,30 +729,6 @@ where .into_any(), ) } - - fn interpolate(&self, other: &Self, value: f64) -> Option { - self.iter().zip(other.iter()).try_fold( - SmallVec::with_capacity(self.len()), - |mut acc, (v1, v2)| { - if let Some(interpolated) = v1.interpolate(v2, value) { - acc.push(interpolated); - Some(acc) - } else { - None - } - }, - ) - } - - fn content_hash(&self) -> u64 { - use std::hash::{Hash, Hasher}; - let mut h = rustc_hash::FxHasher::default(); - self.len().hash(&mut h); - for item in self.iter() { - item.content_hash().hash(&mut h); - } - h.finish() - } } impl StylePropValue for String { fn content_hash(&self) -> u64 { @@ -703,6 +739,13 @@ impl StylePropValue for FontWeight { fn content_hash(&self) -> u64 { hash_f32(self.value()) } + fn interpolate(&self, other: &Self, value: f64) -> Option { + self.value() + .interpolate(&other.value(), value) + .map(FontWeight::new) + } +} +impl PropDebugView for FontWeight { fn debug_view(&self) -> Option> { let clone = *self; Some( @@ -711,16 +754,13 @@ impl StylePropValue for FontWeight { .into_any(), ) } - fn interpolate(&self, other: &Self, value: f64) -> Option { - self.value() - .interpolate(&other.value(), value) - .map(FontWeight::new) - } } impl StylePropValue for crate::text::FontStyle { fn content_hash(&self) -> u64 { hash_value(&std::mem::discriminant(self)) } +} +impl PropDebugView for crate::text::FontStyle { fn debug_view(&self) -> Option> { let clone = *self; Some( @@ -761,10 +801,6 @@ impl StylePropValue for LineHeightValue { impl StylePropValue for Size {} impl StylePropValue for Option { - fn debug_view(&self) -> Option> { - self.as_ref().and_then(|v| v.debug_view()) - } - fn interpolate(&self, other: &Self, value: f64) -> Option { self.as_ref().and_then(|this| { other @@ -786,7 +822,37 @@ impl StylePropValue for Option { h.finish() } } +impl PropDebugView for Option { + fn debug_view(&self) -> Option> { + self.as_ref().and_then(|v| v.debug_view()) + } +} impl StylePropValue for Vec { + fn interpolate(&self, other: &Self, value: f64) -> Option { + self.iter().zip(other.iter()).try_fold( + Vec::with_capacity(self.len()), + |mut acc, (v1, v2)| { + if let Some(interpolated) = v1.interpolate(v2, value) { + acc.push(interpolated); + Some(acc) + } else { + None + } + }, + ) + } + + fn content_hash(&self) -> u64 { + use std::hash::{Hash, Hasher}; + let mut h = rustc_hash::FxHasher::default(); + self.len().hash(&mut h); + for item in self { + item.content_hash().hash(&mut h); + } + h.finish() + } +} +impl PropDebugView for Vec { fn debug_view(&self) -> Option> { if self.is_empty() { return Some( @@ -830,35 +896,8 @@ impl StylePropValue for Vec { tooltip_view().into_any(), ) } - - fn interpolate(&self, other: &Self, value: f64) -> Option { - self.iter().zip(other.iter()).try_fold( - Vec::with_capacity(self.len()), - |mut acc, (v1, v2)| { - if let Some(interpolated) = v1.interpolate(v2, value) { - acc.push(interpolated); - Some(acc) - } else { - None - } - }, - ) - } - - fn content_hash(&self) -> u64 { - use std::hash::{Hash, Hasher}; - let mut h = rustc_hash::FxHasher::default(); - self.len().hash(&mut h); - for item in self { - item.content_hash().hash(&mut h); - } - h.finish() - } } impl StylePropValue for Pt { - fn debug_view(&self) -> Option> { - Some(Label::new(format!("{} pt", self.0)).into_any()) - } fn interpolate(&self, other: &Self, value: f64) -> Option { self.0.interpolate(&other.0, value).map(Pt) } @@ -866,12 +905,13 @@ impl StylePropValue for Pt { hash_f64(self.0) } } -#[allow(deprecated)] -impl StylePropValue for super::unit::Px { +impl PropDebugView for Pt { fn debug_view(&self) -> Option> { - Pt(self.0).debug_view() + Some(Label::new(format!("{} pt", self.0)).into_any()) } - +} +#[allow(deprecated)] +impl StylePropValue for super::unit::Px { fn interpolate(&self, other: &Self, value: f64) -> Option { self.0.interpolate(&other.0, value).map(super::unit::Px) } @@ -879,10 +919,13 @@ impl StylePropValue for super::unit::Px { hash_f64(self.0) } } -impl StylePropValue for Pct { +#[allow(deprecated)] +impl PropDebugView for super::unit::Px { fn debug_view(&self) -> Option> { - Some(Label::new(format!("{}%", self.0)).into_any()) + Pt(self.0).debug_view() } +} +impl StylePropValue for Pct { fn interpolate(&self, other: &Self, value: f64) -> Option { self.0.interpolate(&other.0, value).map(Pct) } @@ -890,17 +933,12 @@ impl StylePropValue for Pct { hash_f64(self.0) } } -impl StylePropValue for LengthAuto { +impl PropDebugView for Pct { fn debug_view(&self) -> Option> { - let label = match self { - Self::Pt(v) => format!("{v} pt"), - Self::Pct(v) => format!("{v}%"), - Self::Em(v) => format!("{v} em"), - Self::Lh(v) => format!("{v} lh"), - Self::Auto => "auto".to_string(), - }; - Some(Label::new(label).into_any()) + Some(Label::new(format!("{}%", self.0)).into_any()) } +} +impl StylePropValue for LengthAuto { fn interpolate(&self, other: &Self, value: f64) -> Option { match (self, other) { (Self::Pt(v1), Self::Pt(v2)) => Some(Self::Pt(v1 + (v2 - v1) * value)), @@ -923,12 +961,20 @@ impl StylePropValue for LengthAuto { h.finish() } } -#[allow(deprecated)] -impl StylePropValue for super::unit::PxPctAuto { +impl PropDebugView for LengthAuto { fn debug_view(&self) -> Option> { - LengthAuto::from(*self).debug_view() + let label = match self { + Self::Pt(v) => format!("{v} pt"), + Self::Pct(v) => format!("{v}%"), + Self::Em(v) => format!("{v} em"), + Self::Lh(v) => format!("{v} lh"), + Self::Auto => "auto".to_string(), + }; + Some(Label::new(label).into_any()) } - +} +#[allow(deprecated)] +impl StylePropValue for super::unit::PxPctAuto { fn interpolate(&self, other: &Self, value: f64) -> Option { match (self, other) { (Self::Px(v1), Self::Px(v2)) => Some(Self::Px(v1 + (v2 - v1) * value)), @@ -948,17 +994,13 @@ impl StylePropValue for super::unit::PxPctAuto { h.finish() } } -impl StylePropValue for Length { +#[allow(deprecated)] +impl PropDebugView for super::unit::PxPctAuto { fn debug_view(&self) -> Option> { - let label = match self { - Self::Pt(v) => format!("{v} pt"), - Self::Pct(v) => format!("{v}%"), - Self::Em(v) => format!("{v} em"), - Self::Lh(v) => format!("{v} lh"), - }; - Some(Label::new(label).into_any()) + LengthAuto::from(*self).debug_view() } - +} +impl StylePropValue for Length { fn interpolate(&self, other: &Self, value: f64) -> Option { match (self, other) { (Self::Pt(v1), Self::Pt(v2)) => Some(Self::Pt(v1 + (v2 - v1) * value)), @@ -979,12 +1021,19 @@ impl StylePropValue for Length { h.finish() } } -#[allow(deprecated)] -impl StylePropValue for super::unit::PxPct { +impl PropDebugView for Length { fn debug_view(&self) -> Option> { - Length::from(*self).debug_view() + let label = match self { + Self::Pt(v) => format!("{v} pt"), + Self::Pct(v) => format!("{v}%"), + Self::Em(v) => format!("{v} em"), + Self::Lh(v) => format!("{v} lh"), + }; + Some(Label::new(label).into_any()) } - +} +#[allow(deprecated)] +impl StylePropValue for super::unit::PxPct { fn interpolate(&self, other: &Self, value: f64) -> Option { match (self, other) { (Self::Px(v1), Self::Px(v2)) => Some(Self::Px(v1 + (v2 - v1) * value)), @@ -1002,6 +1051,12 @@ impl StylePropValue for super::unit::PxPct { h.finish() } } +#[allow(deprecated)] +impl PropDebugView for super::unit::PxPct { + fn debug_view(&self) -> Option> { + Length::from(*self).debug_view() + } +} pub(crate) fn views(views: impl ViewTuple) -> Vec { views.into_views() @@ -1016,6 +1071,13 @@ impl StylePropValue for Color { } h.finish() } + + fn interpolate(&self, other: &Self, value: f64) -> Option { + Some(self.lerp(*other, value as f32, HueDirection::default())) + } +} + +impl PropDebugView for Color { fn debug_view(&self) -> Option> { let color = *self; let swatch = () @@ -1106,13 +1168,28 @@ impl StylePropValue for Color { .into_any(), ) } +} - fn interpolate(&self, other: &Self, value: f64) -> Option { - Some(self.lerp(*other, value as f32, HueDirection::default())) +impl StylePropValue for Gradient { + fn interpolate(&self, _other: &Self, _value: f64) -> Option { + None + } + + fn content_hash(&self) -> u64 { + use std::hash::{Hash, Hasher}; + let mut h = rustc_hash::FxHasher::default(); + std::mem::discriminant(&self.kind).hash(&mut h); + for stop in self.stops.iter() { + stop.offset.to_bits().hash(&mut h); + for c in stop.color.components { + c.to_bits().hash(&mut h); + } + } + h.finish() } } -impl StylePropValue for Gradient { +impl PropDebugView for Gradient { fn debug_view(&self) -> Option> { let box_width = 22.; let box_height = 14.; @@ -1166,23 +1243,6 @@ impl StylePropValue for Gradient { .into_any(), ) } - - fn interpolate(&self, _other: &Self, _value: f64) -> Option { - None - } - - fn content_hash(&self) -> u64 { - use std::hash::{Hash, Hasher}; - let mut h = rustc_hash::FxHasher::default(); - std::mem::discriminant(&self.kind).hash(&mut h); - for stop in self.stops.iter() { - stop.offset.to_bits().hash(&mut h); - for c in stop.color.components { - c.to_bits().hash(&mut h); - } - } - h.finish() - } } // this is a convenience wrapper so border/outline setters can accept numeric widths. @@ -1219,6 +1279,8 @@ impl StylePropValue for Stroke { fn content_hash(&self) -> u64 { hash_f64(self.width) } +} +impl PropDebugView for Stroke { fn debug_view(&self) -> Option> { let stroke = self.clone(); let clone = stroke.clone(); @@ -1342,13 +1404,6 @@ impl StylePropValue for Brush { } h.finish() } - fn debug_view(&self) -> Option> { - match self { - Brush::Solid(color) => color.debug_view(), - Brush::Gradient(grad) => grad.debug_view(), - Brush::Image(_) => None, - } - } fn interpolate(&self, other: &Self, value: f64) -> Option { match (self, other) { @@ -1409,11 +1464,16 @@ impl StylePropValue for Brush { } } } -impl StylePropValue for Duration { +impl PropDebugView for Brush { fn debug_view(&self) -> Option> { - None + match self { + Brush::Solid(color) => color.debug_view(), + Brush::Gradient(grad) => grad.debug_view(), + Brush::Image(_) => None, + } } - +} +impl StylePropValue for Duration { fn interpolate(&self, other: &Self, value: f64) -> Option { self.as_secs_f64() .interpolate(&other.as_secs_f64(), value) @@ -1424,6 +1484,11 @@ impl StylePropValue for Duration { hash_value(self) } } +impl PropDebugView for Duration { + fn debug_view(&self) -> Option> { + None + } +} impl StylePropValue for super::Angle { fn interpolate(&self, other: &Self, value: f64) -> Option { @@ -1466,6 +1531,20 @@ impl StylePropValue for kurbo::Rect { self.y1.to_bits().hash(&mut h); h.finish() } + + fn interpolate(&self, other: &Self, value: f64) -> Option { + let lerp = |a: f64, b: f64| a + (b - a) * value; + + Some(Self { + x0: lerp(self.x0, other.x0), + y0: lerp(self.y0, other.y0), + x1: lerp(self.x1, other.x1), + y1: lerp(self.y1, other.y1), + }) + } +} + +impl PropDebugView for kurbo::Rect { fn debug_view(&self) -> Option> { let r = *self; @@ -1507,20 +1586,27 @@ impl StylePropValue for kurbo::Rect { .into_any(), ) } +} - fn interpolate(&self, other: &Self, value: f64) -> Option { - let lerp = |a: f64, b: f64| a + (b - a) * value; +impl StylePropValue for Affine { + fn interpolate(&self, other: &Self, t: f64) -> Option { + Some(self.lerp(other, t)) + } - Some(Self { - x0: lerp(self.x0, other.x0), - y0: lerp(self.y0, other.y0), - x1: lerp(self.x1, other.x1), - y1: lerp(self.y1, other.y1), - }) + fn content_hash(&self) -> u64 { + use std::hash::{Hash, Hasher}; + let mut hasher = rustc_hash::FxHasher::default(); + + let coeffs = self.as_coeffs(); + for coeff in coeffs { + coeff.to_bits().hash(&mut hasher); + } + + hasher.finish() } } -impl StylePropValue for Affine { +impl PropDebugView for Affine { fn debug_view(&self) -> Option> { let affine = *self; let coeffs = affine.as_coeffs(); @@ -1670,22 +1756,6 @@ impl StylePropValue for Affine { .into_any(), ) } - - fn interpolate(&self, other: &Self, t: f64) -> Option { - Some(self.lerp(other, t)) - } - - fn content_hash(&self) -> u64 { - use std::hash::{Hash, Hasher}; - let mut hasher = rustc_hash::FxHasher::default(); - - let coeffs = self.as_coeffs(); - for coeff in coeffs { - coeff.to_bits().hash(&mut hasher); - } - - hasher.finish() - } } pub trait AffineLerp { diff --git a/src/view/id.rs b/src/view/id.rs index 05206842a..96377694e 100644 --- a/src/view/id.rs +++ b/src/view/id.rs @@ -792,7 +792,7 @@ impl ViewId { pub fn is_hidden(&self) -> bool { let state = self.state(); let state = state.borrow(); - state.visibility.is_hidden() + state.style_storage.visibility.is_hidden() } /// if the view has pointer events none @@ -800,7 +800,7 @@ impl ViewId { let state = self.state(); let state = state.borrow(); state - .computed_style + .style_storage.computed_style .builtin() .pointer_events() .map(|p| p == PointerEvents::None) @@ -811,7 +811,7 @@ impl ViewId { /// /// This is done by checking if the style for this view has `Disabled` set to true. pub fn is_disabled(&self) -> bool { - self.state().borrow_mut().style_interaction_cx.disabled + self.state().borrow_mut().style_storage.style_interaction_cx.disabled } /// Returns true if the view is selected @@ -819,7 +819,7 @@ impl ViewId { /// This is done by checking if the parent has set this view as selected /// via `parent_set_selected()`. pub fn is_selected(&self) -> bool { - self.state().borrow().parent_set_style_interaction.selected + self.state().borrow().style_storage.parent_set_style_interaction.selected } /// Check if this id can be focused. @@ -828,7 +828,7 @@ impl ViewId { pub fn can_focus(&self) -> bool { self.state() .borrow() - .computed_style + .style_storage.computed_style .get(Focusable) .is_focusable() } @@ -1067,7 +1067,7 @@ impl ViewId { /// a [prop extractor](crate::prop_extractor) that is updated in a style method /// of the View to extract the property. pub fn get_combined_style(&self) -> Style { - self.state().borrow().combined_style.clone() + self.state().borrow().style_storage.combined_style.clone() } /// Add a class to the list of style classes that are associated with this `ViewId` @@ -1585,8 +1585,8 @@ impl ViewId { let changed = { let state = self.state(); let mut state = state.borrow_mut(); - if !state.parent_set_style_interaction.selected { - state.parent_set_style_interaction.selected = true; + if !state.style_storage.parent_set_style_interaction.selected { + state.style_storage.parent_set_style_interaction.selected = true; true } else { false @@ -1604,8 +1604,8 @@ impl ViewId { let changed = { let state = self.state(); let mut state = state.borrow_mut(); - if state.parent_set_style_interaction.selected { - state.parent_set_style_interaction.selected = false; + if state.style_storage.parent_set_style_interaction.selected { + state.style_storage.parent_set_style_interaction.selected = false; true } else { false @@ -1623,8 +1623,8 @@ impl ViewId { let changed = { let state = self.state(); let mut state = state.borrow_mut(); - if !state.parent_set_style_interaction.disabled { - state.parent_set_style_interaction.disabled = true; + if !state.style_storage.parent_set_style_interaction.disabled { + state.style_storage.parent_set_style_interaction.disabled = true; true } else { false @@ -1642,8 +1642,8 @@ impl ViewId { let changed = { let state = self.state(); let mut state = state.borrow_mut(); - if state.parent_set_style_interaction.disabled { - state.parent_set_style_interaction.disabled = false; + if state.style_storage.parent_set_style_interaction.disabled { + state.style_storage.parent_set_style_interaction.disabled = false; true } else { false @@ -1663,9 +1663,9 @@ impl ViewId { let changed = { let state = self.state(); let mut state = state.borrow_mut(); - if !state.parent_set_style_interaction.hidden { - state.parent_set_style_interaction.hidden = true; - state.visibility.phase = VisibilityPhase::Hidden; + if !state.style_storage.parent_set_style_interaction.hidden { + state.style_storage.parent_set_style_interaction.hidden = true; + state.style_storage.visibility.phase = VisibilityPhase::Hidden; true } else { false @@ -1684,10 +1684,10 @@ impl ViewId { let changed = { let state = self.state(); let mut state = state.borrow_mut(); - if state.parent_set_style_interaction.hidden { - state.parent_set_style_interaction.hidden = false; + if state.style_storage.parent_set_style_interaction.hidden { + state.style_storage.parent_set_style_interaction.hidden = false; // Reset phase to Initial so the normal transition logic can run - state.visibility.phase = VisibilityPhase::Initial; + state.style_storage.visibility.phase = VisibilityPhase::Initial; true } else { false diff --git a/src/view/state.rs b/src/view/state.rs index 09915aacf..01f24bdf9 100644 --- a/src/view/state.rs +++ b/src/view/state.rs @@ -12,9 +12,8 @@ use crate::{ style::{ Background, BorderBottomColor, BorderBottomLeftRadius, BorderBottomRightRadius, BorderLeftColor, BorderRightColor, BorderTopColor, BorderTopLeftRadius, - BorderTopRightRadius, BoxShadowProp, CursorStyle, InheritedInteractionCx, LayoutProps, - Outline, OutlineColor, Style, StyleClassRef, StyleSelectors, TransformProps, - recalc::StyleReason, + BorderTopRightRadius, BoxShadowProp, CursorStyle, InheritedInteractionCx, Outline, + OutlineColor, Style, StyleClassRef, StyleStorage, recalc::StyleReason, }, view::LayoutTree, }; @@ -325,67 +324,18 @@ pub struct ViewState { /// We store the stack offset to the view style to keep the api consistent but it should /// always be the first offset. pub(crate) view_style_offset: StackOffset