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 pub fn pixels(value: f64) -> Result<Self, LengthError> {
92 validate_signed(value).map(|()| Self::Pixels(value))
93 }
94
95 pub fn rems(value: f64) -> Result<Self, LengthError> {
101 validate_signed(value).map(|()| Self::Rems(value))
102 }
103
104 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 pub fn pixels(value: f64) -> Result<Self, LengthError> {
213 validate_non_negative(value).map(|()| Self::Pixels(value))
214 }
215
216 pub fn rems(value: f64) -> Result<Self, LengthError> {
222 validate_non_negative(value).map(|()| Self::Rems(value))
223 }
224
225 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 pub fn theme_spacing(value: &str) -> Result<Self, LengthError> {
244 SpacingToken::parse(value).map(Self::ThemeSpacing)
245 }
246
247 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 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 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 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 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 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 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 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 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 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 #[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 #[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 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 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 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 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 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 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 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 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 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}