Skip to main content

gpui_rhai/
style.rs

1use std::collections::{BTreeMap, BTreeSet};
2
3use rhai::{
4    Array, CustomType, Dynamic, Engine, EvalAltResult, FLOAT, FuncRegistration, INT,
5    ImmutableString, Map, Position, TypeBuilder,
6};
7use serde::{Deserialize, Serialize};
8use thiserror::Error;
9
10#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
11#[serde(tag = "unit", content = "value", rename_all = "snake_case")]
12pub enum Length {
13    Pixels(f64),
14    Rems(f64),
15    Relative(f64),
16    ThemeSpacing(SpacingToken),
17    ThemeRadius(RadiusToken),
18}
19
20impl<'de> Deserialize<'de> for Length {
21    fn deserialize<Deserializer>(deserializer: Deserializer) -> Result<Self, Deserializer::Error>
22    where
23        Deserializer: serde::Deserializer<'de>,
24    {
25        #[derive(Deserialize)]
26        #[serde(tag = "unit", content = "value", rename_all = "snake_case")]
27        enum Repr {
28            Pixels(f64),
29            Rems(f64),
30            Relative(f64),
31            ThemeSpacing(SpacingToken),
32            ThemeRadius(RadiusToken),
33        }
34
35        match Repr::deserialize(deserializer)? {
36            Repr::Pixels(value) => Self::pixels(value),
37            Repr::Rems(value) => Self::rems(value),
38            Repr::Relative(value) => Self::relative(value),
39            Repr::ThemeSpacing(value) => Ok(Self::ThemeSpacing(value)),
40            Repr::ThemeRadius(value) => Ok(Self::ThemeRadius(value)),
41        }
42        .map_err(serde::de::Error::custom)
43    }
44}
45
46#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
47pub struct AutoLength;
48
49impl CustomType for AutoLength {
50    fn build(mut builder: TypeBuilder<Self>) {
51        builder.with_name("AutoLength");
52    }
53}
54
55#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
56#[serde(tag = "unit", content = "value", rename_all = "snake_case")]
57pub enum SignedLength {
58    Pixels(f64),
59    Rems(f64),
60    Relative(f64),
61}
62
63impl<'de> Deserialize<'de> for SignedLength {
64    fn deserialize<Deserializer>(deserializer: Deserializer) -> Result<Self, Deserializer::Error>
65    where
66        Deserializer: serde::Deserializer<'de>,
67    {
68        #[derive(Deserialize)]
69        #[serde(tag = "unit", content = "value", rename_all = "snake_case")]
70        enum Repr {
71            Pixels(f64),
72            Rems(f64),
73            Relative(f64),
74        }
75
76        match Repr::deserialize(deserializer)? {
77            Repr::Pixels(value) => Self::pixels(value),
78            Repr::Rems(value) => Self::rems(value),
79            Repr::Relative(value) => Self::relative(value),
80        }
81        .map_err(serde::de::Error::custom)
82    }
83}
84
85impl SignedLength {
86    /// Create a finite signed pixel layout offset.
87    ///
88    /// # Errors
89    ///
90    /// Returns [`LengthError`] for non-finite or unrepresentable values.
91    pub fn pixels(value: f64) -> Result<Self, LengthError> {
92        validate_signed(value).map(|()| Self::Pixels(value))
93    }
94
95    /// Create a finite signed rem layout offset.
96    ///
97    /// # Errors
98    ///
99    /// Returns [`LengthError`] for non-finite or unrepresentable values.
100    pub fn rems(value: f64) -> Result<Self, LengthError> {
101        validate_signed(value).map(|()| Self::Rems(value))
102    }
103
104    /// Create a bounded signed parent-relative layout offset.
105    ///
106    /// # Errors
107    ///
108    /// Returns [`LengthError`] outside -10 through 10 parent lengths.
109    pub fn relative(value: f64) -> Result<Self, LengthError> {
110        if value.is_finite() && (-10.0..=10.0).contains(&value) {
111            Ok(Self::Relative(value))
112        } else {
113            Err(LengthError::InvalidSignedRelative(value))
114        }
115    }
116}
117
118impl CustomType for SignedLength {
119    fn build(mut builder: TypeBuilder<Self>) {
120        builder.with_name("SignedLength");
121    }
122}
123
124#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
125#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
126pub enum LayoutLength {
127    Definite(Length),
128    Signed(SignedLength),
129    Auto,
130}
131
132impl From<Length> for LayoutLength {
133    fn from(value: Length) -> Self {
134        Self::Definite(value)
135    }
136}
137
138impl From<SignedLength> for LayoutLength {
139    fn from(value: SignedLength) -> Self {
140        Self::Signed(value)
141    }
142}
143
144#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
145#[serde(rename_all = "snake_case")]
146pub enum SpacingToken {
147    Xxs,
148    Xs,
149    Sm,
150    Md,
151    Lg,
152}
153
154impl SpacingToken {
155    #[must_use]
156    pub const fn as_str(self) -> &'static str {
157        match self {
158            Self::Xxs => "xxs",
159            Self::Xs => "xs",
160            Self::Sm => "sm",
161            Self::Md => "md",
162            Self::Lg => "lg",
163        }
164    }
165
166    fn parse(value: &str) -> Result<Self, LengthError> {
167        match value {
168            "xxs" => Ok(Self::Xxs),
169            "xs" => Ok(Self::Xs),
170            "sm" => Ok(Self::Sm),
171            "md" => Ok(Self::Md),
172            "lg" => Ok(Self::Lg),
173            _ => Err(LengthError::UnknownSpacingToken(value.to_owned())),
174        }
175    }
176}
177
178#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
179#[serde(rename_all = "snake_case")]
180pub enum RadiusToken {
181    Sm,
182    Md,
183    Lg,
184}
185
186impl RadiusToken {
187    #[must_use]
188    pub const fn as_str(self) -> &'static str {
189        match self {
190            Self::Sm => "sm",
191            Self::Md => "md",
192            Self::Lg => "lg",
193        }
194    }
195
196    fn parse(value: &str) -> Result<Self, LengthError> {
197        match value {
198            "sm" => Ok(Self::Sm),
199            "md" => Ok(Self::Md),
200            "lg" => Ok(Self::Lg),
201            _ => Err(LengthError::UnknownRadiusToken(value.to_owned())),
202        }
203    }
204}
205
206impl Length {
207    /// Create a finite, non-negative pixel length.
208    ///
209    /// # Errors
210    ///
211    /// Returns [`LengthError`] for negative or non-finite values.
212    pub fn pixels(value: f64) -> Result<Self, LengthError> {
213        validate_non_negative(value).map(|()| Self::Pixels(value))
214    }
215
216    /// Create a finite, non-negative rem length.
217    ///
218    /// # Errors
219    ///
220    /// Returns [`LengthError`] for negative or non-finite values.
221    pub fn rems(value: f64) -> Result<Self, LengthError> {
222        validate_non_negative(value).map(|()| Self::Rems(value))
223    }
224
225    /// Create a finite relative fraction.
226    ///
227    /// # Errors
228    ///
229    /// Returns [`LengthError`] unless `value` is between zero and one.
230    pub fn relative(value: f64) -> Result<Self, LengthError> {
231        if value.is_finite() && (0.0..=1.0).contains(&value) {
232            Ok(Self::Relative(value))
233        } else {
234            Err(LengthError::InvalidRelative(value))
235        }
236    }
237
238    /// Create one standard semantic spacing reference.
239    ///
240    /// # Errors
241    ///
242    /// Returns [`LengthError`] for an unknown standard token.
243    pub fn theme_spacing(value: &str) -> Result<Self, LengthError> {
244        SpacingToken::parse(value).map(Self::ThemeSpacing)
245    }
246
247    /// Create one standard semantic radius reference.
248    ///
249    /// # Errors
250    ///
251    /// Returns [`LengthError`] for an unknown standard token.
252    pub fn theme_radius(value: &str) -> Result<Self, LengthError> {
253        RadiusToken::parse(value).map(Self::ThemeRadius)
254    }
255
256    #[must_use]
257    pub const fn is_theme_token(self) -> bool {
258        matches!(self, Self::ThemeSpacing(_) | Self::ThemeRadius(_))
259    }
260
261    /// Revalidate a deserialized length.
262    ///
263    /// # Errors
264    ///
265    /// Returns [`LengthError`] when the stored value is outside its unit's
266    /// allowed range.
267    pub fn validate(self) -> Result<(), LengthError> {
268        match self {
269            Self::Pixels(value) | Self::Rems(value) => validate_non_negative(value),
270            Self::Relative(value) => Self::relative(value).map(|_| ()),
271            Self::ThemeSpacing(_) | Self::ThemeRadius(_) => Ok(()),
272        }
273    }
274}
275
276impl CustomType for Length {
277    fn build(mut builder: TypeBuilder<Self>) {
278        builder.with_name("Length");
279    }
280}
281
282#[derive(Clone, Debug, Error, PartialEq)]
283pub enum LengthError {
284    #[error("length must be finite and non-negative, got {0}")]
285    InvalidLength(f64),
286    #[error("relative length must be finite and between 0 and 1, got {0}")]
287    InvalidRelative(f64),
288    #[error("signed relative length must be finite and between -10 and 10, got {0}")]
289    InvalidSignedRelative(f64),
290    #[error("spacing token `{0}` is unknown; expected xs, sm, md, or lg")]
291    UnknownSpacingToken(String),
292    #[error("radius token `{0}` is unknown; expected sm, md, or lg")]
293    UnknownRadiusToken(String),
294}
295
296fn validate_signed(value: f64) -> Result<(), LengthError> {
297    if value.is_finite() && value.abs() <= f64::from(f32::MAX) {
298        Ok(())
299    } else {
300        Err(LengthError::InvalidLength(value))
301    }
302}
303
304fn validate_non_negative(value: f64) -> Result<(), LengthError> {
305    if value.is_finite() && value >= 0.0 && value <= f64::from(f32::MAX) {
306        Ok(())
307    } else {
308        Err(LengthError::InvalidLength(value))
309    }
310}
311
312#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
313pub struct Rgba8(u32);
314
315impl Rgba8 {
316    #[must_use]
317    pub const fn from_rgba_hex(value: u32) -> Self {
318        Self(value)
319    }
320
321    #[must_use]
322    pub const fn from_rgb_hex(value: u32) -> Self {
323        Self((value << 8) | 0xff)
324    }
325
326    #[must_use]
327    pub const fn as_rgba_hex(self) -> u32 {
328        self.0
329    }
330}
331
332#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
333#[serde(tag = "source", content = "value", rename_all = "snake_case")]
334pub enum ColorValue {
335    Literal(Rgba8),
336    Token(String),
337}
338
339impl CustomType for ColorValue {
340    fn build(mut builder: TypeBuilder<Self>) {
341        builder.with_name("ColorValue");
342    }
343}
344
345impl ColorValue {
346    /// Parse a strict, bounded color literal shared by Style and Canvas.
347    ///
348    /// Supported forms are `#rgb`, `#rgba`, `#rrggbb`, `#rrggbbaa`, the CSS
349    /// basic named colors plus `transparent`, comma-separated `rgb/rgba`, and
350    /// `hsl/hsla` with percentage saturation/lightness, and comma-form `hwb`
351    /// with percentage whiteness/blackness plus optional alpha.
352    ///
353    /// # Errors
354    ///
355    /// Returns [`ColorParseError`] for malformed syntax or out-of-range channels.
356    pub fn parse(value: &str) -> Result<Self, ColorParseError> {
357        let value = value.trim().to_ascii_lowercase();
358        if value.is_empty() || value.len() > 256 {
359            return Err(ColorParseError::Invalid(value));
360        }
361        if let Some(color) = parse_named_color(&value) {
362            return Ok(Self::Literal(color));
363        }
364        if let Some(hex) = value.strip_prefix('#') {
365            return parse_hex_color(hex).map(Self::Literal);
366        }
367        if value.starts_with("rgb(") || value.starts_with("rgba(") {
368            return parse_rgb_function(&value).map(Self::Literal);
369        }
370        if value.starts_with("hsl(") || value.starts_with("hsla(") {
371            return parse_hsl_function(&value).map(Self::Literal);
372        }
373        if value.starts_with("hwb(") {
374            return parse_hwb_function(&value).map(Self::Literal);
375        }
376        Err(ColorParseError::Invalid(value))
377    }
378}
379
380#[derive(Clone, Debug, Error, Eq, PartialEq)]
381pub enum ColorParseError {
382    #[error("invalid or unsupported color literal `{0}`")]
383    Invalid(String),
384    #[error("color channel `{0}` is outside its allowed range")]
385    Channel(String),
386}
387
388fn parse_named_color(value: &str) -> Option<Rgba8> {
389    let rgba = match value {
390        "transparent" => 0x0000_0000,
391        "black" => 0x0000_00ff,
392        "silver" => 0xc0c0_c0ff,
393        "gray" | "grey" => 0x8080_80ff,
394        "white" => 0xffff_ffff,
395        "maroon" => 0x8000_00ff,
396        "red" => 0xff00_00ff,
397        "purple" => 0x8000_80ff,
398        "fuchsia" | "magenta" => 0xff00_ffff,
399        "green" => 0x0080_00ff,
400        "lime" => 0x00ff_00ff,
401        "olive" => 0x8080_00ff,
402        "yellow" => 0xffff_00ff,
403        "navy" => 0x0000_80ff,
404        "blue" => 0x0000_ffff,
405        "teal" => 0x0080_80ff,
406        "aqua" | "cyan" => 0x00ff_ffff,
407        _ => return None,
408    };
409    Some(Rgba8::from_rgba_hex(rgba))
410}
411
412fn parse_hex_color(value: &str) -> Result<Rgba8, ColorParseError> {
413    let expanded = match value.len() {
414        3 | 4 => value
415            .chars()
416            .flat_map(|character| [character, character])
417            .collect::<String>(),
418        6 | 8 => value.to_owned(),
419        _ => return Err(ColorParseError::Invalid(format!("#{value}"))),
420    };
421    let parsed = u32::from_str_radix(&expanded, 16)
422        .map_err(|_| ColorParseError::Invalid(format!("#{value}")))?;
423    Ok(if expanded.len() == 6 {
424        Rgba8::from_rgb_hex(parsed)
425    } else {
426        Rgba8::from_rgba_hex(parsed)
427    })
428}
429
430fn function_parts<'a>(value: &'a str, name: &str) -> Result<Vec<&'a str>, ColorParseError> {
431    let body = value
432        .strip_prefix(name)
433        .and_then(|value| value.strip_prefix('('))
434        .and_then(|value| value.strip_suffix(')'))
435        .ok_or_else(|| ColorParseError::Invalid(value.to_owned()))?;
436    let parts = body.split(',').map(str::trim).collect::<Vec<_>>();
437    if parts.iter().any(|part| part.is_empty()) {
438        Err(ColorParseError::Invalid(value.to_owned()))
439    } else {
440        Ok(parts)
441    }
442}
443
444fn parse_rgb_function(value: &str) -> Result<Rgba8, ColorParseError> {
445    let (name, expected) = if value.starts_with("rgba(") {
446        ("rgba", 4)
447    } else {
448        ("rgb", 3)
449    };
450    let parts = function_parts(value, name)?;
451    if parts.len() != expected {
452        return Err(ColorParseError::Invalid(value.to_owned()));
453    }
454    let red = parse_byte(parts[0])?;
455    let green = parse_byte(parts[1])?;
456    let blue = parse_byte(parts[2])?;
457    let alpha = if expected == 4 {
458        parse_alpha(parts[3])?
459    } else {
460        u8::MAX
461    };
462    Ok(rgba_channels(red, green, blue, alpha))
463}
464
465fn parse_hsl_function(value: &str) -> Result<Rgba8, ColorParseError> {
466    let (name, expected) = if value.starts_with("hsla(") {
467        ("hsla", 4)
468    } else {
469        ("hsl", 3)
470    };
471    let parts = function_parts(value, name)?;
472    if parts.len() != expected {
473        return Err(ColorParseError::Invalid(value.to_owned()));
474    }
475    let hue = parse_finite(parts[0])?.rem_euclid(360.0) / 360.0;
476    let saturation = parse_percent(parts[1])?;
477    let lightness = parse_percent(parts[2])?;
478    let alpha = if expected == 4 {
479        parse_alpha(parts[3])?
480    } else {
481        u8::MAX
482    };
483    let (red, green, blue) = hsl_to_rgb(hue, saturation, lightness);
484    Ok(rgba_channels(red, green, blue, alpha))
485}
486
487fn parse_hwb_function(value: &str) -> Result<Rgba8, ColorParseError> {
488    let parts = function_parts(value, "hwb")?;
489    if !(3..=4).contains(&parts.len()) {
490        return Err(ColorParseError::Invalid(value.to_owned()));
491    }
492    let hue = parse_finite(parts[0])?.rem_euclid(360.0) / 360.0;
493    let whiteness = parse_percent(parts[1])?;
494    let blackness = parse_percent(parts[2])?;
495    let alpha = parts
496        .get(3)
497        .map_or(Ok(u8::MAX), |alpha| parse_alpha(alpha))?;
498    let (red, green, blue) = hwb_to_rgb(hue, whiteness, blackness);
499    Ok(rgba_channels(red, green, blue, alpha))
500}
501
502fn parse_byte(value: &str) -> Result<u8, ColorParseError> {
503    value
504        .parse::<u16>()
505        .ok()
506        .and_then(|value| u8::try_from(value).ok())
507        .ok_or_else(|| ColorParseError::Channel(value.to_owned()))
508}
509
510#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
511fn parse_alpha(value: &str) -> Result<u8, ColorParseError> {
512    let alpha = if value.ends_with('%') {
513        parse_percent(value)?
514    } else {
515        let value = parse_finite(value)?;
516        if !(0.0..=1.0).contains(&value) {
517            return Err(ColorParseError::Channel(value.to_string()));
518        }
519        value
520    };
521    Ok((alpha * 255.0).round() as u8)
522}
523
524fn parse_percent(value: &str) -> Result<f64, ColorParseError> {
525    let number = value
526        .strip_suffix('%')
527        .ok_or_else(|| ColorParseError::Channel(value.to_owned()))?;
528    let number = parse_finite(number)?;
529    if (0.0..=100.0).contains(&number) {
530        Ok(number / 100.0)
531    } else {
532        Err(ColorParseError::Channel(value.to_owned()))
533    }
534}
535
536fn parse_finite(value: &str) -> Result<f64, ColorParseError> {
537    value
538        .parse::<f64>()
539        .ok()
540        .filter(|value| value.is_finite())
541        .ok_or_else(|| ColorParseError::Channel(value.to_owned()))
542}
543
544#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
545fn hsl_to_rgb(hue: f64, saturation: f64, lightness: f64) -> (u8, u8, u8) {
546    let channel = |offset: f64| {
547        let k = (offset + hue * 12.0).rem_euclid(12.0);
548        let a = saturation * lightness.min(1.0 - lightness);
549        let value = lightness - a * (-1.0_f64).max((k - 3.0).min(9.0 - k).min(1.0));
550        (value * 255.0).round() as u8
551    };
552    (channel(0.0), channel(8.0), channel(4.0))
553}
554
555#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
556fn hwb_to_rgb(hue: f64, whiteness: f64, blackness: f64) -> (u8, u8, u8) {
557    let total = whiteness + blackness;
558    if total >= 1.0 {
559        let gray = (whiteness / total * 255.0).round() as u8;
560        return (gray, gray, gray);
561    }
562    let (red, green, blue) = hsl_to_rgb(hue, 1.0, 0.5);
563    let convert = |channel: u8| {
564        (f64::from(channel) / 255.0 * (1.0 - total) + whiteness)
565            .mul_add(255.0, 0.0)
566            .round() as u8
567    };
568    (convert(red), convert(green), convert(blue))
569}
570
571fn rgba_channels(red: u8, green: u8, blue: u8, alpha: u8) -> Rgba8 {
572    Rgba8::from_rgba_hex(
573        u32::from(red) << 24 | u32::from(green) << 16 | u32::from(blue) << 8 | u32::from(alpha),
574    )
575}
576
577#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
578#[serde(rename_all = "snake_case")]
579pub enum FlexDirection {
580    Row,
581    Column,
582}
583
584#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
585#[serde(rename_all = "snake_case")]
586pub enum OverflowMode {
587    Visible,
588    Hidden,
589    Scroll,
590}
591
592#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
593#[serde(rename_all = "snake_case")]
594pub enum PositionMode {
595    Relative,
596    Absolute,
597}
598
599#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
600#[serde(rename_all = "snake_case")]
601pub enum Align {
602    Start,
603    Center,
604    End,
605    Stretch,
606}
607
608#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
609#[serde(rename_all = "snake_case")]
610pub enum Justify {
611    Start,
612    Center,
613    End,
614    Between,
615    Around,
616}
617
618#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
619#[serde(rename_all = "snake_case")]
620pub enum DisplayMode {
621    Block,
622    Flex,
623    Grid,
624    None,
625}
626
627#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
628#[serde(rename_all = "snake_case")]
629pub enum FlexWrapMode {
630    NoWrap,
631    Wrap,
632    WrapReverse,
633}
634
635#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
636#[serde(rename_all = "snake_case")]
637pub enum TextAlignMode {
638    Start,
639    Center,
640    End,
641}
642
643#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
644#[serde(rename_all = "snake_case")]
645pub enum WhiteSpaceMode {
646    Normal,
647    NoWrap,
648}
649
650#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
651#[serde(rename_all = "snake_case")]
652pub enum FontSlant {
653    Normal,
654    Italic,
655}
656
657#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
658#[serde(rename_all = "snake_case")]
659pub enum BorderLineStyle {
660    Solid,
661    Dashed,
662}
663
664#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
665#[serde(rename_all = "snake_case")]
666pub enum CursorKind {
667    Default,
668    Pointer,
669    Text,
670    Move,
671    Crosshair,
672    NotAllowed,
673    ResizeHorizontal,
674    ResizeVertical,
675}
676
677#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
678#[serde(rename_all = "snake_case")]
679pub enum HitTestBehavior {
680    Block,
681    BlockExceptScroll,
682}
683
684#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
685pub struct ShadowSpec {
686    pub x: f64,
687    pub y: f64,
688    pub blur: f64,
689    pub spread: f64,
690    pub color: ColorValue,
691}
692
693impl ShadowSpec {
694    /// Create one validated box shadow.
695    ///
696    /// # Errors
697    ///
698    /// Returns [`StyleValueError`] for non-finite offsets or negative radii.
699    pub fn new(
700        x: f64,
701        y: f64,
702        blur: f64,
703        spread: f64,
704        color: ColorValue,
705    ) -> Result<Self, StyleValueError> {
706        if !x.is_finite()
707            || !y.is_finite()
708            || x.abs() > f64::from(f32::MAX)
709            || y.abs() > f64::from(f32::MAX)
710            || !blur.is_finite()
711            || blur < 0.0
712            || blur > f64::from(f32::MAX)
713            || !spread.is_finite()
714            || spread < 0.0
715            || spread > f64::from(f32::MAX)
716        {
717            return Err(StyleValueError::InvalidShadow);
718        }
719        Ok(Self {
720            x,
721            y,
722            blur,
723            spread,
724            color,
725        })
726    }
727}
728
729impl CustomType for ShadowSpec {
730    fn build(mut builder: TypeBuilder<Self>) {
731        builder.with_name("ShadowSpec");
732    }
733}
734
735#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
736pub struct LinearGradientSpec {
737    pub angle_degrees: f64,
738    pub from: ColorValue,
739    pub to: ColorValue,
740}
741
742impl LinearGradientSpec {
743    /// Create a two-stop gradient with a normalized finite angle.
744    ///
745    /// # Errors
746    ///
747    /// Returns [`StyleValueError`] for a non-finite angle.
748    pub fn new(
749        angle_degrees: f64,
750        from: ColorValue,
751        to: ColorValue,
752    ) -> Result<Self, StyleValueError> {
753        if !angle_degrees.is_finite() {
754            return Err(StyleValueError::InvalidGradientAngle);
755        }
756        Ok(Self {
757            angle_degrees: angle_degrees.rem_euclid(360.0),
758            from,
759            to,
760        })
761    }
762}
763
764impl CustomType for LinearGradientSpec {
765    fn build(mut builder: TypeBuilder<Self>) {
766        builder.with_name("LinearGradientSpec");
767    }
768}
769
770#[derive(Clone, Debug, Error, PartialEq)]
771pub enum StyleValueError {
772    #[error("shadow offsets must be finite and blur/spread must be finite and non-negative")]
773    InvalidShadow,
774    #[error("gradient angle must be finite")]
775    InvalidGradientAngle,
776    #[error("opacity must be finite and between zero and one, got {0}")]
777    InvalidOpacity(f64),
778    #[error("translation must be finite, got {0}")]
779    InvalidTranslation(f64),
780    #[error("flex grow weight must be finite, positive, and representable by GPUI, got {0}")]
781    InvalidFlexGrowWeight(f64),
782    #[error("font weight must be between 1 and 1000, got {0}")]
783    InvalidFontWeight(i64),
784    #[error("font family must be a non-empty string no longer than 256 bytes")]
785    InvalidFontFamily,
786    #[error("font fallback list must contain 1-16 unique non-empty family names")]
787    InvalidFontFallbacks,
788    #[error("unknown typography role `{0}`")]
789    UnknownTypographyRole(String),
790    #[error("OpenType feature tag must be four ASCII alphanumeric characters")]
791    InvalidFontFeatureTag,
792    #[error("OpenType feature value must be between 0 and 65535, got {0}")]
793    InvalidFontFeatureValue(i64),
794    #[error("line clamp must be between 1 and 10000, got {0}")]
795    InvalidLineClamp(i64),
796    #[error("grid count/span must be between 1 and 1024, got {0}")]
797    InvalidGridValue(i64),
798}
799
800#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
801pub struct EdgeLengths {
802    #[serde(default, skip_serializing_if = "Option::is_none")]
803    pub top: Option<Length>,
804    #[serde(default, skip_serializing_if = "Option::is_none")]
805    pub right: Option<Length>,
806    #[serde(default, skip_serializing_if = "Option::is_none")]
807    pub bottom: Option<Length>,
808    #[serde(default, skip_serializing_if = "Option::is_none")]
809    pub left: Option<Length>,
810    #[serde(default, skip_serializing_if = "Option::is_none")]
811    pub start: Option<Length>,
812    #[serde(default, skip_serializing_if = "Option::is_none")]
813    pub end: Option<Length>,
814}
815
816impl EdgeLengths {
817    #[must_use]
818    pub fn all(value: Length) -> Self {
819        Self {
820            top: Some(value),
821            right: Some(value),
822            bottom: Some(value),
823            left: Some(value),
824            start: None,
825            end: None,
826        }
827    }
828
829    #[must_use]
830    pub fn horizontal(value: Length) -> Self {
831        Self {
832            right: Some(value),
833            left: Some(value),
834            ..Self::default()
835        }
836    }
837
838    #[must_use]
839    pub fn vertical(value: Length) -> Self {
840        Self {
841            top: Some(value),
842            bottom: Some(value),
843            ..Self::default()
844        }
845    }
846
847    fn merge(&mut self, overlay: &Self) {
848        merge_option(&mut self.top, overlay.top);
849        merge_option(&mut self.right, overlay.right);
850        merge_option(&mut self.bottom, overlay.bottom);
851        merge_option(&mut self.left, overlay.left);
852        merge_option(&mut self.start, overlay.start);
853        merge_option(&mut self.end, overlay.end);
854    }
855}
856
857#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
858pub struct LayoutEdgeLengths {
859    #[serde(default, skip_serializing_if = "Option::is_none")]
860    pub top: Option<LayoutLength>,
861    #[serde(default, skip_serializing_if = "Option::is_none")]
862    pub right: Option<LayoutLength>,
863    #[serde(default, skip_serializing_if = "Option::is_none")]
864    pub bottom: Option<LayoutLength>,
865    #[serde(default, skip_serializing_if = "Option::is_none")]
866    pub left: Option<LayoutLength>,
867    #[serde(default, skip_serializing_if = "Option::is_none")]
868    pub start: Option<LayoutLength>,
869    #[serde(default, skip_serializing_if = "Option::is_none")]
870    pub end: Option<LayoutLength>,
871}
872
873impl LayoutEdgeLengths {
874    #[must_use]
875    pub fn all(value: LayoutLength) -> Self {
876        Self {
877            top: Some(value),
878            right: Some(value),
879            bottom: Some(value),
880            left: Some(value),
881            start: None,
882            end: None,
883        }
884    }
885
886    #[must_use]
887    pub fn horizontal(value: LayoutLength) -> Self {
888        Self {
889            right: Some(value),
890            left: Some(value),
891            ..Self::default()
892        }
893    }
894
895    #[must_use]
896    pub fn vertical(value: LayoutLength) -> Self {
897        Self {
898            top: Some(value),
899            bottom: Some(value),
900            ..Self::default()
901        }
902    }
903
904    fn merge(&mut self, overlay: &Self) {
905        merge_option(&mut self.top, overlay.top);
906        merge_option(&mut self.right, overlay.right);
907        merge_option(&mut self.bottom, overlay.bottom);
908        merge_option(&mut self.left, overlay.left);
909        merge_option(&mut self.start, overlay.start);
910        merge_option(&mut self.end, overlay.end);
911    }
912}
913
914#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
915pub struct CornerLengths {
916    pub top_left: Option<Length>,
917    pub top_right: Option<Length>,
918    pub bottom_right: Option<Length>,
919    pub bottom_left: Option<Length>,
920}
921
922impl CornerLengths {
923    #[must_use]
924    pub fn all(value: Length) -> Self {
925        Self {
926            top_left: Some(value),
927            top_right: Some(value),
928            bottom_right: Some(value),
929            bottom_left: Some(value),
930        }
931    }
932
933    fn merge(&mut self, overlay: &Self) {
934        merge_option(&mut self.top_left, overlay.top_left);
935        merge_option(&mut self.top_right, overlay.top_right);
936        merge_option(&mut self.bottom_right, overlay.bottom_right);
937        merge_option(&mut self.bottom_left, overlay.bottom_left);
938    }
939}
940
941#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
942pub struct StyleProperties {
943    pub display: Option<DisplayMode>,
944    pub direction: Option<FlexDirection>,
945    pub flex_wrap: Option<FlexWrapMode>,
946    pub align: Option<Align>,
947    pub align_self: Option<Align>,
948    pub justify: Option<Justify>,
949    pub width: Option<LayoutLength>,
950    pub height: Option<LayoutLength>,
951    pub min_width: Option<LayoutLength>,
952    pub max_width: Option<LayoutLength>,
953    pub min_height: Option<LayoutLength>,
954    pub max_height: Option<LayoutLength>,
955    pub gap: Option<Length>,
956    #[serde(default)]
957    pub padding: EdgeLengths,
958    #[serde(default)]
959    pub margin: LayoutEdgeLengths,
960    pub background: Option<ColorValue>,
961    pub text_color: Option<ColorValue>,
962    pub border_color: Option<ColorValue>,
963    pub border_style: Option<BorderLineStyle>,
964    #[serde(default)]
965    pub border_widths: EdgeLengths,
966    #[serde(default)]
967    pub radii: CornerLengths,
968    pub typography: Option<String>,
969    pub font_size: Option<Length>,
970    pub flex_grow: Option<bool>,
971    pub flex_grow_weight: Option<f64>,
972    pub flex_shrink: Option<bool>,
973    pub flex_basis: Option<LayoutLength>,
974    pub grid_columns: Option<u16>,
975    pub grid_rows: Option<u16>,
976    pub column_span: Option<u16>,
977    pub row_span: Option<u16>,
978    pub clip: Option<bool>,
979    pub overflow_x: Option<OverflowMode>,
980    pub overflow_y: Option<OverflowMode>,
981    pub position: Option<PositionMode>,
982    pub top: Option<LayoutLength>,
983    pub right: Option<LayoutLength>,
984    pub bottom: Option<LayoutLength>,
985    pub left: Option<LayoutLength>,
986    pub opacity: Option<f64>,
987    pub inset_start: Option<LayoutLength>,
988    pub inset_end: Option<LayoutLength>,
989    pub visible: Option<bool>,
990    pub cursor: Option<CursorKind>,
991    pub hit_test: Option<HitTestBehavior>,
992    pub font_family: Option<String>,
993    pub font_fallbacks: Option<Vec<String>>,
994    pub font_features: Option<BTreeMap<String, u32>>,
995    pub font_weight: Option<u16>,
996    pub font_slant: Option<FontSlant>,
997    pub line_height: Option<Length>,
998    pub text_align: Option<TextAlignMode>,
999    pub white_space: Option<WhiteSpaceMode>,
1000    pub text_ellipsis: Option<bool>,
1001    pub line_clamp: Option<usize>,
1002    pub shadows: Option<Vec<ShadowSpec>>,
1003    pub gradient: Option<LinearGradientSpec>,
1004    pub translate_x: Option<f64>,
1005    pub translate_y: Option<f64>,
1006}
1007
1008impl StyleProperties {
1009    fn merge(&mut self, overlay: &Self) {
1010        merge_option(&mut self.display, overlay.display);
1011        merge_option(&mut self.direction, overlay.direction);
1012        merge_option(&mut self.flex_wrap, overlay.flex_wrap);
1013        merge_option(&mut self.align, overlay.align);
1014        merge_option(&mut self.align_self, overlay.align_self);
1015        merge_option(&mut self.justify, overlay.justify);
1016        merge_option(&mut self.width, overlay.width);
1017        merge_option(&mut self.height, overlay.height);
1018        merge_option(&mut self.min_width, overlay.min_width);
1019        merge_option(&mut self.max_width, overlay.max_width);
1020        merge_option(&mut self.min_height, overlay.min_height);
1021        merge_option(&mut self.max_height, overlay.max_height);
1022        merge_option(&mut self.gap, overlay.gap);
1023        self.padding.merge(&overlay.padding);
1024        self.margin.merge(&overlay.margin);
1025        merge_option(&mut self.background, overlay.background.clone());
1026        merge_option(&mut self.text_color, overlay.text_color.clone());
1027        merge_option(&mut self.border_color, overlay.border_color.clone());
1028        merge_option(&mut self.border_style, overlay.border_style);
1029        self.border_widths.merge(&overlay.border_widths);
1030        self.radii.merge(&overlay.radii);
1031        merge_option(&mut self.typography, overlay.typography.clone());
1032        merge_option(&mut self.font_size, overlay.font_size);
1033        if let Some(weight) = overlay.flex_grow_weight {
1034            self.flex_grow = None;
1035            self.flex_grow_weight = Some(weight);
1036        } else if let Some(grow) = overlay.flex_grow {
1037            self.flex_grow = Some(grow);
1038            self.flex_grow_weight = None;
1039        }
1040        merge_option(&mut self.flex_shrink, overlay.flex_shrink);
1041        merge_option(&mut self.flex_basis, overlay.flex_basis);
1042        merge_option(&mut self.grid_columns, overlay.grid_columns);
1043        merge_option(&mut self.grid_rows, overlay.grid_rows);
1044        merge_option(&mut self.column_span, overlay.column_span);
1045        merge_option(&mut self.row_span, overlay.row_span);
1046        merge_option(&mut self.clip, overlay.clip);
1047        merge_option(&mut self.overflow_x, overlay.overflow_x);
1048        merge_option(&mut self.overflow_y, overlay.overflow_y);
1049        merge_option(&mut self.position, overlay.position);
1050        merge_option(&mut self.top, overlay.top);
1051        merge_option(&mut self.right, overlay.right);
1052        merge_option(&mut self.bottom, overlay.bottom);
1053        merge_option(&mut self.left, overlay.left);
1054        merge_option(&mut self.inset_start, overlay.inset_start);
1055        merge_option(&mut self.inset_end, overlay.inset_end);
1056        merge_option(&mut self.opacity, overlay.opacity);
1057        merge_option(&mut self.visible, overlay.visible);
1058        merge_option(&mut self.cursor, overlay.cursor);
1059        merge_option(&mut self.hit_test, overlay.hit_test);
1060        merge_option(&mut self.font_family, overlay.font_family.clone());
1061        merge_option(&mut self.font_fallbacks, overlay.font_fallbacks.clone());
1062        merge_option(&mut self.font_features, overlay.font_features.clone());
1063        merge_option(&mut self.font_weight, overlay.font_weight);
1064        merge_option(&mut self.font_slant, overlay.font_slant);
1065        merge_option(&mut self.line_height, overlay.line_height);
1066        merge_option(&mut self.text_align, overlay.text_align);
1067        merge_option(&mut self.white_space, overlay.white_space);
1068        merge_option(&mut self.text_ellipsis, overlay.text_ellipsis);
1069        merge_option(&mut self.line_clamp, overlay.line_clamp);
1070        merge_option(&mut self.shadows, overlay.shadows.clone());
1071        merge_option(&mut self.gradient, overlay.gradient.clone());
1072        merge_option(&mut self.translate_x, overlay.translate_x);
1073        merge_option(&mut self.translate_y, overlay.translate_y);
1074    }
1075}
1076
1077fn merge_option<T>(base: &mut Option<T>, overlay: Option<T>) {
1078    if overlay.is_some() {
1079        *base = overlay;
1080    }
1081}
1082
1083#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
1084pub struct Style {
1085    #[serde(default)]
1086    pub base: StyleProperties,
1087    #[serde(default, skip_serializing_if = "Option::is_none")]
1088    pub hover: Option<StyleProperties>,
1089    #[serde(default, skip_serializing_if = "Option::is_none")]
1090    pub active: Option<StyleProperties>,
1091    #[serde(default, skip_serializing_if = "Option::is_none")]
1092    pub focus: Option<StyleProperties>,
1093    #[serde(default, skip_serializing_if = "Option::is_none")]
1094    pub disabled: Option<StyleProperties>,
1095}
1096
1097impl Style {
1098    #[must_use]
1099    pub fn new() -> Self {
1100        Self::default()
1101    }
1102
1103    #[must_use]
1104    pub fn merged(mut self, overlay: &Self) -> Self {
1105        self.merge_in_place(overlay);
1106        self
1107    }
1108
1109    pub(crate) fn merge_in_place(&mut self, overlay: &Self) {
1110        self.base.merge(&overlay.base);
1111        merge_pseudo(&mut self.hover, overlay.hover.as_ref());
1112        merge_pseudo(&mut self.active, overlay.active.as_ref());
1113        merge_pseudo(&mut self.focus, overlay.focus.as_ref());
1114        merge_pseudo(&mut self.disabled, overlay.disabled.as_ref());
1115    }
1116
1117    #[must_use]
1118    pub fn resolve(&self, state: &InteractionState) -> StyleProperties {
1119        let mut resolved = self.base.clone();
1120        if state.contains(PseudoState::Hovered)
1121            && let Some(hover) = &self.hover
1122        {
1123            resolved.merge(hover);
1124        }
1125        if state.contains(PseudoState::Active)
1126            && let Some(active) = &self.active
1127        {
1128            resolved.merge(active);
1129        }
1130        if state.contains(PseudoState::Focused)
1131            && let Some(focus) = &self.focus
1132        {
1133            resolved.merge(focus);
1134        }
1135        if state.contains(PseudoState::Disabled)
1136            && let Some(disabled) = &self.disabled
1137        {
1138            resolved.merge(disabled);
1139        }
1140        resolved
1141    }
1142
1143    #[must_use]
1144    pub fn width(mut self, value: Length) -> Self {
1145        self.base.width = Some(value.into());
1146        self
1147    }
1148
1149    #[must_use]
1150    pub fn height(mut self, value: Length) -> Self {
1151        self.base.height = Some(value.into());
1152        self
1153    }
1154
1155    #[must_use]
1156    pub fn min_width(mut self, value: Length) -> Self {
1157        self.base.min_width = Some(value.into());
1158        self
1159    }
1160
1161    #[must_use]
1162    pub fn min_height(mut self, value: Length) -> Self {
1163        self.base.min_height = Some(value.into());
1164        self
1165    }
1166
1167    #[must_use]
1168    pub fn max_height(mut self, value: Length) -> Self {
1169        self.base.max_height = Some(value.into());
1170        self
1171    }
1172
1173    #[must_use]
1174    pub fn max_width(mut self, value: Length) -> Self {
1175        self.base.max_width = Some(value.into());
1176        self
1177    }
1178
1179    #[must_use]
1180    pub fn gap(mut self, value: Length) -> Self {
1181        self.base.gap = Some(value);
1182        self
1183    }
1184
1185    #[must_use]
1186    pub fn padding(mut self, value: Length) -> Self {
1187        self.base.padding = EdgeLengths::all(value);
1188        self
1189    }
1190
1191    #[must_use]
1192    pub fn padding_x(mut self, value: Length) -> Self {
1193        self.base.padding.merge(&EdgeLengths::horizontal(value));
1194        self
1195    }
1196
1197    #[must_use]
1198    pub fn padding_y(mut self, value: Length) -> Self {
1199        self.base.padding.merge(&EdgeLengths::vertical(value));
1200        self
1201    }
1202
1203    #[must_use]
1204    pub fn padding_top(mut self, value: Length) -> Self {
1205        self.base.padding.top = Some(value);
1206        self
1207    }
1208
1209    #[must_use]
1210    pub fn padding_right(mut self, value: Length) -> Self {
1211        self.base.padding.right = Some(value);
1212        self
1213    }
1214
1215    #[must_use]
1216    pub fn padding_bottom(mut self, value: Length) -> Self {
1217        self.base.padding.bottom = Some(value);
1218        self
1219    }
1220
1221    #[must_use]
1222    pub fn padding_left(mut self, value: Length) -> Self {
1223        self.base.padding.left = Some(value);
1224        self
1225    }
1226
1227    #[must_use]
1228    pub fn margin(mut self, value: Length) -> Self {
1229        self.base.margin = LayoutEdgeLengths::all(value.into());
1230        self
1231    }
1232
1233    #[must_use]
1234    pub fn margin_x(mut self, value: Length) -> Self {
1235        self.base
1236            .margin
1237            .merge(&LayoutEdgeLengths::horizontal(value.into()));
1238        self
1239    }
1240
1241    #[must_use]
1242    pub fn margin_y(mut self, value: Length) -> Self {
1243        self.base
1244            .margin
1245            .merge(&LayoutEdgeLengths::vertical(value.into()));
1246        self
1247    }
1248
1249    #[must_use]
1250    pub fn margin_top(mut self, value: Length) -> Self {
1251        self.base.margin.top = Some(value.into());
1252        self
1253    }
1254
1255    #[must_use]
1256    pub fn margin_right(mut self, value: Length) -> Self {
1257        self.base.margin.right = Some(value.into());
1258        self
1259    }
1260
1261    #[must_use]
1262    pub fn margin_bottom(mut self, value: Length) -> Self {
1263        self.base.margin.bottom = Some(value.into());
1264        self
1265    }
1266
1267    #[must_use]
1268    pub fn margin_left(mut self, value: Length) -> Self {
1269        self.base.margin.left = Some(value.into());
1270        self
1271    }
1272
1273    #[must_use]
1274    pub fn padding_start(mut self, value: Length) -> Self {
1275        self.base.padding.start = Some(value);
1276        self
1277    }
1278
1279    #[must_use]
1280    pub fn padding_end(mut self, value: Length) -> Self {
1281        self.base.padding.end = Some(value);
1282        self
1283    }
1284
1285    #[must_use]
1286    pub fn margin_start(mut self, value: Length) -> Self {
1287        self.base.margin.start = Some(value.into());
1288        self
1289    }
1290
1291    #[must_use]
1292    pub fn margin_end(mut self, value: Length) -> Self {
1293        self.base.margin.end = Some(value.into());
1294        self
1295    }
1296
1297    #[must_use]
1298    pub fn background(mut self, value: ColorValue) -> Self {
1299        self.base.background = Some(value);
1300        self.base.gradient = None;
1301        self
1302    }
1303
1304    #[must_use]
1305    pub fn text_color(mut self, value: ColorValue) -> Self {
1306        self.base.text_color = Some(value);
1307        self
1308    }
1309
1310    #[must_use]
1311    pub fn radius(mut self, value: Length) -> Self {
1312        self.base.radii = CornerLengths::all(value);
1313        self
1314    }
1315
1316    #[must_use]
1317    pub fn radius_top_left(mut self, value: Length) -> Self {
1318        self.base.radii.top_left = Some(value);
1319        self
1320    }
1321
1322    #[must_use]
1323    pub fn radius_top_right(mut self, value: Length) -> Self {
1324        self.base.radii.top_right = Some(value);
1325        self
1326    }
1327
1328    #[must_use]
1329    pub fn radius_bottom_right(mut self, value: Length) -> Self {
1330        self.base.radii.bottom_right = Some(value);
1331        self
1332    }
1333
1334    #[must_use]
1335    pub fn radius_bottom_left(mut self, value: Length) -> Self {
1336        self.base.radii.bottom_left = Some(value);
1337        self
1338    }
1339
1340    #[must_use]
1341    pub fn border(mut self, value: Length) -> Self {
1342        self.base.border_widths = EdgeLengths::all(value);
1343        self
1344    }
1345
1346    #[must_use]
1347    pub fn border_top(mut self, value: Length) -> Self {
1348        self.base.border_widths.top = Some(value);
1349        self
1350    }
1351
1352    #[must_use]
1353    pub fn border_right(mut self, value: Length) -> Self {
1354        self.base.border_widths.right = Some(value);
1355        self
1356    }
1357
1358    #[must_use]
1359    pub fn border_bottom(mut self, value: Length) -> Self {
1360        self.base.border_widths.bottom = Some(value);
1361        self
1362    }
1363
1364    #[must_use]
1365    pub fn border_left(mut self, value: Length) -> Self {
1366        self.base.border_widths.left = Some(value);
1367        self
1368    }
1369
1370    #[must_use]
1371    pub fn border_start(mut self, value: Length) -> Self {
1372        self.base.border_widths.start = Some(value);
1373        self
1374    }
1375
1376    #[must_use]
1377    pub fn border_end(mut self, value: Length) -> Self {
1378        self.base.border_widths.end = Some(value);
1379        self
1380    }
1381
1382    #[must_use]
1383    pub fn border_color(mut self, value: ColorValue) -> Self {
1384        self.base.border_color = Some(value);
1385        self
1386    }
1387
1388    #[must_use]
1389    pub fn border_solid(mut self) -> Self {
1390        self.base.border_style = Some(BorderLineStyle::Solid);
1391        self
1392    }
1393
1394    #[must_use]
1395    pub fn border_dashed(mut self) -> Self {
1396        self.base.border_style = Some(BorderLineStyle::Dashed);
1397        self
1398    }
1399
1400    #[must_use]
1401    pub fn font_size(mut self, value: Length) -> Self {
1402        self.base.font_size = Some(value);
1403        self
1404    }
1405
1406    #[must_use]
1407    pub fn block(mut self) -> Self {
1408        self.base.display = Some(DisplayMode::Block);
1409        self
1410    }
1411
1412    #[must_use]
1413    pub fn grid(mut self) -> Self {
1414        self.base.display = Some(DisplayMode::Grid);
1415        self
1416    }
1417
1418    #[must_use]
1419    pub fn hidden(mut self) -> Self {
1420        self.base.display = Some(DisplayMode::None);
1421        self
1422    }
1423
1424    #[must_use]
1425    pub fn flex_row(mut self) -> Self {
1426        self.base.display = Some(DisplayMode::Flex);
1427        self.base.direction = Some(FlexDirection::Row);
1428        self
1429    }
1430
1431    #[must_use]
1432    pub fn flex_col(mut self) -> Self {
1433        self.base.display = Some(DisplayMode::Flex);
1434        self.base.direction = Some(FlexDirection::Column);
1435        self
1436    }
1437
1438    #[must_use]
1439    pub fn items_center(mut self) -> Self {
1440        self.base.align = Some(Align::Center);
1441        self
1442    }
1443
1444    #[must_use]
1445    pub fn items_start(mut self) -> Self {
1446        self.base.align = Some(Align::Start);
1447        self
1448    }
1449
1450    #[must_use]
1451    pub fn items_end(mut self) -> Self {
1452        self.base.align = Some(Align::End);
1453        self
1454    }
1455
1456    #[must_use]
1457    pub fn self_start(mut self) -> Self {
1458        self.base.align_self = Some(Align::Start);
1459        self
1460    }
1461
1462    #[must_use]
1463    pub fn self_center(mut self) -> Self {
1464        self.base.align_self = Some(Align::Center);
1465        self
1466    }
1467
1468    #[must_use]
1469    pub fn self_end(mut self) -> Self {
1470        self.base.align_self = Some(Align::End);
1471        self
1472    }
1473
1474    #[must_use]
1475    pub fn self_stretch(mut self) -> Self {
1476        self.base.align_self = Some(Align::Stretch);
1477        self
1478    }
1479
1480    #[must_use]
1481    pub fn justify_center(mut self) -> Self {
1482        self.base.justify = Some(Justify::Center);
1483        self
1484    }
1485
1486    #[must_use]
1487    pub fn justify_start(mut self) -> Self {
1488        self.base.justify = Some(Justify::Start);
1489        self
1490    }
1491
1492    #[must_use]
1493    pub fn justify_end(mut self) -> Self {
1494        self.base.justify = Some(Justify::End);
1495        self
1496    }
1497
1498    #[must_use]
1499    pub fn justify_between(mut self) -> Self {
1500        self.base.justify = Some(Justify::Between);
1501        self
1502    }
1503
1504    #[must_use]
1505    pub fn justify_around(mut self) -> Self {
1506        self.base.justify = Some(Justify::Around);
1507        self
1508    }
1509
1510    #[must_use]
1511    pub fn flex_grow(mut self) -> Self {
1512        self.base.flex_grow = Some(true);
1513        self.base.flex_grow_weight = None;
1514        self
1515    }
1516
1517    /// Set the positive relative rate used to distribute free main-axis space.
1518    ///
1519    /// # Errors
1520    ///
1521    /// Returns [`StyleValueError`] for zero, negative, non-finite, or
1522    /// unrepresentable weights.
1523    pub fn flex_grow_weight(mut self, weight: f64) -> Result<Self, StyleValueError> {
1524        validate_flex_grow_weight(weight)?;
1525        self.base.flex_grow = None;
1526        self.base.flex_grow_weight = Some(weight);
1527        Ok(self)
1528    }
1529
1530    #[must_use]
1531    pub fn flex_shrink(mut self, shrink: bool) -> Self {
1532        self.base.flex_shrink = Some(shrink);
1533        self
1534    }
1535
1536    #[must_use]
1537    pub fn flex_basis(mut self, basis: Length) -> Self {
1538        self.base.flex_basis = Some(basis.into());
1539        self
1540    }
1541
1542    #[must_use]
1543    pub fn flex_wrap(mut self, mode: FlexWrapMode) -> Self {
1544        self.base.flex_wrap = Some(mode);
1545        self
1546    }
1547
1548    /// Set a bounded explicit grid column count.
1549    ///
1550    /// # Errors
1551    ///
1552    /// Returns [`StyleValueError`] outside 1..=1024.
1553    pub fn grid_columns(mut self, columns: i64) -> Result<Self, StyleValueError> {
1554        self.base.grid_columns = Some(grid_value(columns)?);
1555        self.base.display = Some(DisplayMode::Grid);
1556        Ok(self)
1557    }
1558
1559    /// Set a bounded explicit grid row count.
1560    ///
1561    /// # Errors
1562    ///
1563    /// Returns [`StyleValueError`] outside 1..=1024.
1564    pub fn grid_rows(mut self, rows: i64) -> Result<Self, StyleValueError> {
1565        self.base.grid_rows = Some(grid_value(rows)?);
1566        self.base.display = Some(DisplayMode::Grid);
1567        Ok(self)
1568    }
1569
1570    /// Set a bounded grid column span.
1571    ///
1572    /// # Errors
1573    ///
1574    /// Returns [`StyleValueError`] outside 1..=1024.
1575    pub fn column_span(mut self, span: i64) -> Result<Self, StyleValueError> {
1576        self.base.column_span = Some(grid_value(span)?);
1577        Ok(self)
1578    }
1579
1580    /// Set a bounded grid row span.
1581    ///
1582    /// # Errors
1583    ///
1584    /// Returns [`StyleValueError`] outside 1..=1024.
1585    pub fn row_span(mut self, span: i64) -> Result<Self, StyleValueError> {
1586        self.base.row_span = Some(grid_value(span)?);
1587        Ok(self)
1588    }
1589
1590    #[must_use]
1591    pub fn clip(mut self) -> Self {
1592        self.base.clip = Some(true);
1593        self
1594    }
1595
1596    #[must_use]
1597    pub fn overflow_x_scroll(mut self) -> Self {
1598        self.base.overflow_x = Some(OverflowMode::Scroll);
1599        self
1600    }
1601
1602    #[must_use]
1603    pub fn overflow_y_scroll(mut self) -> Self {
1604        self.base.overflow_y = Some(OverflowMode::Scroll);
1605        self
1606    }
1607
1608    #[must_use]
1609    pub fn overflow_scroll(mut self) -> Self {
1610        self.base.overflow_x = Some(OverflowMode::Scroll);
1611        self.base.overflow_y = Some(OverflowMode::Scroll);
1612        self
1613    }
1614
1615    #[must_use]
1616    pub fn overflow_hidden(mut self) -> Self {
1617        self.base.overflow_x = Some(OverflowMode::Hidden);
1618        self.base.overflow_y = Some(OverflowMode::Hidden);
1619        self
1620    }
1621
1622    #[must_use]
1623    pub fn relative(mut self) -> Self {
1624        self.base.position = Some(PositionMode::Relative);
1625        self
1626    }
1627
1628    #[must_use]
1629    pub fn absolute(mut self) -> Self {
1630        self.base.position = Some(PositionMode::Absolute);
1631        self
1632    }
1633
1634    #[must_use]
1635    pub fn top(mut self, value: Length) -> Self {
1636        self.base.top = Some(value.into());
1637        self
1638    }
1639
1640    #[must_use]
1641    pub fn right(mut self, value: Length) -> Self {
1642        self.base.right = Some(value.into());
1643        self
1644    }
1645
1646    #[must_use]
1647    pub fn bottom(mut self, value: Length) -> Self {
1648        self.base.bottom = Some(value.into());
1649        self
1650    }
1651
1652    #[must_use]
1653    pub fn left(mut self, value: Length) -> Self {
1654        self.base.left = Some(value.into());
1655        self
1656    }
1657
1658    /// Position at the logical inline start; overrides the matching physical edge.
1659    #[must_use]
1660    pub fn inset_start(mut self, value: impl Into<LayoutLength>) -> Self {
1661        self.base.inset_start = Some(value.into());
1662        self
1663    }
1664
1665    /// Position at the logical inline end; overrides the matching physical edge.
1666    #[must_use]
1667    pub fn inset_end(mut self, value: impl Into<LayoutLength>) -> Self {
1668        self.base.inset_end = Some(value.into());
1669        self
1670    }
1671
1672    /// Set finite unit opacity.
1673    ///
1674    /// # Errors
1675    ///
1676    /// Returns [`StyleValueError`] outside zero through one.
1677    pub fn opacity(mut self, value: f64) -> Result<Self, StyleValueError> {
1678        if !value.is_finite() || !(0.0..=1.0).contains(&value) {
1679            return Err(StyleValueError::InvalidOpacity(value));
1680        }
1681        self.base.opacity = Some(value);
1682        Ok(self)
1683    }
1684
1685    #[must_use]
1686    pub fn visible(mut self, visible: bool) -> Self {
1687        self.base.visible = Some(visible);
1688        self
1689    }
1690
1691    #[must_use]
1692    pub fn cursor(mut self, cursor: CursorKind) -> Self {
1693        self.base.cursor = Some(cursor);
1694        self
1695    }
1696
1697    #[must_use]
1698    pub fn hit_test(mut self, behavior: HitTestBehavior) -> Self {
1699        self.base.hit_test = Some(behavior);
1700        self
1701    }
1702
1703    /// Apply one symbolic typography role from the active theme.
1704    ///
1705    /// Explicit font properties on the same composed style override the role.
1706    ///
1707    /// # Errors
1708    ///
1709    /// Returns [`StyleValueError::UnknownTypographyRole`] for non-core roles.
1710    pub fn typography(mut self, role: impl Into<String>) -> Result<Self, StyleValueError> {
1711        let role = role.into();
1712        if !crate::REQUIRED_TYPOGRAPHY.contains(&role.as_str()) {
1713            return Err(StyleValueError::UnknownTypographyRole(role));
1714        }
1715        self.base.typography = Some(role);
1716        Ok(self)
1717    }
1718
1719    /// Set one validated platform font family name.
1720    ///
1721    /// # Errors
1722    ///
1723    /// Returns [`StyleValueError`] for an empty or oversized name.
1724    pub fn font_family(mut self, family: impl Into<String>) -> Result<Self, StyleValueError> {
1725        let family = family.into();
1726        if family.trim().is_empty() || family.len() > 256 {
1727            return Err(StyleValueError::InvalidFontFamily);
1728        }
1729        self.base.font_family = Some(family);
1730        Ok(self)
1731    }
1732
1733    /// Set an ordered font fallback stack.
1734    ///
1735    /// # Errors
1736    ///
1737    /// Returns [`StyleValueError::InvalidFontFallbacks`] for empty, duplicate,
1738    /// oversized, or invalid family names.
1739    pub fn font_fallbacks(mut self, families: Vec<String>) -> Result<Self, StyleValueError> {
1740        let unique = families.iter().collect::<BTreeSet<_>>();
1741        if families.is_empty()
1742            || families.len() > 16
1743            || unique.len() != families.len()
1744            || families
1745                .iter()
1746                .any(|family| family.trim().is_empty() || family.len() > 256)
1747        {
1748            return Err(StyleValueError::InvalidFontFallbacks);
1749        }
1750        self.base.font_fallbacks = Some(families);
1751        Ok(self)
1752    }
1753
1754    /// Set one OpenType feature tag value.
1755    ///
1756    /// # Errors
1757    ///
1758    /// Returns for invalid four-character tags or values outside 0..=65535.
1759    pub fn font_feature(
1760        mut self,
1761        tag: impl Into<String>,
1762        value: i64,
1763    ) -> Result<Self, StyleValueError> {
1764        let tag = tag.into();
1765        if tag.len() != 4
1766            || !tag
1767                .chars()
1768                .all(|character| character.is_ascii_alphanumeric())
1769        {
1770            return Err(StyleValueError::InvalidFontFeatureTag);
1771        }
1772        let value = u32::try_from(value)
1773            .ok()
1774            .filter(|value| *value <= 65_535)
1775            .ok_or(StyleValueError::InvalidFontFeatureValue(value))?;
1776        self.base
1777            .font_features
1778            .get_or_insert_with(BTreeMap::new)
1779            .insert(tag, value);
1780        Ok(self)
1781    }
1782
1783    /// Set a numeric font weight.
1784    ///
1785    /// # Errors
1786    ///
1787    /// Returns [`StyleValueError`] outside 1..=1000.
1788    pub fn font_weight(mut self, weight: i64) -> Result<Self, StyleValueError> {
1789        let weight = u16::try_from(weight)
1790            .ok()
1791            .filter(|weight| (1..=1_000).contains(weight))
1792            .ok_or(StyleValueError::InvalidFontWeight(weight))?;
1793        self.base.font_weight = Some(weight);
1794        Ok(self)
1795    }
1796
1797    #[must_use]
1798    pub fn font_slant(mut self, slant: FontSlant) -> Self {
1799        self.base.font_slant = Some(slant);
1800        self
1801    }
1802
1803    #[must_use]
1804    pub fn line_height(mut self, value: Length) -> Self {
1805        self.base.line_height = Some(value);
1806        self
1807    }
1808
1809    #[must_use]
1810    pub fn text_align(mut self, align: TextAlignMode) -> Self {
1811        self.base.text_align = Some(align);
1812        self
1813    }
1814
1815    #[must_use]
1816    pub fn white_space(mut self, white_space: WhiteSpaceMode) -> Self {
1817        self.base.white_space = Some(white_space);
1818        self
1819    }
1820
1821    #[must_use]
1822    pub fn text_ellipsis(mut self) -> Self {
1823        self.base.text_ellipsis = Some(true);
1824        self
1825    }
1826
1827    /// Clamp visible text to a bounded number of lines.
1828    ///
1829    /// # Errors
1830    ///
1831    /// Returns [`StyleValueError`] outside 1..=10000.
1832    pub fn line_clamp(mut self, lines: i64) -> Result<Self, StyleValueError> {
1833        let lines = usize::try_from(lines)
1834            .ok()
1835            .filter(|lines| (1..=10_000).contains(lines))
1836            .ok_or(StyleValueError::InvalidLineClamp(lines))?;
1837        self.base.line_clamp = Some(lines);
1838        Ok(self)
1839    }
1840
1841    #[must_use]
1842    pub fn shadow(mut self, shadow: ShadowSpec) -> Self {
1843        self.base.shadows = Some(vec![shadow]);
1844        self
1845    }
1846
1847    #[must_use]
1848    pub fn linear_gradient(mut self, gradient: LinearGradientSpec) -> Self {
1849        self.base.gradient = Some(gradient);
1850        self.base.background = None;
1851        self
1852    }
1853
1854    /// Apply a finite paint translation along x.
1855    ///
1856    /// # Errors
1857    ///
1858    /// Returns [`StyleValueError`] for non-finite or unrepresentable values.
1859    pub fn translate_x(mut self, value: f64) -> Result<Self, StyleValueError> {
1860        validate_translation(value)?;
1861        self.base.translate_x = Some(value);
1862        Ok(self)
1863    }
1864
1865    /// Apply a finite paint translation along y.
1866    ///
1867    /// # Errors
1868    ///
1869    /// Returns [`StyleValueError`] for non-finite or unrepresentable values.
1870    pub fn translate_y(mut self, value: f64) -> Result<Self, StyleValueError> {
1871        validate_translation(value)?;
1872        self.base.translate_y = Some(value);
1873        Ok(self)
1874    }
1875
1876    #[must_use]
1877    pub fn hover(mut self, style: &Self) -> Self {
1878        merge_pseudo(&mut self.hover, Some(&style.base));
1879        self
1880    }
1881
1882    #[must_use]
1883    pub fn active(mut self, style: &Self) -> Self {
1884        merge_pseudo(&mut self.active, Some(&style.base));
1885        self
1886    }
1887
1888    #[must_use]
1889    pub fn focus(mut self, style: &Self) -> Self {
1890        merge_pseudo(&mut self.focus, Some(&style.base));
1891        self
1892    }
1893
1894    #[must_use]
1895    pub fn disabled(mut self, style: &Self) -> Self {
1896        merge_pseudo(&mut self.disabled, Some(&style.base));
1897        self
1898    }
1899}
1900
1901fn grid_value(value: i64) -> Result<u16, StyleValueError> {
1902    u16::try_from(value)
1903        .ok()
1904        .filter(|value| (1..=1_024).contains(value))
1905        .ok_or(StyleValueError::InvalidGridValue(value))
1906}
1907
1908fn validate_translation(value: f64) -> Result<(), StyleValueError> {
1909    if value.is_finite() && value.abs() <= f64::from(f32::MAX) {
1910        Ok(())
1911    } else {
1912        Err(StyleValueError::InvalidTranslation(value))
1913    }
1914}
1915
1916fn validate_flex_grow_weight(value: f64) -> Result<(), StyleValueError> {
1917    if value.is_finite() && value > 0.0 && value <= f64::from(f32::MAX) {
1918        Ok(())
1919    } else {
1920        Err(StyleValueError::InvalidFlexGrowWeight(value))
1921    }
1922}
1923
1924fn merge_pseudo(base: &mut Option<StyleProperties>, overlay: Option<&StyleProperties>) {
1925    if let Some(overlay) = overlay {
1926        base.get_or_insert_with(StyleProperties::default)
1927            .merge(overlay);
1928    }
1929}
1930
1931#[derive(Clone, Copy)]
1932enum LayoutSlot {
1933    Width,
1934    Height,
1935    MinWidth,
1936    MaxWidth,
1937    MinHeight,
1938    MaxHeight,
1939    FlexBasis,
1940    Margin,
1941    MarginX,
1942    MarginY,
1943    MarginTop,
1944    MarginRight,
1945    MarginBottom,
1946    MarginLeft,
1947    MarginStart,
1948    MarginEnd,
1949    Top,
1950    Right,
1951    Bottom,
1952    Left,
1953    InsetStart,
1954    InsetEnd,
1955}
1956
1957fn with_layout_value(mut style: Style, slot: LayoutSlot, value: LayoutLength) -> Style {
1958    match slot {
1959        LayoutSlot::Width => style.base.width = Some(value),
1960        LayoutSlot::Height => style.base.height = Some(value),
1961        LayoutSlot::MinWidth => style.base.min_width = Some(value),
1962        LayoutSlot::MaxWidth => style.base.max_width = Some(value),
1963        LayoutSlot::MinHeight => style.base.min_height = Some(value),
1964        LayoutSlot::MaxHeight => style.base.max_height = Some(value),
1965        LayoutSlot::FlexBasis => style.base.flex_basis = Some(value),
1966        LayoutSlot::Margin => style.base.margin = LayoutEdgeLengths::all(value),
1967        LayoutSlot::MarginX => style
1968            .base
1969            .margin
1970            .merge(&LayoutEdgeLengths::horizontal(value)),
1971        LayoutSlot::MarginY => style.base.margin.merge(&LayoutEdgeLengths::vertical(value)),
1972        LayoutSlot::MarginTop => style.base.margin.top = Some(value),
1973        LayoutSlot::MarginRight => style.base.margin.right = Some(value),
1974        LayoutSlot::MarginBottom => style.base.margin.bottom = Some(value),
1975        LayoutSlot::MarginLeft => style.base.margin.left = Some(value),
1976        LayoutSlot::MarginStart => style.base.margin.start = Some(value),
1977        LayoutSlot::MarginEnd => style.base.margin.end = Some(value),
1978        LayoutSlot::Top => style.base.top = Some(value),
1979        LayoutSlot::Right => style.base.right = Some(value),
1980        LayoutSlot::Bottom => style.base.bottom = Some(value),
1981        LayoutSlot::Left => style.base.left = Some(value),
1982        LayoutSlot::InsetStart => style.base.inset_start = Some(value),
1983        LayoutSlot::InsetEnd => style.base.inset_end = Some(value),
1984    }
1985    style
1986}
1987
1988fn register_layout_overloads(
1989    builder: &mut TypeBuilder<Style>,
1990    name: &'static str,
1991    slot: LayoutSlot,
1992    signed: bool,
1993) {
1994    builder.with_fn(name, move |style: &mut Style, _: AutoLength| {
1995        with_layout_value(style.clone(), slot, LayoutLength::Auto)
1996    });
1997    if signed {
1998        builder.with_fn(name, move |style: &mut Style, value: SignedLength| {
1999            with_layout_value(style.clone(), slot, value.into())
2000        });
2001    }
2002}
2003
2004fn register_property_specific_layout_methods(builder: &mut TypeBuilder<Style>) {
2005    for (name, slot) in [
2006        ("width", LayoutSlot::Width),
2007        ("height", LayoutSlot::Height),
2008        ("min_width", LayoutSlot::MinWidth),
2009        ("max_width", LayoutSlot::MaxWidth),
2010        ("min_height", LayoutSlot::MinHeight),
2011        ("max_height", LayoutSlot::MaxHeight),
2012        ("flex_basis", LayoutSlot::FlexBasis),
2013    ] {
2014        register_layout_overloads(builder, name, slot, false);
2015    }
2016    for (name, slot) in [
2017        ("margin", LayoutSlot::Margin),
2018        ("margin_x", LayoutSlot::MarginX),
2019        ("margin_y", LayoutSlot::MarginY),
2020        ("margin_top", LayoutSlot::MarginTop),
2021        ("margin_right", LayoutSlot::MarginRight),
2022        ("margin_bottom", LayoutSlot::MarginBottom),
2023        ("margin_left", LayoutSlot::MarginLeft),
2024        ("margin_start", LayoutSlot::MarginStart),
2025        ("margin_end", LayoutSlot::MarginEnd),
2026        ("top", LayoutSlot::Top),
2027        ("right", LayoutSlot::Right),
2028        ("bottom", LayoutSlot::Bottom),
2029        ("left", LayoutSlot::Left),
2030        ("inset_start", LayoutSlot::InsetStart),
2031        ("inset_end", LayoutSlot::InsetEnd),
2032    ] {
2033        register_layout_overloads(builder, name, slot, true);
2034    }
2035}
2036
2037impl CustomType for Style {
2038    fn build(mut builder: TypeBuilder<Self>) {
2039        builder
2040            .with_name("Style")
2041            .with_fn("width", |style: &mut Self, value: Length| {
2042                style.clone().width(value)
2043            })
2044            .with_fn("height", |style: &mut Self, value: Length| {
2045                style.clone().height(value)
2046            })
2047            .with_fn("max_width", |style: &mut Self, value: Length| {
2048                style.clone().max_width(value)
2049            })
2050            .with_fn("gap", |style: &mut Self, value: Length| {
2051                style.clone().gap(value)
2052            })
2053            .with_fn("padding", |style: &mut Self, value: Length| {
2054                style.clone().padding(value)
2055            })
2056            .with_fn("padding_x", |style: &mut Self, value: Length| {
2057                style.clone().padding_x(value)
2058            })
2059            .with_fn("padding_y", |style: &mut Self, value: Length| {
2060                style.clone().padding_y(value)
2061            })
2062            .with_fn("padding_top", |style: &mut Self, value: Length| {
2063                style.clone().padding_top(value)
2064            })
2065            .with_fn("padding_start", |style: &mut Self, value: Length| {
2066                style.clone().padding_start(value)
2067            })
2068            .with_fn("padding_end", |style: &mut Self, value: Length| {
2069                style.clone().padding_end(value)
2070            })
2071            .with_fn("margin_start", |style: &mut Self, value: Length| {
2072                style.clone().margin_start(value)
2073            })
2074            .with_fn("margin_end", |style: &mut Self, value: Length| {
2075                style.clone().margin_end(value)
2076            })
2077            .with_fn("background", |style: &mut Self, value: ColorValue| {
2078                style.clone().background(value)
2079            })
2080            .with_fn("text_color", |style: &mut Self, value: ColorValue| {
2081                style.clone().text_color(value)
2082            });
2083        register_border_methods(&mut builder);
2084        builder
2085            .with_fn("font_size", |style: &mut Self, value: Length| {
2086                style.clone().font_size(value)
2087            })
2088            .with_fn("flex_row", |style: &mut Self| style.clone().flex_row())
2089            .with_fn("flex_col", |style: &mut Self| style.clone().flex_col())
2090            .with_fn("items_center", |style: &mut Self| {
2091                style.clone().items_center()
2092            })
2093            .with_fn("items_start", |style: &mut Self| {
2094                style.clone().items_start()
2095            })
2096            .with_fn("items_end", |style: &mut Self| style.clone().items_end())
2097            .with_fn("justify_center", |style: &mut Self| {
2098                style.clone().justify_center()
2099            })
2100            .with_fn("justify_start", |style: &mut Self| {
2101                style.clone().justify_start()
2102            })
2103            .with_fn("justify_end", |style: &mut Self| {
2104                style.clone().justify_end()
2105            })
2106            .with_fn("justify_between", |style: &mut Self| {
2107                style.clone().justify_between()
2108            })
2109            .with_fn("clip", |style: &mut Self| style.clone().clip());
2110        register_self_alignment_methods(&mut builder);
2111        register_overflow_methods(&mut builder);
2112        register_position_methods(&mut builder);
2113        register_extended_layout_methods(&mut builder);
2114        register_property_specific_layout_methods(&mut builder);
2115        register_visual_methods(&mut builder);
2116        register_text_methods(&mut builder);
2117        builder
2118            .with_fn("hover", |style: &mut Self, state: Self| {
2119                style.clone().hover(&state)
2120            })
2121            .with_fn("active", |style: &mut Self, state: Self| {
2122                style.clone().active(&state)
2123            })
2124            .with_fn("focus", |style: &mut Self, state: Self| {
2125                style.clone().focus(&state)
2126            })
2127            .with_fn("disabled", |style: &mut Self, state: Self| {
2128                style.clone().disabled(&state)
2129            })
2130            .with_fn("merge", |style: &mut Self, overlay: Self| {
2131                style.clone().merged(&overlay)
2132            });
2133    }
2134}
2135
2136fn register_border_methods(builder: &mut TypeBuilder<Style>) {
2137    builder
2138        .with_fn("radius", |style: &mut Style, value: Length| {
2139            style.clone().radius(value)
2140        })
2141        .with_fn("radius_top_left", |style: &mut Style, value: Length| {
2142            style.clone().radius_top_left(value)
2143        })
2144        .with_fn("radius_top_right", |style: &mut Style, value: Length| {
2145            style.clone().radius_top_right(value)
2146        })
2147        .with_fn("radius_bottom_right", |style: &mut Style, value: Length| {
2148            style.clone().radius_bottom_right(value)
2149        })
2150        .with_fn("radius_bottom_left", |style: &mut Style, value: Length| {
2151            style.clone().radius_bottom_left(value)
2152        })
2153        .with_fn("border", |style: &mut Style, value: Length| {
2154            style.clone().border(value)
2155        })
2156        .with_fn("border_top", |style: &mut Style, value: Length| {
2157            style.clone().border_top(value)
2158        })
2159        .with_fn("border_right", |style: &mut Style, value: Length| {
2160            style.clone().border_right(value)
2161        })
2162        .with_fn("border_bottom", |style: &mut Style, value: Length| {
2163            style.clone().border_bottom(value)
2164        })
2165        .with_fn("border_left", |style: &mut Style, value: Length| {
2166            style.clone().border_left(value)
2167        })
2168        .with_fn("border_start", |style: &mut Style, value: Length| {
2169            style.clone().border_start(value)
2170        })
2171        .with_fn("border_end", |style: &mut Style, value: Length| {
2172            style.clone().border_end(value)
2173        })
2174        .with_fn("border_color", |style: &mut Style, value: ColorValue| {
2175            style.clone().border_color(value)
2176        })
2177        .with_fn("border_solid", |style: &mut Style| {
2178            style.clone().border_solid()
2179        })
2180        .with_fn("border_dashed", |style: &mut Style| {
2181            style.clone().border_dashed()
2182        });
2183}
2184
2185fn register_self_alignment_methods(builder: &mut TypeBuilder<Style>) {
2186    builder
2187        .with_fn("self_start", |style: &mut Style| style.clone().self_start())
2188        .with_fn("self_center", |style: &mut Style| {
2189            style.clone().self_center()
2190        })
2191        .with_fn("self_end", |style: &mut Style| style.clone().self_end())
2192        .with_fn("self_stretch", |style: &mut Style| {
2193            style.clone().self_stretch()
2194        });
2195}
2196
2197fn register_overflow_methods(builder: &mut TypeBuilder<Style>) {
2198    builder
2199        .with_fn("overflow_x_scroll", |style: &mut Style| {
2200            style.clone().overflow_x_scroll()
2201        })
2202        .with_fn("overflow_y_scroll", |style: &mut Style| {
2203            style.clone().overflow_y_scroll()
2204        })
2205        .with_fn("overflow_scroll", |style: &mut Style| {
2206            style.clone().overflow_scroll()
2207        })
2208        .with_fn("overflow_hidden", |style: &mut Style| {
2209            style.clone().overflow_hidden()
2210        });
2211}
2212
2213fn register_position_methods(builder: &mut TypeBuilder<Style>) {
2214    builder
2215        .with_fn("relative", |style: &mut Style| style.clone().relative())
2216        .with_fn("absolute", |style: &mut Style| style.clone().absolute())
2217        .with_fn("top", |style: &mut Style, value: Length| {
2218            style.clone().top(value)
2219        })
2220        .with_fn("right", |style: &mut Style, value: Length| {
2221            style.clone().right(value)
2222        })
2223        .with_fn("bottom", |style: &mut Style, value: Length| {
2224            style.clone().bottom(value)
2225        })
2226        .with_fn("left", |style: &mut Style, value: Length| {
2227            style.clone().left(value)
2228        })
2229        .with_fn("inset_start", |style: &mut Style, value: Length| {
2230            style.clone().inset_start(value)
2231        })
2232        .with_fn("inset_end", |style: &mut Style, value: Length| {
2233            style.clone().inset_end(value)
2234        });
2235}
2236
2237fn register_extended_layout_methods(builder: &mut TypeBuilder<Style>) {
2238    register_flex_methods(builder);
2239    builder
2240        .with_fn("min_width", |style: &mut Style, value: Length| {
2241            style.clone().min_width(value)
2242        })
2243        .with_fn("min_height", |style: &mut Style, value: Length| {
2244            style.clone().min_height(value)
2245        })
2246        .with_fn("max_height", |style: &mut Style, value: Length| {
2247            style.clone().max_height(value)
2248        })
2249        .with_fn("padding_right", |style: &mut Style, value: Length| {
2250            style.clone().padding_right(value)
2251        })
2252        .with_fn("padding_bottom", |style: &mut Style, value: Length| {
2253            style.clone().padding_bottom(value)
2254        })
2255        .with_fn("padding_left", |style: &mut Style, value: Length| {
2256            style.clone().padding_left(value)
2257        })
2258        .with_fn("margin", |style: &mut Style, value: Length| {
2259            style.clone().margin(value)
2260        })
2261        .with_fn("margin_x", |style: &mut Style, value: Length| {
2262            style.clone().margin_x(value)
2263        })
2264        .with_fn("margin_y", |style: &mut Style, value: Length| {
2265            style.clone().margin_y(value)
2266        })
2267        .with_fn("margin_top", |style: &mut Style, value: Length| {
2268            style.clone().margin_top(value)
2269        })
2270        .with_fn("margin_right", |style: &mut Style, value: Length| {
2271            style.clone().margin_right(value)
2272        })
2273        .with_fn("margin_bottom", |style: &mut Style, value: Length| {
2274            style.clone().margin_bottom(value)
2275        })
2276        .with_fn("margin_left", |style: &mut Style, value: Length| {
2277            style.clone().margin_left(value)
2278        })
2279        .with_fn("block", |style: &mut Style| style.clone().block())
2280        .with_fn("grid", |style: &mut Style| style.clone().grid())
2281        .with_fn("hidden", |style: &mut Style| style.clone().hidden())
2282        .with_fn("justify_around", |style: &mut Style| {
2283            style.clone().justify_around()
2284        })
2285        .with_fn("flex_wrap", |style: &mut Style| {
2286            style.clone().flex_wrap(FlexWrapMode::Wrap)
2287        })
2288        .with_fn("flex_wrap_reverse", |style: &mut Style| {
2289            style.clone().flex_wrap(FlexWrapMode::WrapReverse)
2290        })
2291        .with_fn("flex_nowrap", |style: &mut Style| {
2292            style.clone().flex_wrap(FlexWrapMode::NoWrap)
2293        })
2294        .with_fn("grid_cols", |style: &mut Style, value: INT| {
2295            style
2296                .clone()
2297                .grid_columns(value)
2298                .map_err(|error| Box::new(style_runtime_error(error.to_string())))
2299        })
2300        .with_fn("grid_rows", |style: &mut Style, value: INT| {
2301            style
2302                .clone()
2303                .grid_rows(value)
2304                .map_err(|error| Box::new(style_runtime_error(error.to_string())))
2305        })
2306        .with_fn("col_span", |style: &mut Style, value: INT| {
2307            style
2308                .clone()
2309                .column_span(value)
2310                .map_err(|error| Box::new(style_runtime_error(error.to_string())))
2311        })
2312        .with_fn("row_span", |style: &mut Style, value: INT| {
2313            style
2314                .clone()
2315                .row_span(value)
2316                .map_err(|error| Box::new(style_runtime_error(error.to_string())))
2317        });
2318}
2319
2320fn register_flex_methods(builder: &mut TypeBuilder<Style>) {
2321    builder
2322        .with_fn("flex_grow", |style: &mut Style| style.clone().flex_grow())
2323        .with_fn(
2324            "flex_grow",
2325            |style: &mut Style, value: FLOAT| -> Result<Style, Box<EvalAltResult>> {
2326                style
2327                    .clone()
2328                    .flex_grow_weight(value)
2329                    .map_err(|error| Box::new(style_runtime_error(error.to_string())))
2330            },
2331        )
2332        .with_fn(
2333            "flex_grow",
2334            |style: &mut Style, value: INT| -> Result<Style, Box<EvalAltResult>> {
2335                style
2336                    .clone()
2337                    .flex_grow_weight(numeric_to_f64(&value)?)
2338                    .map_err(|error| Box::new(style_runtime_error(error.to_string())))
2339            },
2340        )
2341        .with_fn("flex_shrink", |style: &mut Style, value: bool| {
2342            style.clone().flex_shrink(value)
2343        })
2344        .with_fn("flex_basis", |style: &mut Style, value: Length| {
2345            style.clone().flex_basis(value)
2346        });
2347}
2348
2349fn register_visual_methods(builder: &mut TypeBuilder<Style>) {
2350    builder
2351        .with_fn("opacity", |style: &mut Style, value: FLOAT| {
2352            style
2353                .clone()
2354                .opacity(value)
2355                .map_err(|error| Box::new(style_runtime_error(error.to_string())))
2356        })
2357        .with_fn("opacity", |style: &mut Style, value: INT| {
2358            style
2359                .clone()
2360                .opacity(numeric_to_f64(&value)?)
2361                .map_err(|error| Box::new(style_runtime_error(error.to_string())))
2362        })
2363        .with_fn("visible", |style: &mut Style| style.clone().visible(true))
2364        .with_fn("invisible", |style: &mut Style| {
2365            style.clone().visible(false)
2366        })
2367        .with_fn("cursor_default", |style: &mut Style| {
2368            style.clone().cursor(CursorKind::Default)
2369        })
2370        .with_fn("cursor_pointer", |style: &mut Style| {
2371            style.clone().cursor(CursorKind::Pointer)
2372        })
2373        .with_fn("cursor_text", |style: &mut Style| {
2374            style.clone().cursor(CursorKind::Text)
2375        })
2376        .with_fn("cursor_move", |style: &mut Style| {
2377            style.clone().cursor(CursorKind::Move)
2378        })
2379        .with_fn("cursor_crosshair", |style: &mut Style| {
2380            style.clone().cursor(CursorKind::Crosshair)
2381        })
2382        .with_fn("cursor_not_allowed", |style: &mut Style| {
2383            style.clone().cursor(CursorKind::NotAllowed)
2384        })
2385        .with_fn("cursor_resize_x", |style: &mut Style| {
2386            style.clone().cursor(CursorKind::ResizeHorizontal)
2387        })
2388        .with_fn("cursor_resize_y", |style: &mut Style| {
2389            style.clone().cursor(CursorKind::ResizeVertical)
2390        })
2391        .with_fn("occlude", |style: &mut Style| {
2392            style.clone().hit_test(HitTestBehavior::Block)
2393        })
2394        .with_fn("occlude_except_scroll", |style: &mut Style| {
2395            style.clone().hit_test(HitTestBehavior::BlockExceptScroll)
2396        })
2397        .with_fn("shadow", |style: &mut Style, shadow: ShadowSpec| {
2398            style.clone().shadow(shadow)
2399        })
2400        .with_fn(
2401            "linear_gradient",
2402            |style: &mut Style, gradient: LinearGradientSpec| {
2403                style.clone().linear_gradient(gradient)
2404            },
2405        )
2406        .with_fn("translate_x", |style: &mut Style, value: FLOAT| {
2407            style
2408                .clone()
2409                .translate_x(value)
2410                .map_err(|error| Box::new(style_runtime_error(error.to_string())))
2411        })
2412        .with_fn("translate_x", |style: &mut Style, value: INT| {
2413            style
2414                .clone()
2415                .translate_x(numeric_to_f64(&value)?)
2416                .map_err(|error| Box::new(style_runtime_error(error.to_string())))
2417        })
2418        .with_fn("translate_y", |style: &mut Style, value: FLOAT| {
2419            style
2420                .clone()
2421                .translate_y(value)
2422                .map_err(|error| Box::new(style_runtime_error(error.to_string())))
2423        })
2424        .with_fn("translate_y", |style: &mut Style, value: INT| {
2425            style
2426                .clone()
2427                .translate_y(numeric_to_f64(&value)?)
2428                .map_err(|error| Box::new(style_runtime_error(error.to_string())))
2429        });
2430}
2431
2432fn register_text_methods(builder: &mut TypeBuilder<Style>) {
2433    builder
2434        .with_fn("typography", |style: &mut Style, role: ImmutableString| {
2435            style
2436                .clone()
2437                .typography(role.to_string())
2438                .map_err(|error| Box::new(style_runtime_error(error.to_string())))
2439        })
2440        .with_fn(
2441            "font_family",
2442            |style: &mut Style, family: ImmutableString| {
2443                style
2444                    .clone()
2445                    .font_family(family.to_string())
2446                    .map_err(|error| Box::new(style_runtime_error(error.to_string())))
2447            },
2448        )
2449        .with_fn(
2450            "font_fallbacks",
2451            |style: &mut Style, families: Array| -> Result<Style, Box<EvalAltResult>> {
2452                let families = families
2453                    .into_iter()
2454                    .map(|family| {
2455                        family
2456                            .try_cast::<ImmutableString>()
2457                            .map(|family| family.to_string())
2458                    })
2459                    .collect::<Option<Vec<_>>>()
2460                    .ok_or_else(|| {
2461                        Box::new(style_runtime_error(
2462                            "font_fallbacks values must be strings".to_owned(),
2463                        ))
2464                    })?;
2465                style
2466                    .clone()
2467                    .font_fallbacks(families)
2468                    .map_err(|error| Box::new(style_runtime_error(error.to_string())))
2469            },
2470        )
2471        .with_fn(
2472            "font_feature",
2473            |style: &mut Style,
2474             tag: ImmutableString,
2475             value: INT|
2476             -> Result<Style, Box<EvalAltResult>> {
2477                style
2478                    .clone()
2479                    .font_feature(tag.to_string(), value)
2480                    .map_err(|error| Box::new(style_runtime_error(error.to_string())))
2481            },
2482        )
2483        .with_fn("font_weight", |style: &mut Style, weight: INT| {
2484            style
2485                .clone()
2486                .font_weight(weight)
2487                .map_err(|error| Box::new(style_runtime_error(error.to_string())))
2488        })
2489        .with_fn("italic", |style: &mut Style| {
2490            style.clone().font_slant(FontSlant::Italic)
2491        })
2492        .with_fn("not_italic", |style: &mut Style| {
2493            style.clone().font_slant(FontSlant::Normal)
2494        })
2495        .with_fn("line_height", |style: &mut Style, value: Length| {
2496            style.clone().line_height(value)
2497        })
2498        .with_fn("text_left", |style: &mut Style| {
2499            style.clone().text_align(TextAlignMode::Start)
2500        })
2501        .with_fn("text_center", |style: &mut Style| {
2502            style.clone().text_align(TextAlignMode::Center)
2503        })
2504        .with_fn("text_right", |style: &mut Style| {
2505            style.clone().text_align(TextAlignMode::End)
2506        })
2507        .with_fn("whitespace_normal", |style: &mut Style| {
2508            style.clone().white_space(WhiteSpaceMode::Normal)
2509        })
2510        .with_fn("whitespace_nowrap", |style: &mut Style| {
2511            style.clone().white_space(WhiteSpaceMode::NoWrap)
2512        })
2513        .with_fn("text_ellipsis", |style: &mut Style| {
2514            style.clone().text_ellipsis()
2515        })
2516        .with_fn("line_clamp", |style: &mut Style, lines: INT| {
2517            style
2518                .clone()
2519                .line_clamp(lines)
2520                .map_err(|error| Box::new(style_runtime_error(error.to_string())))
2521        });
2522}
2523
2524#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
2525pub enum PseudoState {
2526    Hovered,
2527    Active,
2528    Focused,
2529    Disabled,
2530}
2531
2532#[derive(Clone, Debug, Default, Eq, PartialEq)]
2533pub struct InteractionState {
2534    states: BTreeSet<PseudoState>,
2535}
2536
2537impl InteractionState {
2538    #[must_use]
2539    pub fn with(mut self, state: PseudoState) -> Self {
2540        self.states.insert(state);
2541        self
2542    }
2543
2544    #[must_use]
2545    pub fn contains(&self, state: PseudoState) -> bool {
2546        self.states.contains(&state)
2547    }
2548}
2549
2550pub(crate) fn register_style_api(engine: &mut Engine) {
2551    engine.build_type::<Length>();
2552    engine.build_type::<AutoLength>();
2553    engine.build_type::<SignedLength>();
2554    engine.build_type::<ColorValue>();
2555    engine.build_type::<ShadowSpec>();
2556    engine.build_type::<LinearGradientSpec>();
2557    engine.build_type::<Style>();
2558
2559    FuncRegistration::new("style")
2560        .in_global_namespace()
2561        .register_into_engine(engine, Style::new);
2562    FuncRegistration::new("theme_typography")
2563        .in_global_namespace()
2564        .register_into_engine(
2565            engine,
2566            |role: ImmutableString| -> Result<Style, Box<EvalAltResult>> {
2567                Style::new()
2568                    .typography(role.to_string())
2569                    .map_err(|error| Box::new(style_runtime_error(error.to_string())))
2570            },
2571        );
2572    register_length_constructor(engine, "px", Length::pixels);
2573    register_length_constructor(engine, "rem", Length::rems);
2574    register_length_constructor(engine, "relative", Length::relative);
2575    FuncRegistration::new("auto")
2576        .in_global_namespace()
2577        .register_into_engine(engine, || AutoLength);
2578    register_signed_length_constructor(engine, "offset_px", SignedLength::pixels);
2579    register_signed_length_constructor(engine, "offset_rem", SignedLength::rems);
2580    register_signed_length_constructor(engine, "offset_relative", SignedLength::relative);
2581    FuncRegistration::new("theme_spacing")
2582        .in_global_namespace()
2583        .register_into_engine(
2584            engine,
2585            |token: ImmutableString| -> Result<Length, Box<EvalAltResult>> {
2586                Length::theme_spacing(token.as_str())
2587                    .map_err(|error| Box::new(style_runtime_error(error.to_string())))
2588            },
2589        );
2590    FuncRegistration::new("theme_radius")
2591        .in_global_namespace()
2592        .register_into_engine(
2593            engine,
2594            |token: ImmutableString| -> Result<Length, Box<EvalAltResult>> {
2595                Length::theme_radius(token.as_str())
2596                    .map_err(|error| Box::new(style_runtime_error(error.to_string())))
2597            },
2598        );
2599    FuncRegistration::new("rgb")
2600        .in_global_namespace()
2601        .register_into_engine(engine, rgb_color);
2602    FuncRegistration::new("rgba")
2603        .in_global_namespace()
2604        .register_into_engine(engine, rgba_color);
2605    FuncRegistration::new("shadow")
2606        .in_global_namespace()
2607        .register_into_engine(engine, shadow_from_map);
2608    FuncRegistration::new("linear_gradient")
2609        .in_global_namespace()
2610        .register_into_engine(engine, linear_gradient_from_map);
2611    FuncRegistration::new("theme_color")
2612        .in_global_namespace()
2613        .register_into_engine(engine, |token: String| ColorValue::Token(token));
2614    FuncRegistration::new("color")
2615        .in_global_namespace()
2616        .register_into_engine(
2617            engine,
2618            |value: ImmutableString| -> Result<ColorValue, Box<EvalAltResult>> {
2619                ColorValue::parse(value.as_str())
2620                    .map_err(|error| Box::new(style_runtime_error(error.to_string())))
2621            },
2622        );
2623}
2624
2625fn register_length_constructor(
2626    engine: &mut Engine,
2627    name: &str,
2628    constructor: fn(f64) -> Result<Length, LengthError>,
2629) {
2630    FuncRegistration::new(name)
2631        .in_global_namespace()
2632        .register_into_engine(
2633            engine,
2634            move |value: FLOAT| -> Result<Length, Box<EvalAltResult>> {
2635                constructor(value).map_err(|error| Box::new(style_runtime_error(error.to_string())))
2636            },
2637        );
2638    FuncRegistration::new(name)
2639        .in_global_namespace()
2640        .register_into_engine(
2641            engine,
2642            move |value: INT| -> Result<Length, Box<EvalAltResult>> {
2643                constructor(numeric_to_f64(&value)?)
2644                    .map_err(|error| Box::new(style_runtime_error(error.to_string())))
2645            },
2646        );
2647}
2648
2649fn register_signed_length_constructor(
2650    engine: &mut Engine,
2651    name: &str,
2652    constructor: fn(f64) -> Result<SignedLength, LengthError>,
2653) {
2654    FuncRegistration::new(name)
2655        .in_global_namespace()
2656        .register_into_engine(
2657            engine,
2658            move |value: FLOAT| -> Result<SignedLength, Box<EvalAltResult>> {
2659                constructor(value).map_err(|error| Box::new(style_runtime_error(error.to_string())))
2660            },
2661        );
2662    FuncRegistration::new(name)
2663        .in_global_namespace()
2664        .register_into_engine(
2665            engine,
2666            move |value: INT| -> Result<SignedLength, Box<EvalAltResult>> {
2667                constructor(numeric_to_f64(&value)?)
2668                    .map_err(|error| Box::new(style_runtime_error(error.to_string())))
2669            },
2670        );
2671}
2672
2673fn numeric_to_f64(value: &impl ToString) -> Result<f64, Box<EvalAltResult>> {
2674    value.to_string().parse::<f64>().map_err(|_| {
2675        Box::new(style_runtime_error(
2676            "numeric length is outside the supported range".to_owned(),
2677        ))
2678    })
2679}
2680
2681fn rgb_color(value: INT) -> Result<ColorValue, Box<EvalAltResult>> {
2682    let value = u32::try_from(value)
2683        .ok()
2684        .filter(|value| *value <= 0x00ff_ffff)
2685        .ok_or_else(|| {
2686            Box::new(style_runtime_error(
2687                "rgb value must be between 0 and 0xffffff".to_owned(),
2688            ))
2689        })?;
2690    Ok(ColorValue::Literal(Rgba8::from_rgb_hex(value)))
2691}
2692
2693fn rgba_color(value: INT) -> Result<ColorValue, Box<EvalAltResult>> {
2694    let value = u32::try_from(value).map_err(|_| {
2695        Box::new(style_runtime_error(
2696            "rgba value must be between 0 and 0xffffffff".to_owned(),
2697        ))
2698    })?;
2699    Ok(ColorValue::Literal(Rgba8::from_rgba_hex(value)))
2700}
2701
2702fn shadow_from_map(mut value: Map) -> Result<ShadowSpec, Box<EvalAltResult>> {
2703    let x = optional_map_number(&mut value, "x")?.unwrap_or(0.0);
2704    let y = optional_map_number(&mut value, "y")?.unwrap_or(0.0);
2705    let blur = optional_map_number(&mut value, "blur")?.unwrap_or(0.0);
2706    let spread = optional_map_number(&mut value, "spread")?.unwrap_or(0.0);
2707    let color = value
2708        .remove("color")
2709        .and_then(Dynamic::try_cast::<ColorValue>)
2710        .ok_or_else(|| {
2711            Box::new(style_runtime_error(
2712                "shadow color must be a ColorValue".to_owned(),
2713            ))
2714        })?;
2715    reject_style_map_unknown(value, "shadow")?;
2716    ShadowSpec::new(x, y, blur, spread, color)
2717        .map_err(|error| Box::new(style_runtime_error(error.to_string())))
2718}
2719
2720fn linear_gradient_from_map(mut value: Map) -> Result<LinearGradientSpec, Box<EvalAltResult>> {
2721    let angle = optional_map_number(&mut value, "angle")?.unwrap_or(180.0);
2722    let from = value
2723        .remove("from")
2724        .and_then(Dynamic::try_cast::<ColorValue>)
2725        .ok_or_else(|| {
2726            Box::new(style_runtime_error(
2727                "linear gradient from must be a ColorValue".to_owned(),
2728            ))
2729        })?;
2730    let to = value
2731        .remove("to")
2732        .and_then(Dynamic::try_cast::<ColorValue>)
2733        .ok_or_else(|| {
2734            Box::new(style_runtime_error(
2735                "linear gradient to must be a ColorValue".to_owned(),
2736            ))
2737        })?;
2738    reject_style_map_unknown(value, "linear gradient")?;
2739    LinearGradientSpec::new(angle, from, to)
2740        .map_err(|error| Box::new(style_runtime_error(error.to_string())))
2741}
2742
2743fn optional_map_number(value: &mut Map, name: &str) -> Result<Option<f64>, Box<EvalAltResult>> {
2744    value
2745        .remove(name)
2746        .map(|value| {
2747            if value.is::<FLOAT>() {
2748                Ok(value.cast::<FLOAT>())
2749            } else if value.is::<INT>() {
2750                numeric_to_f64(&value.cast::<INT>())
2751            } else {
2752                Err(Box::new(style_runtime_error(format!(
2753                    "style field `{name}` must be a number"
2754                ))))
2755            }
2756        })
2757        .transpose()
2758}
2759
2760fn reject_style_map_unknown(value: Map, context: &str) -> Result<(), Box<EvalAltResult>> {
2761    if let Some((name, _)) = value.into_iter().next() {
2762        Err(Box::new(style_runtime_error(format!(
2763            "unknown {context} field `{name}`"
2764        ))))
2765    } else {
2766        Ok(())
2767    }
2768}
2769
2770fn style_runtime_error(error: String) -> EvalAltResult {
2771    EvalAltResult::ErrorRuntime(error.into(), Position::NONE)
2772}
2773
2774#[cfg(test)]
2775mod tests {
2776    use super::*;
2777
2778    #[test]
2779    fn caller_overlay_wins_deterministically() {
2780        let base = Style::new()
2781            .width(Length::pixels(100.0).unwrap())
2782            .background(ColorValue::Literal(Rgba8::from_rgb_hex(0x0011_1111)));
2783        let caller = Style::new()
2784            .width(Length::pixels(240.0).unwrap())
2785            .text_color(ColorValue::Literal(Rgba8::from_rgb_hex(0x00ff_ffff)));
2786        let merged = base.merged(&caller);
2787
2788        assert_eq!(merged.base.width, Some(Length::Pixels(240.0).into()));
2789        assert_eq!(
2790            merged.base.background,
2791            Some(ColorValue::Literal(Rgba8::from_rgb_hex(0x0011_1111)))
2792        );
2793        assert_eq!(
2794            merged.base.text_color,
2795            Some(ColorValue::Literal(Rgba8::from_rgb_hex(0x00ff_ffff)))
2796        );
2797    }
2798
2799    #[test]
2800    fn disabled_pseudo_state_has_final_precedence() {
2801        let style = Style::new()
2802            .background(ColorValue::Token("button".to_owned()))
2803            .hover(&Style::new().background(ColorValue::Token("button_hover".to_owned())))
2804            .disabled(&Style::new().background(ColorValue::Token("button_disabled".to_owned())));
2805        let resolved = style.resolve(
2806            &InteractionState::default()
2807                .with(PseudoState::Hovered)
2808                .with(PseudoState::Disabled),
2809        );
2810        assert_eq!(
2811            resolved.background,
2812            Some(ColorValue::Token("button_disabled".to_owned()))
2813        );
2814    }
2815
2816    #[test]
2817    fn script_builds_typed_styles() {
2818        let mut engine = Engine::new();
2819        register_style_api(&mut engine);
2820        let style: Style = engine
2821            .eval(
2822                r#"
2823                    style()
2824                        .flex_col()
2825                        .self_start()
2826                        .gap(theme_spacing("sm"))
2827                        .padding_x(rem(1.5))
2828                        .radius(theme_radius("md"))
2829                        .background(rgb(0x112233))
2830                        .hover(style().background(theme_color("surface_hover")))
2831                "#,
2832            )
2833            .unwrap();
2834        assert_eq!(style.base.direction, Some(FlexDirection::Column));
2835        assert_eq!(style.base.align_self, Some(Align::Start));
2836        assert_eq!(style.base.gap, Some(Length::ThemeSpacing(SpacingToken::Sm)));
2837        assert_eq!(
2838            style.base.radii,
2839            CornerLengths::all(Length::ThemeRadius(RadiusToken::Md))
2840        );
2841        assert!(style.hover.is_some());
2842    }
2843
2844    #[test]
2845    fn weighted_flex_grow_is_validated_overloaded_and_merges_as_one_property() {
2846        let mut engine = Engine::new();
2847        register_style_api(&mut engine);
2848        let float: Style = engine.eval("style().flex_grow(2.5)").unwrap();
2849        let integer: Style = engine.eval("style().flex_grow(3)").unwrap();
2850        assert_eq!(float.base.flex_grow_weight, Some(2.5));
2851        assert_eq!(float.base.flex_grow, None);
2852        assert_eq!(integer.base.flex_grow_weight, Some(3.0));
2853        for invalid in ["0", "-1"] {
2854            assert!(
2855                engine
2856                    .eval::<Style>(&format!("style().flex_grow({invalid})"))
2857                    .is_err()
2858            );
2859        }
2860        assert!(Style::new().flex_grow_weight(f64::INFINITY).is_err());
2861
2862        let standard = Style::new()
2863            .flex_grow_weight(4.0)
2864            .unwrap()
2865            .merged(&Style::new().flex_grow());
2866        assert_eq!(standard.base.flex_grow, Some(true));
2867        assert_eq!(standard.base.flex_grow_weight, None);
2868        let weighted = Style::new()
2869            .flex_grow()
2870            .merged(&Style::new().flex_grow_weight(4.0).unwrap());
2871        assert_eq!(weighted.base.flex_grow, None);
2872        assert_eq!(weighted.base.flex_grow_weight, Some(4.0));
2873
2874        let encoded = serde_json::to_string(&weighted).unwrap();
2875        assert_eq!(serde_json::from_str::<Style>(&encoded).unwrap(), weighted);
2876    }
2877
2878    #[test]
2879    fn script_builds_extended_layout_paint_and_typography() {
2880        let mut engine = Engine::new();
2881        register_style_api(&mut engine);
2882        let style: Style = engine
2883            .eval(
2884                r#"
2885                    style()
2886                        .grid_cols(3).grid_rows(2).col_span(2)
2887                        .min_width(px(120)).max_height(px(480))
2888                        .margin_x(px(8)).padding_bottom(px(6))
2889                        .border_top(px(1)).border_start(px(2))
2890                        .radius_top_left(px(12)).radius_bottom_right(px(4))
2891                        .linear_gradient(linear_gradient(#{
2892                            angle: 135, from: rgb(0x112233), to: rgba(0x445566cc)
2893                        }))
2894                        .shadow(shadow(#{ x: 0, y: 8, blur: 24, spread: 2,
2895                            color: rgba(0x00000066) }))
2896                        .opacity(0.85).translate_x(-12)
2897                        .cursor_pointer().occlude_except_scroll().font_family("Avenir Next")
2898                        .font_fallbacks(["PingFang SC", "Noto Sans"])
2899                        .font_feature("liga", 0).font_feature("ss01", 1)
2900                        .font_weight(650).italic().line_height(px(24))
2901                        .text_center().whitespace_nowrap().text_ellipsis().line_clamp(2)
2902                "#,
2903            )
2904            .unwrap();
2905        assert_eq!(style.base.display, Some(DisplayMode::Grid));
2906        assert_eq!(style.base.grid_columns, Some(3));
2907        assert_eq!(style.base.column_span, Some(2));
2908        assert_eq!(style.base.opacity, Some(0.85));
2909        assert_eq!(style.base.translate_x, Some(-12.0));
2910        assert_eq!(style.base.border_widths.top, Some(Length::Pixels(1.0)));
2911        assert_eq!(style.base.border_widths.start, Some(Length::Pixels(2.0)));
2912        assert_eq!(style.base.radii.top_left, Some(Length::Pixels(12.0)));
2913        assert_eq!(style.base.radii.bottom_right, Some(Length::Pixels(4.0)));
2914        assert_eq!(style.base.cursor, Some(CursorKind::Pointer));
2915        assert_eq!(
2916            style.base.hit_test,
2917            Some(HitTestBehavior::BlockExceptScroll)
2918        );
2919        assert_eq!(style.base.font_family.as_deref(), Some("Avenir Next"));
2920        assert_eq!(
2921            style.base.font_fallbacks.as_deref(),
2922            Some(["PingFang SC".to_owned(), "Noto Sans".to_owned()].as_slice())
2923        );
2924        assert_eq!(
2925            style.base.font_features,
2926            Some(BTreeMap::from([
2927                ("liga".to_owned(), 0),
2928                ("ss01".to_owned(), 1)
2929            ]))
2930        );
2931        assert_eq!(style.base.font_weight, Some(650));
2932        assert_eq!(style.base.line_clamp, Some(2));
2933        assert!(style.base.gradient.is_some());
2934        assert_eq!(style.base.shadows.as_ref().map(Vec::len), Some(1));
2935        assert!(
2936            Style::new()
2937                .font_fallbacks(vec!["Duplicate".to_owned(), "Duplicate".to_owned()])
2938                .is_err()
2939        );
2940        assert!(Style::new().font_feature("bad", 1).is_err());
2941    }
2942
2943    #[test]
2944    fn script_uses_validated_symbolic_typography_roles() {
2945        let mut engine = Engine::new();
2946        register_style_api(&mut engine);
2947        let style: Style = engine
2948            .eval(r#"theme_typography("body").font_weight(650)"#)
2949            .unwrap();
2950        assert_eq!(style.base.typography.as_deref(), Some("body"));
2951        assert_eq!(style.base.font_weight, Some(650));
2952        assert!(
2953            engine
2954                .eval::<Style>(r#"style().typography("bodyish")"#)
2955                .is_err()
2956        );
2957    }
2958
2959    #[test]
2960    fn script_selects_uniform_border_style() {
2961        let mut engine = Engine::new();
2962        register_style_api(&mut engine);
2963        let dashed: Style = engine.eval("style().border_dashed()").unwrap();
2964        let solid: Style = engine.eval("style().border_solid()").unwrap();
2965        assert_eq!(dashed.base.border_style, Some(BorderLineStyle::Dashed));
2966        assert_eq!(solid.base.border_style, Some(BorderLineStyle::Solid));
2967    }
2968
2969    #[test]
2970    fn script_layout_values_separate_auto_and_signed_offsets_from_lengths() {
2971        let mut engine = Engine::new();
2972        register_style_api(&mut engine);
2973        let style: Style = engine
2974            .eval(
2975                r#"
2976                    style()
2977                        .width(auto()).flex_basis(auto())
2978                        .margin_x(auto()).margin_bottom(offset_relative(-0.5))
2979                        .top(offset_px(-12)).left(offset_rem(1.5))
2980                        .inset_start(offset_px(-4)).inset_end(theme_spacing("xs"))
2981                "#,
2982            )
2983            .unwrap();
2984        assert_eq!(style.base.width, Some(LayoutLength::Auto));
2985        assert_eq!(
2986            style.base.inset_start,
2987            Some(LayoutLength::Signed(SignedLength::Pixels(-4.0)))
2988        );
2989        assert_eq!(
2990            style.base.inset_end,
2991            Some(LayoutLength::Definite(Length::ThemeSpacing(
2992                SpacingToken::Xs
2993            )))
2994        );
2995        let auto: Style = engine.eval("style().inset_start(auto())").unwrap();
2996        assert_eq!(auto.base.inset_start, Some(LayoutLength::Auto));
2997        let composed = style.clone().merged(&auto);
2998        assert_eq!(composed.base.inset_start, Some(LayoutLength::Auto));
2999        assert_eq!(style.base.flex_basis, Some(LayoutLength::Auto));
3000        assert_eq!(style.base.margin.left, Some(LayoutLength::Auto));
3001        assert_eq!(style.base.margin.right, Some(LayoutLength::Auto));
3002        assert_eq!(
3003            style.base.margin.bottom,
3004            Some(LayoutLength::Signed(SignedLength::Relative(-0.5)))
3005        );
3006        assert_eq!(
3007            style.base.top,
3008            Some(LayoutLength::Signed(SignedLength::Pixels(-12.0)))
3009        );
3010        assert_eq!(
3011            style.base.left,
3012            Some(LayoutLength::Signed(SignedLength::Rems(1.5)))
3013        );
3014        assert!(
3015            engine
3016                .eval::<Style>("style().padding(offset_px(-1))")
3017                .is_err()
3018        );
3019        assert!(
3020            engine
3021                .eval::<Style>("style().width(offset_px(-1))")
3022                .is_err()
3023        );
3024        assert!(serde_json::from_str::<Length>(r#"{"unit":"pixels","value":-1}"#).is_err());
3025        assert!(serde_json::from_str::<SignedLength>(r#"{"unit":"relative","value":11}"#).is_err());
3026    }
3027
3028    #[test]
3029    fn strict_color_literals_share_one_typed_value_path() {
3030        assert_eq!(
3031            ColorValue::parse("#abc").unwrap(),
3032            ColorValue::Literal(Rgba8::from_rgba_hex(0xaabb_ccff))
3033        );
3034        assert_eq!(
3035            ColorValue::parse("rgba(255, 0, 0, 50%)").unwrap(),
3036            ColorValue::Literal(Rgba8::from_rgba_hex(0xff00_0080))
3037        );
3038        assert_eq!(
3039            ColorValue::parse("hsl(120, 100%, 50%)").unwrap(),
3040            ColorValue::Literal(Rgba8::from_rgba_hex(0x00ff_00ff))
3041        );
3042        assert_eq!(
3043            ColorValue::parse("hwb(0, 20%, 30%)").unwrap(),
3044            ColorValue::Literal(Rgba8::from_rgba_hex(0xb333_33ff))
3045        );
3046        assert_eq!(
3047            ColorValue::parse("hwb(120, 40%, 60%, 50%)").unwrap(),
3048            ColorValue::Literal(Rgba8::from_rgba_hex(0x6666_6680))
3049        );
3050        assert_eq!(
3051            ColorValue::parse("transparent").unwrap(),
3052            ColorValue::Literal(Rgba8::from_rgba_hex(0))
3053        );
3054        assert!(ColorValue::parse("rgb(256, 0, 0)").is_err());
3055        assert!(ColorValue::parse("not-a-color").is_err());
3056
3057        let mut engine = Engine::new();
3058        register_style_api(&mut engine);
3059        assert_eq!(
3060            engine
3061                .eval::<ColorValue>(r##"color("#336699cc")"##)
3062                .unwrap(),
3063            ColorValue::Literal(Rgba8::from_rgba_hex(0x3366_99cc))
3064        );
3065    }
3066
3067    #[test]
3068    fn invalid_lengths_are_rejected() {
3069        assert_eq!(
3070            Length::relative(1.5),
3071            Err(LengthError::InvalidRelative(1.5))
3072        );
3073        assert!(Length::pixels(f64::NAN).is_err());
3074        assert!(Length::theme_spacing("xxl").is_err());
3075        assert!(Style::new().opacity(1.1).is_err());
3076        assert!(Style::new().translate_y(f64::NAN).is_err());
3077        assert!(Style::new().grid_columns(0).is_err());
3078        assert!(ShadowSpec::new(0.0, 1.0, -1.0, 0.0, ColorValue::Token("x".into())).is_err());
3079    }
3080}