orbit/
style.rs

1// Enhanced styling system for the Orbit UI framework with CSS-like properties and layout integration
2
3#[cfg(test)]
4mod tests;
5
6use crate::component::ComponentId;
7use crate::layout::{Dimension, EdgeValues, LayoutStyle};
8use std::collections::HashMap;
9use std::sync::atomic::{AtomicU64, Ordering};
10
11/// CSS selector specificity (a, b, c)
12#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
13pub struct Specificity(pub u32, pub u32, pub u32);
14
15/// CSS property
16#[derive(Debug, Clone, PartialEq)]
17pub struct CssProperty {
18    /// Name of the property
19    pub name: String,
20    /// Value of the property
21    pub value: String,
22}
23
24/// CSS selector
25#[derive(Debug, Clone, PartialEq)]
26pub struct CssSelector {
27    /// Selector text
28    pub selector: String,
29    /// Properties for this selector
30    pub properties: Vec<CssProperty>,
31}
32
33/// CSS rule with selector, properties, and metadata
34#[derive(Debug, Clone)]
35pub struct StyleRule {
36    /// The CSS selector(s) for this rule
37    pub selectors: Vec<CssSelector>,
38    /// Whether this rule is scoped to a component
39    pub scoped: bool,
40    /// The computed specificity of the selector
41    pub specificity: Specificity,
42    /// Source order for breaking specificity ties
43    pub source_order: usize,
44}
45
46/// CSS stylesheet
47#[derive(Debug, Clone, Default)]
48pub struct Stylesheet {
49    /// Rules in this stylesheet
50    pub rules: Vec<StyleRule>,
51}
52
53impl StyleRule {
54    /// Create a new StyleRule
55    pub fn new(selectors: Vec<CssSelector>, scoped: bool, source_order: usize) -> Self {
56        // Calculate specificity for the most specific selector
57        let specificity = selectors
58            .iter()
59            .map(|s| Self::calculate_specificity(&s.selector))
60            .max()
61            .unwrap_or(Specificity(0, 0, 0));
62
63        Self {
64            selectors,
65            scoped,
66            specificity,
67            source_order,
68        }
69    }
70
71    /// Calculate selector specificity (a, b, c)
72    /// a = ID selectors
73    /// b = Class selectors, attributes, and pseudo-classes
74    /// c = Element selectors and pseudo-elements
75    fn calculate_specificity(selector: &str) -> Specificity {
76        let mut a = 0; // ID selectors
77        let mut b = 0; // Class, attribute, pseudo-class
78        let mut c = 0; // Element and pseudo-element
79
80        for part in selector.split(' ') {
81            // Count ID selectors (#)
82            a += part.matches('#').count() as u32;
83
84            // Count class selectors (.), attributes ([]), pseudo-classes (:)
85            b += part.matches('.').count() as u32;
86            b += part.matches('[').count() as u32;
87            b += part.matches(':').count() as u32;
88            b -= part.matches("::").count() as u32; // Adjust for pseudo-elements
89
90            // Count element selectors and pseudo-elements (::)
91            if !part.starts_with('.') && !part.starts_with('#') && !part.starts_with('[') {
92                c += 1;
93            }
94            c += part.matches("::").count() as u32;
95        }
96
97        Specificity(a, b, c)
98    }
99
100    /// Apply component scoping to selectors
101    pub fn apply_scoping(&mut self, component_id: &str) {
102        if self.scoped {
103            for selector in &mut self.selectors {
104                // Add component scoping class to each selector
105                selector.selector = format!(".{} {}", component_id, selector.selector);
106                self.specificity = Self::calculate_specificity(&selector.selector);
107            }
108        }
109    }
110}
111
112impl Stylesheet {
113    /// Create a new empty stylesheet
114    pub fn new() -> Self {
115        Self::default()
116    }
117
118    /// Add a rule to the stylesheet
119    pub fn add_rule(&mut self, rule: StyleRule) {
120        self.rules.push(rule);
121    }
122
123    /// Parse CSS text into a stylesheet
124    pub fn parse(css: &str, scoped: bool) -> Result<Self, StyleError> {
125        let mut stylesheet = Self::new();
126        let mut current_selectors = Vec::new();
127        let mut current_properties = Vec::new();
128        let mut in_rule = false;
129        let mut source_order = 0;
130
131        for line in css.lines() {
132            let line = line.trim();
133
134            // Skip empty lines and comments
135            if line.is_empty() || line.starts_with("/*") {
136                continue;
137            }
138
139            if line.contains('{') {
140                // Start of a rule - parse selectors
141                in_rule = true;
142                current_selectors = line
143                    .split('{')
144                    .next()
145                    .unwrap_or("")
146                    .split(',')
147                    .map(|s| CssSelector {
148                        selector: s.trim().to_string(),
149                        properties: Vec::new(),
150                    })
151                    .collect();
152            } else if line.contains('}') {
153                // End of a rule - create StyleRule
154                in_rule = false;
155                if !current_selectors.is_empty() {
156                    for selector in &mut current_selectors {
157                        selector.properties = current_properties.clone();
158                    }
159                    let rule = StyleRule::new(current_selectors.clone(), scoped, source_order);
160                    stylesheet.add_rule(rule);
161                    source_order += 1;
162                }
163                current_selectors.clear();
164                current_properties.clear();
165            } else if in_rule && line.contains(':') {
166                // Property definition
167                let parts: Vec<&str> = line.split(':').collect();
168                if parts.len() == 2 {
169                    current_properties.push(CssProperty {
170                        name: parts[0].trim().to_string(),
171                        value: parts[1].trim().trim_end_matches(';').to_string(),
172                    });
173                }
174            }
175        }
176
177        Ok(stylesheet)
178    }
179}
180
181impl std::fmt::Display for Stylesheet {
182    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
183        let mut result = String::new();
184
185        for rule in &self.rules {
186            for selector in &rule.selectors {
187                result.push_str(&selector.selector);
188                if rule.scoped {
189                    result.push_str(" /* scoped */");
190                }
191                result.push_str(" {\n");
192
193                for property in &selector.properties {
194                    result.push_str("  ");
195                    result.push_str(&property.name);
196                    result.push_str(": ");
197                    result.push_str(&property.value);
198                    result.push_str(";\n");
199                }
200
201                result.push_str("}\n\n");
202            }
203        }
204
205        write!(f, "{result}")
206    }
207}
208
209/// Enhanced style properties for a UI element with CSS-like properties
210#[derive(Debug, Clone, Default, PartialEq)]
211pub struct Style {
212    // Visual properties
213    pub background_color: Option<Color>,
214    pub color: Option<Color>,
215    pub opacity: Option<f32>,
216    pub visibility: Option<Visibility>,
217
218    // Border properties
219    pub border_width: Option<EdgeValues>,
220    pub border_color: Option<EdgeColors>,
221    pub border_style: Option<BorderStyle>,
222    pub border_radius: Option<BorderRadius>,
223
224    // Font and text properties
225    pub font_family: Option<String>,
226    pub font_size: Option<f32>,
227    pub font_weight: Option<FontWeight>,
228    pub font_style: Option<FontStyle>,
229    pub line_height: Option<f32>,
230    pub letter_spacing: Option<f32>,
231    pub text_align: Option<TextAlign>,
232    pub text_decoration: Option<TextDecoration>,
233    pub text_transform: Option<TextTransform>,
234
235    // Layout integration (connects to layout engine)
236    pub layout_style: Option<LayoutStyle>,
237
238    // Transform properties
239    pub transform: Option<Transform>,
240    pub transform_origin: Option<Point2D>,
241
242    // Transition properties (for animation integration)
243    pub transition_property: Option<Vec<String>>,
244    pub transition_duration: Option<f32>,
245    pub transition_timing_function: Option<TimingFunction>,
246    pub transition_delay: Option<f32>,
247
248    // Shadow properties
249    pub box_shadow: Option<Vec<BoxShadow>>,
250    pub text_shadow: Option<Vec<TextShadow>>,
251
252    // Advanced properties
253    pub filter: Option<Vec<Filter>>,
254    pub backdrop_filter: Option<Vec<Filter>>,
255    pub z_index: Option<i32>,
256    pub cursor: Option<CursorType>,
257
258    // Performance and caching
259    pub computed_hash: Option<u64>,
260    pub is_dirty: bool,
261}
262
263/// Color representation supporting multiple formats
264#[derive(Debug, Clone, PartialEq)]
265pub enum Color {
266    Rgba(f32, f32, f32, f32),
267    Hex(String),
268    Named(String),
269    Hsl(f32, f32, f32, f32),
270    CurrentColor,
271    Transparent,
272}
273
274impl std::hash::Hash for Color {
275    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
276        match self {
277            Color::Rgba(r, g, b, a) => {
278                0u8.hash(state);
279                r.to_bits().hash(state);
280                g.to_bits().hash(state);
281                b.to_bits().hash(state);
282                a.to_bits().hash(state);
283            }
284            Color::Hex(s) => {
285                1u8.hash(state);
286                s.hash(state);
287            }
288            Color::Named(s) => {
289                2u8.hash(state);
290                s.hash(state);
291            }
292            Color::Hsl(h, s, l, a) => {
293                3u8.hash(state);
294                h.to_bits().hash(state);
295                s.to_bits().hash(state);
296                l.to_bits().hash(state);
297                a.to_bits().hash(state);
298            }
299            Color::CurrentColor => {
300                4u8.hash(state);
301            }
302            Color::Transparent => {
303                5u8.hash(state);
304            }
305        }
306    }
307}
308
309/// Visibility values
310#[derive(Debug, Clone, PartialEq)]
311pub enum Visibility {
312    Visible,
313    Hidden,
314    Collapse,
315}
316
317/// Border style options
318#[derive(Debug, Clone, PartialEq)]
319pub enum BorderStyle {
320    None,
321    Solid,
322    Dashed,
323    Dotted,
324    Double,
325    Groove,
326    Ridge,
327    Inset,
328    Outset,
329}
330
331/// Border radius configuration
332#[derive(Debug, Clone, PartialEq)]
333pub struct BorderRadius {
334    pub top_left: f32,
335    pub top_right: f32,
336    pub bottom_right: f32,
337    pub bottom_left: f32,
338}
339
340/// Edge-specific colors for borders
341#[derive(Debug, Clone, PartialEq)]
342pub struct EdgeColors {
343    pub top: Color,
344    pub right: Color,
345    pub bottom: Color,
346    pub left: Color,
347}
348
349/// Font weight values
350#[derive(Debug, Clone, PartialEq)]
351pub enum FontWeight {
352    Thin,       // 100
353    ExtraLight, // 200
354    Light,      // 300
355    Normal,     // 400
356    Medium,     // 500
357    SemiBold,   // 600
358    Bold,       // 700
359    ExtraBold,  // 800
360    Black,      // 900
361    Numeric(u16),
362}
363
364/// Font style values
365#[derive(Debug, Clone, PartialEq)]
366pub enum FontStyle {
367    Normal,
368    Italic,
369    Oblique(f32), // angle in degrees
370}
371
372/// Text alignment
373#[derive(Debug, Clone, PartialEq)]
374pub enum TextAlign {
375    Left,
376    Right,
377    Center,
378    Justify,
379    Start,
380    End,
381}
382
383/// Text decoration
384#[derive(Debug, Clone, PartialEq)]
385pub enum TextDecoration {
386    None,
387    Underline,
388    Overline,
389    LineThrough,
390    Blink,
391}
392
393/// Text transform
394#[derive(Debug, Clone, PartialEq)]
395pub enum TextTransform {
396    None,
397    Capitalize,
398    Uppercase,
399    Lowercase,
400}
401
402/// 2D point for transform origins
403#[derive(Debug, Clone, PartialEq)]
404pub struct Point2D {
405    pub x: f32,
406    pub y: f32,
407}
408
409/// CSS transform operations
410#[derive(Debug, Clone, PartialEq)]
411pub enum Transform {
412    None,
413    Matrix(f32, f32, f32, f32, f32, f32),
414    Translate(f32, f32),
415    TranslateX(f32),
416    TranslateY(f32),
417    Scale(f32, f32),
418    ScaleX(f32),
419    ScaleY(f32),
420    Rotate(f32), // degrees
421    SkewX(f32),  // degrees
422    SkewY(f32),  // degrees
423    Multiple(Vec<Transform>),
424}
425
426/// Timing functions for animations
427#[derive(Debug, Clone, PartialEq)]
428pub enum TimingFunction {
429    Linear,
430    Ease,
431    EaseIn,
432    EaseOut,
433    EaseInOut,
434    CubicBezier(f32, f32, f32, f32),
435    Steps(u32, StepPosition),
436}
437
438/// Step position for stepped timing functions
439#[derive(Debug, Clone, PartialEq)]
440pub enum StepPosition {
441    Start,
442    End,
443}
444
445/// Box shadow definition
446#[derive(Debug, Clone, PartialEq)]
447pub struct BoxShadow {
448    pub offset_x: f32,
449    pub offset_y: f32,
450    pub blur_radius: f32,
451    pub spread_radius: f32,
452    pub color: Color,
453    pub inset: bool,
454}
455
456/// Text shadow definition
457#[derive(Debug, Clone, PartialEq)]
458pub struct TextShadow {
459    pub offset_x: f32,
460    pub offset_y: f32,
461    pub blur_radius: f32,
462    pub color: Color,
463}
464
465/// CSS filter functions
466#[derive(Debug, Clone, PartialEq)]
467pub enum Filter {
468    Blur(f32),
469    Brightness(f32),
470    Contrast(f32),
471    DropShadow(f32, f32, f32, Color),
472    Grayscale(f32),
473    HueRotate(f32),
474    Invert(f32),
475    Opacity(f32),
476    Saturate(f32),
477    Sepia(f32),
478}
479
480/// Cursor types
481#[derive(Debug, Clone, PartialEq)]
482pub enum CursorType {
483    Auto,
484    Default,
485    None,
486    ContextMenu,
487    Help,
488    Pointer,
489    Progress,
490    Wait,
491    Cell,
492    Crosshair,
493    Text,
494    VerticalText,
495    Alias,
496    Copy,
497    Move,
498    NoDrop,
499    NotAllowed,
500    Grab,
501    Grabbing,
502    EResize,
503    NResize,
504    NeResize,
505    NwResize,
506    SResize,
507    SeResize,
508    SwResize,
509    WResize,
510    EwResize,
511    NsResize,
512    NeswResize,
513    NwseResize,
514    ColResize,
515    RowResize,
516    AllScroll,
517    ZoomIn,
518    ZoomOut,
519}
520
521/// Style engine for efficient style computation and caching
522#[derive(Debug)]
523pub struct StyleEngine {
524    /// Cache of computed styles keyed by hash
525    computed_cache: HashMap<u64, ComputedStyle>,
526    /// Style inheritance tree
527    inheritance_tree: HashMap<ComponentId, ComponentId>,
528    /// Global style rules
529    global_rules: Vec<StyleRule>,
530    /// Component-scoped style rules
531    component_rules: HashMap<ComponentId, Vec<StyleRule>>,
532    /// Performance statistics
533    stats: StyleStats,
534    /// Cache hit counter for performance monitoring
535    cache_hit_counter: AtomicU64,
536}
537
538/// Computed style represents the final resolved style values
539#[derive(Debug, Clone, PartialEq)]
540pub struct ComputedStyle {
541    /// Final computed style properties
542    pub style: Style,
543    /// Layout style for layout engine integration
544    pub layout_style: LayoutStyle,
545    /// Hash of the computed result for caching
546    pub hash: u64,
547    /// Whether this style is animatable
548    pub is_animatable: bool,
549    /// Timestamp when computed
550    pub computed_at: std::time::Instant,
551}
552
553/// Performance statistics for the style engine
554#[derive(Debug, Default, Clone)]
555pub struct StyleStats {
556    /// Number of style computations performed
557    pub computations: u64,
558    /// Number of cache hits
559    pub cache_hits: u64,
560    /// Total time spent computing styles (ms)
561    pub computation_time_ms: f32,
562    /// Average computation time per style (ms)
563    pub avg_computation_time_ms: f32,
564    /// Number of layout style conversions
565    pub layout_conversions: u64,
566    /// Number of style inheritance operations
567    pub inheritance_operations: u64,
568    /// Current cache size
569    pub cache_size: usize,
570}
571
572/// Style resolution context for computing styles
573#[derive(Debug)]
574pub struct StyleContext {
575    /// Viewport dimensions
576    pub viewport_width: f32,
577    pub viewport_height: f32,
578    /// Device pixel ratio
579    pub device_pixel_ratio: f32,
580    /// Inherited styles from parent
581    pub inherited_style: Option<ComputedStyle>,
582    /// Component ID for scoped styles
583    pub component_id: Option<ComponentId>,
584    /// Current theme variables
585    pub theme_variables: HashMap<String, String>,
586    /// Performance monitoring enabled
587    pub performance_monitoring: bool,
588}
589
590impl Style {
591    /// Create a new empty style
592    pub fn new() -> Self {
593        Self::default()
594    }
595
596    /// Apply another style on top of this one
597    pub fn merge(&mut self, other: &Style) {
598        if let Some(color) = &other.color {
599            self.color = Some(color.clone());
600        }
601        if let Some(bg) = &other.background_color {
602            self.background_color = Some(bg.clone());
603        }
604        // Merge other properties...
605    }
606}
607
608/// Errors that can occur in styling operations
609#[derive(Debug, thiserror::Error)]
610pub enum StyleError {
611    #[error("Error parsing CSS: {0}")]
612    ParseError(String),
613}
614
615impl StyleEngine {
616    /// Create a new style engine
617    pub fn new() -> Self {
618        Self {
619            computed_cache: HashMap::new(),
620            inheritance_tree: HashMap::new(),
621            global_rules: Vec::new(),
622            component_rules: HashMap::new(),
623            stats: StyleStats::default(),
624            cache_hit_counter: AtomicU64::new(0),
625        }
626    }
627
628    /// Add global CSS rules that apply to all components
629    pub fn add_global_rules(&mut self, rules: Vec<StyleRule>) {
630        self.global_rules.extend(rules);
631    }
632
633    /// Add component-scoped CSS rules
634    pub fn add_component_rules(&mut self, component_id: ComponentId, rules: Vec<StyleRule>) {
635        self.component_rules
636            .entry(component_id)
637            .or_default()
638            .extend(rules);
639    }
640
641    /// Compute the final style for a component with context
642    pub fn compute_style(
643        &mut self,
644        component_id: ComponentId,
645        base_style: &Style,
646        context: &StyleContext,
647    ) -> Result<ComputedStyle, StyleError> {
648        let start_time = std::time::Instant::now();
649
650        // Generate cache key from component ID, style, and context
651        let cache_key = self.generate_cache_key(component_id, base_style, context);
652
653        // Check cache first
654        if let Some(computed) = self.computed_cache.get(&cache_key) {
655            self.cache_hit_counter.fetch_add(1, Ordering::Relaxed);
656            self.stats.cache_hits += 1;
657            return Ok(computed.clone());
658        }
659
660        // Compute new style
661        let mut computed_style = base_style.clone();
662
663        // Apply inheritance from parent
664        if let Some(inherited) = &context.inherited_style {
665            self.apply_inheritance(&mut computed_style, &inherited.style);
666        }
667
668        // Apply global CSS rules
669        self.apply_css_rules(&mut computed_style, &self.global_rules.clone(), context)?;
670
671        // Apply component-scoped rules
672        if let Some(component_rules) = self.component_rules.get(&component_id) {
673            self.apply_css_rules(&mut computed_style, component_rules, context)?;
674        }
675
676        // Convert to layout style
677        let layout_style = self.style_to_layout_style(&computed_style, context)?;
678
679        // Create computed style result
680        let computed = ComputedStyle {
681            style: computed_style.clone(),
682            layout_style,
683            hash: cache_key,
684            is_animatable: self.is_style_animatable(&computed_style),
685            computed_at: std::time::Instant::now(),
686        };
687
688        // Cache the result
689        self.computed_cache.insert(cache_key, computed.clone());
690
691        // Update performance statistics
692        let computation_time = start_time.elapsed().as_secs_f32() * 1000.0;
693        self.stats.computations += 1;
694        self.stats.computation_time_ms += computation_time;
695        self.stats.avg_computation_time_ms =
696            self.stats.computation_time_ms / self.stats.computations as f32;
697        self.stats.cache_size = self.computed_cache.len();
698
699        Ok(computed)
700    }
701
702    /// Generate cache key for style computation
703    fn generate_cache_key(
704        &self,
705        component_id: ComponentId,
706        style: &Style,
707        context: &StyleContext,
708    ) -> u64 {
709        use std::collections::hash_map::DefaultHasher;
710        use std::hash::{Hash, Hasher};
711
712        let mut hasher = DefaultHasher::new();
713        component_id.hash(&mut hasher);
714
715        // Hash style properties that affect computation
716        style.color.hash(&mut hasher);
717        style.background_color.hash(&mut hasher);
718        style.font_size.map(|f| f.to_bits()).hash(&mut hasher);
719        style.opacity.map(|f| f.to_bits()).hash(&mut hasher);
720
721        // Hash context
722        context.viewport_width.to_bits().hash(&mut hasher);
723        context.viewport_height.to_bits().hash(&mut hasher);
724        context.device_pixel_ratio.to_bits().hash(&mut hasher);
725
726        hasher.finish()
727    }
728
729    /// Apply CSS inheritance rules
730    fn apply_inheritance(&mut self, style: &mut Style, parent_style: &Style) {
731        // Inherit font properties if not explicitly set
732        if style.font_family.is_none() {
733            style.font_family = parent_style.font_family.clone();
734        }
735        if style.font_size.is_none() {
736            style.font_size = parent_style.font_size;
737        }
738        if style.font_weight.is_none() {
739            style.font_weight = parent_style.font_weight.clone();
740        }
741        if style.color.is_none() {
742            style.color = parent_style.color.clone();
743        }
744        if style.line_height.is_none() {
745            style.line_height = parent_style.line_height;
746        }
747        if style.letter_spacing.is_none() {
748            style.letter_spacing = parent_style.letter_spacing;
749        }
750        if style.text_align.is_none() {
751            style.text_align = parent_style.text_align.clone();
752        }
753
754        self.stats.inheritance_operations += 1;
755    }
756
757    /// Apply CSS rules to a style
758    fn apply_css_rules(
759        &self,
760        style: &mut Style,
761        rules: &[StyleRule],
762        context: &StyleContext,
763    ) -> Result<(), StyleError> {
764        // Sort rules by specificity and source order
765        let mut sorted_rules = rules.to_vec();
766        sorted_rules.sort_by(|a, b| {
767            a.specificity
768                .cmp(&b.specificity)
769                .then(a.source_order.cmp(&b.source_order))
770        });
771
772        // Apply rules in order
773        for rule in sorted_rules {
774            for selector in &rule.selectors {
775                for property in &selector.properties {
776                    self.apply_css_property(style, property, context)?;
777                }
778            }
779        }
780
781        Ok(())
782    }
783
784    /// Apply a single CSS property to a style
785    fn apply_css_property(
786        &self,
787        style: &mut Style,
788        property: &CssProperty,
789        _context: &StyleContext,
790    ) -> Result<(), StyleError> {
791        match property.name.as_str() {
792            "color" => {
793                style.color = Some(self.parse_color(&property.value)?);
794            }
795            "background-color" => {
796                style.background_color = Some(self.parse_color(&property.value)?);
797            }
798            "opacity" => {
799                style.opacity = property.value.parse().ok();
800            }
801            "font-size" => {
802                style.font_size = self.parse_font_size(&property.value);
803            }
804            "font-weight" => {
805                style.font_weight = Some(self.parse_font_weight(&property.value)?);
806            }
807            "font-family" => {
808                style.font_family = Some(property.value.clone());
809            }
810            "text-align" => {
811                style.text_align = Some(self.parse_text_align(&property.value)?);
812            }
813            "border-radius" => {
814                style.border_radius = Some(self.parse_border_radius(&property.value)?);
815            }
816            "z-index" => {
817                style.z_index = property.value.parse().ok();
818            }
819            _ => {
820                // Unknown property - could log warning in debug mode
821            }
822        }
823
824        Ok(())
825    }
826
827    /// Convert Style to LayoutStyle for layout engine integration
828    fn style_to_layout_style(
829        &mut self,
830        style: &Style,
831        context: &StyleContext,
832    ) -> Result<LayoutStyle, StyleError> {
833        let mut layout_style = LayoutStyle::default();
834
835        // Copy layout-related properties from existing layout_style if present
836        if let Some(existing_layout) = &style.layout_style {
837            layout_style = existing_layout.clone();
838        }
839
840        // Apply style properties that affect layout
841        if let Some(opacity) = style.opacity {
842            // Opacity affects layout in some cases (e.g., visibility)
843            if opacity == 0.0 {
844                // Could set display: none equivalent
845            }
846        }
847
848        // Handle border width affecting layout
849        if let Some(border_width) = &style.border_width {
850            // Border width affects content area
851            layout_style.border = *border_width;
852        }
853
854        // Handle transforms that affect layout bounds
855        if let Some(_transform) = &style.transform {
856            // Transforms can affect layout bounds
857            // Implementation depends on transform type
858        }
859
860        // Apply responsive sizing based on viewport
861        self.apply_responsive_sizing(&mut layout_style, context);
862
863        self.stats.layout_conversions += 1;
864        Ok(layout_style)
865    }
866    /// Apply responsive sizing rules
867    fn apply_responsive_sizing(&self, layout_style: &mut LayoutStyle, context: &StyleContext) {
868        // Apply responsive breakpoints and scaling
869        let scale_factor = context.device_pixel_ratio;
870
871        // Scale dimensions if needed
872        if let Dimension::Points(width) = layout_style.width {
873            layout_style.width = Dimension::Points(width * scale_factor);
874        }
875        if let Dimension::Points(height) = layout_style.height {
876            layout_style.height = Dimension::Points(height * scale_factor);
877        }
878    }
879
880    /// Check if a style contains animatable properties
881    fn is_style_animatable(&self, style: &Style) -> bool {
882        style.opacity.is_some()
883            || style.background_color.is_some()
884            || style.color.is_some()
885            || style.transform.is_some()
886            || style.border_radius.is_some()
887            || style.font_size.is_some()
888            || style.transition_duration.is_some()
889    }
890
891    /// Parse color from CSS value
892    fn parse_color(&self, value: &str) -> Result<Color, StyleError> {
893        let value = value.trim();
894
895        if value.starts_with('#') {
896            Ok(Color::Hex(value.to_string()))
897        } else if value.starts_with("rgb(") {
898            // Parse rgb(r, g, b) format
899            let inner = value.trim_start_matches("rgb(").trim_end_matches(')');
900            let parts: Vec<&str> = inner.split(',').collect();
901            if parts.len() == 3 {
902                let r = parts[0]
903                    .trim()
904                    .parse::<u8>()
905                    .map_err(|_| StyleError::ParseError("Invalid red value".to_string()))?
906                    as f32
907                    / 255.0;
908                let g = parts[1]
909                    .trim()
910                    .parse::<u8>()
911                    .map_err(|_| StyleError::ParseError("Invalid green value".to_string()))?
912                    as f32
913                    / 255.0;
914                let b = parts[2]
915                    .trim()
916                    .parse::<u8>()
917                    .map_err(|_| StyleError::ParseError("Invalid blue value".to_string()))?
918                    as f32
919                    / 255.0;
920                Ok(Color::Rgba(r, g, b, 1.0))
921            } else {
922                Err(StyleError::ParseError("Invalid RGB format".to_string()))
923            }
924        } else if value.starts_with("rgba(") {
925            // Parse rgba(r, g, b, a) format
926            let inner = value.trim_start_matches("rgba(").trim_end_matches(')');
927            let parts: Vec<&str> = inner.split(',').collect();
928            if parts.len() == 4 {
929                let r = parts[0]
930                    .trim()
931                    .parse::<u8>()
932                    .map_err(|_| StyleError::ParseError("Invalid red value".to_string()))?
933                    as f32
934                    / 255.0;
935                let g = parts[1]
936                    .trim()
937                    .parse::<u8>()
938                    .map_err(|_| StyleError::ParseError("Invalid green value".to_string()))?
939                    as f32
940                    / 255.0;
941                let b = parts[2]
942                    .trim()
943                    .parse::<u8>()
944                    .map_err(|_| StyleError::ParseError("Invalid blue value".to_string()))?
945                    as f32
946                    / 255.0;
947                let a = parts[3]
948                    .trim()
949                    .parse::<f32>()
950                    .map_err(|_| StyleError::ParseError("Invalid alpha value".to_string()))?;
951                Ok(Color::Rgba(r, g, b, a))
952            } else {
953                Err(StyleError::ParseError("Invalid RGBA format".to_string()))
954            }
955        } else if value == "transparent" {
956            Ok(Color::Transparent)
957        } else if value == "currentColor" {
958            Ok(Color::CurrentColor)
959        } else {
960            Ok(Color::Named(value.to_string()))
961        }
962    }
963
964    /// Parse font size from CSS value
965    fn parse_font_size(&self, value: &str) -> Option<f32> {
966        let value = value.trim();
967        if value.ends_with("px") {
968            value.trim_end_matches("px").parse().ok()
969        } else if value.ends_with("pt") {
970            value
971                .trim_end_matches("pt")
972                .parse::<f32>()
973                .map(|pt| pt * 1.333)
974                .ok()
975        } else if value.ends_with("em") {
976            value
977                .trim_end_matches("em")
978                .parse::<f32>()
979                .map(|em| em * 16.0)
980                .ok()
981        } else {
982            value.parse().ok()
983        }
984    }
985
986    /// Parse font weight from CSS value
987    fn parse_font_weight(&self, value: &str) -> Result<FontWeight, StyleError> {
988        match value.trim() {
989            "thin" | "100" => Ok(FontWeight::Thin),
990            "extra-light" | "200" => Ok(FontWeight::ExtraLight),
991            "light" | "300" => Ok(FontWeight::Light),
992            "normal" | "400" => Ok(FontWeight::Normal),
993            "medium" | "500" => Ok(FontWeight::Medium),
994            "semi-bold" | "600" => Ok(FontWeight::SemiBold),
995            "bold" | "700" => Ok(FontWeight::Bold),
996            "extra-bold" | "800" => Ok(FontWeight::ExtraBold),
997            "black" | "900" => Ok(FontWeight::Black),
998            _ => {
999                if let Ok(numeric) = value.parse::<u16>() {
1000                    Ok(FontWeight::Numeric(numeric))
1001                } else {
1002                    Err(StyleError::ParseError(format!(
1003                        "Invalid font weight: {value}"
1004                    )))
1005                }
1006            }
1007        }
1008    }
1009
1010    /// Parse text alignment from CSS value
1011    fn parse_text_align(&self, value: &str) -> Result<TextAlign, StyleError> {
1012        match value.trim() {
1013            "left" => Ok(TextAlign::Left),
1014            "right" => Ok(TextAlign::Right),
1015            "center" => Ok(TextAlign::Center),
1016            "justify" => Ok(TextAlign::Justify),
1017            "start" => Ok(TextAlign::Start),
1018            "end" => Ok(TextAlign::End),
1019            _ => Err(StyleError::ParseError(format!(
1020                "Invalid text align: {value}"
1021            ))),
1022        }
1023    }
1024
1025    /// Parse border radius from CSS value
1026    fn parse_border_radius(&self, value: &str) -> Result<BorderRadius, StyleError> {
1027        let parts: Vec<&str> = value.split_whitespace().collect();
1028        match parts.len() {
1029            1 => {
1030                let radius = self.parse_length(parts[0])?;
1031                Ok(BorderRadius {
1032                    top_left: radius,
1033                    top_right: radius,
1034                    bottom_right: radius,
1035                    bottom_left: radius,
1036                })
1037            }
1038            2 => {
1039                let radius1 = self.parse_length(parts[0])?;
1040                let radius2 = self.parse_length(parts[1])?;
1041                Ok(BorderRadius {
1042                    top_left: radius1,
1043                    top_right: radius2,
1044                    bottom_right: radius1,
1045                    bottom_left: radius2,
1046                })
1047            }
1048            4 => Ok(BorderRadius {
1049                top_left: self.parse_length(parts[0])?,
1050                top_right: self.parse_length(parts[1])?,
1051                bottom_right: self.parse_length(parts[2])?,
1052                bottom_left: self.parse_length(parts[3])?,
1053            }),
1054            _ => Err(StyleError::ParseError(
1055                "Invalid border radius format".to_string(),
1056            )),
1057        }
1058    }
1059
1060    /// Parse length value (px, pt, em, etc.)
1061    fn parse_length(&self, value: &str) -> Result<f32, StyleError> {
1062        let value = value.trim();
1063        if value.ends_with("px") {
1064            value
1065                .trim_end_matches("px")
1066                .parse()
1067                .map_err(|_| StyleError::ParseError("Invalid pixel value".to_string()))
1068        } else if value.ends_with("pt") {
1069            value
1070                .trim_end_matches("pt")
1071                .parse::<f32>()
1072                .map(|pt| pt * 1.333)
1073                .map_err(|_| StyleError::ParseError("Invalid point value".to_string()))
1074        } else if value.ends_with("em") {
1075            value
1076                .trim_end_matches("em")
1077                .parse::<f32>()
1078                .map(|em| em * 16.0)
1079                .map_err(|_| StyleError::ParseError("Invalid em value".to_string()))
1080        } else {
1081            value
1082                .parse()
1083                .map_err(|_| StyleError::ParseError("Invalid length value".to_string()))
1084        }
1085    }
1086
1087    /// Get performance statistics
1088    pub fn get_stats(&self) -> StyleStats {
1089        let mut stats = self.stats.clone();
1090        stats.cache_hits = self.cache_hit_counter.load(Ordering::Relaxed);
1091        stats.cache_size = self.computed_cache.len();
1092        stats
1093    }
1094
1095    /// Clear style cache (useful for memory management)
1096    pub fn clear_cache(&mut self) {
1097        self.computed_cache.clear();
1098        self.stats.cache_size = 0;
1099    }
1100
1101    /// Set parent-child inheritance relationship
1102    pub fn set_inheritance(&mut self, child_id: ComponentId, parent_id: ComponentId) {
1103        self.inheritance_tree.insert(child_id, parent_id);
1104    }
1105}
1106
1107impl Default for StyleEngine {
1108    fn default() -> Self {
1109        Self::new()
1110    }
1111}
1112
1113impl Default for StyleContext {
1114    fn default() -> Self {
1115        Self {
1116            viewport_width: 1920.0,
1117            viewport_height: 1080.0,
1118            device_pixel_ratio: 1.0,
1119            inherited_style: None,
1120            component_id: None,
1121            theme_variables: HashMap::new(),
1122            performance_monitoring: false,
1123        }
1124    }
1125}
1126
1127impl ComputedStyle {
1128    /// Check if this computed style is expired (for cache invalidation)
1129    pub fn is_expired(&self, max_age_ms: u128) -> bool {
1130        self.computed_at.elapsed().as_millis() > max_age_ms
1131    }
1132
1133    /// Get a specific property value from the computed style
1134    pub fn get_property(&self, name: &str) -> Option<String> {
1135        match name {
1136            "color" => self.style.color.as_ref().map(|c| format!("{c:?}")),
1137            "background-color" => self
1138                .style
1139                .background_color
1140                .as_ref()
1141                .map(|c| format!("{c:?}")),
1142            "opacity" => self.style.opacity.map(|o| o.to_string()),
1143            "font-size" => self.style.font_size.map(|f| format!("{f}px")),
1144            "font-weight" => self.style.font_weight.as_ref().map(|w| format!("{w:?}")),
1145            _ => None,
1146        }
1147    }
1148}