1use alloc::string::{String, ToString};
4use core::fmt;
5
6#[cfg(feature = "parser")]
7use crate::props::basic::{
8 error::{InvalidValueErr, InvalidValueErrOwned},
9 length::parse_percentage_value,
10};
11use crate::props::{
12 basic::length::{PercentageParseError, PercentageValue},
13 formatter::PrintAsCssValue,
14};
15
16#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
20#[repr(C)]
21pub struct StyleOpacity {
22 pub inner: PercentageValue,
23}
24
25impl Default for StyleOpacity {
26 fn default() -> Self {
27 StyleOpacity {
28 inner: PercentageValue::const_new(100),
29 }
30 }
31}
32
33impl PrintAsCssValue for StyleOpacity {
34 fn print_as_css_value(&self) -> String {
35 format!("{}", self.inner.normalized())
36 }
37}
38
39#[cfg(feature = "parser")]
40impl_percentage_value!(StyleOpacity);
41
42#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
46#[repr(C)]
47pub enum StyleVisibility {
48 Visible,
49 Hidden,
50 Collapse,
51}
52
53impl Default for StyleVisibility {
54 fn default() -> StyleVisibility {
55 StyleVisibility::Visible
56 }
57}
58
59impl PrintAsCssValue for StyleVisibility {
60 fn print_as_css_value(&self) -> String {
61 String::from(match self {
62 Self::Visible => "visible",
63 Self::Hidden => "hidden",
64 Self::Collapse => "collapse",
65 })
66 }
67}
68
69#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
74#[repr(C)]
75pub enum StyleMixBlendMode {
76 Normal,
77 Multiply,
78 Screen,
79 Overlay,
80 Darken,
81 Lighten,
82 ColorDodge,
83 ColorBurn,
84 HardLight,
85 SoftLight,
86 Difference,
87 Exclusion,
88 Hue,
89 Saturation,
90 Color,
91 Luminosity,
92}
93
94impl Default for StyleMixBlendMode {
95 fn default() -> StyleMixBlendMode {
96 StyleMixBlendMode::Normal
97 }
98}
99
100impl fmt::Display for StyleMixBlendMode {
101 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
102 write!(
103 f,
104 "{}",
105 match self {
106 Self::Normal => "normal",
107 Self::Multiply => "multiply",
108 Self::Screen => "screen",
109 Self::Overlay => "overlay",
110 Self::Darken => "darken",
111 Self::Lighten => "lighten",
112 Self::ColorDodge => "color-dodge",
113 Self::ColorBurn => "color-burn",
114 Self::HardLight => "hard-light",
115 Self::SoftLight => "soft-light",
116 Self::Difference => "difference",
117 Self::Exclusion => "exclusion",
118 Self::Hue => "hue",
119 Self::Saturation => "saturation",
120 Self::Color => "color",
121 Self::Luminosity => "luminosity",
122 }
123 )
124 }
125}
126
127impl PrintAsCssValue for StyleMixBlendMode {
128 fn print_as_css_value(&self) -> String {
129 self.to_string()
130 }
131}
132
133#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
138#[repr(C)]
139pub enum StyleCursor {
140 Alias,
141 AllScroll,
142 Cell,
143 ColResize,
144 ContextMenu,
145 Copy,
146 Crosshair,
147 Default,
148 EResize,
149 EwResize,
150 Grab,
151 Grabbing,
152 Help,
153 Move,
154 NResize,
155 NsResize,
156 NeswResize,
157 NwseResize,
158 Pointer,
159 Progress,
160 RowResize,
161 SResize,
162 SeResize,
163 Text,
164 Unset,
165 VerticalText,
166 WResize,
167 Wait,
168 ZoomIn,
169 ZoomOut,
170}
171
172impl Default for StyleCursor {
173 fn default() -> StyleCursor {
174 StyleCursor::Default
175 }
176}
177
178impl PrintAsCssValue for StyleCursor {
179 fn print_as_css_value(&self) -> String {
180 String::from(match self {
181 Self::Alias => "alias",
182 Self::AllScroll => "all-scroll",
183 Self::Cell => "cell",
184 Self::ColResize => "col-resize",
185 Self::ContextMenu => "context-menu",
186 Self::Copy => "copy",
187 Self::Crosshair => "crosshair",
188 Self::Default => "default",
189 Self::EResize => "e-resize",
190 Self::EwResize => "ew-resize",
191 Self::Grab => "grab",
192 Self::Grabbing => "grabbing",
193 Self::Help => "help",
194 Self::Move => "move",
195 Self::NResize => "n-resize",
196 Self::NsResize => "ns-resize",
197 Self::NeswResize => "nesw-resize",
198 Self::NwseResize => "nwse-resize",
199 Self::Pointer => "pointer",
200 Self::Progress => "progress",
201 Self::RowResize => "row-resize",
202 Self::SResize => "s-resize",
203 Self::SeResize => "se-resize",
204 Self::Text => "text",
205 Self::Unset => "unset",
206 Self::VerticalText => "vertical-text",
207 Self::WResize => "w-resize",
208 Self::Wait => "wait",
209 Self::ZoomIn => "zoom-in",
210 Self::ZoomOut => "zoom-out",
211 })
212 }
213}
214
215#[cfg(feature = "parser")]
218pub mod parsers {
219 use super::*;
220 use crate::props::basic::error::{InvalidValueErr, InvalidValueErrOwned};
221
222 #[derive(Clone, PartialEq)]
225 pub enum OpacityParseError<'a> {
226 ParsePercentage(PercentageParseError, &'a str),
227 OutOfRange(&'a str),
228 }
229 impl_debug_as_display!(OpacityParseError<'a>);
230 impl_display! { OpacityParseError<'a>, {
231 ParsePercentage(e, s) => format!("Invalid opacity value \"{}\": {}", s, e),
232 OutOfRange(s) => format!("Invalid opacity value \"{}\": must be between 0 and 1", s),
233 }}
234
235 #[derive(Debug, Clone, PartialEq)]
236 pub enum OpacityParseErrorOwned {
237 ParsePercentage(PercentageParseError, String),
238 OutOfRange(String),
239 }
240
241 impl<'a> OpacityParseError<'a> {
242 pub fn to_contained(&self) -> OpacityParseErrorOwned {
243 match self {
244 Self::ParsePercentage(err, s) => {
245 OpacityParseErrorOwned::ParsePercentage(err.clone(), s.to_string())
246 }
247 Self::OutOfRange(s) => OpacityParseErrorOwned::OutOfRange(s.to_string()),
248 }
249 }
250 }
251
252 impl OpacityParseErrorOwned {
253 pub fn to_shared<'a>(&'a self) -> OpacityParseError<'a> {
254 match self {
255 Self::ParsePercentage(err, s) => {
256 OpacityParseError::ParsePercentage(err.clone(), s.as_str())
257 }
258 Self::OutOfRange(s) => OpacityParseError::OutOfRange(s.as_str()),
259 }
260 }
261 }
262
263 pub fn parse_style_opacity<'a>(input: &'a str) -> Result<StyleOpacity, OpacityParseError<'a>> {
264 let val = parse_percentage_value(input)
265 .map_err(|e| OpacityParseError::ParsePercentage(e, input))?;
266
267 let normalized = val.normalized();
268 if normalized < 0.0 || normalized > 1.0 {
269 return Err(OpacityParseError::OutOfRange(input));
270 }
271
272 Ok(StyleOpacity { inner: val })
273 }
274
275 #[derive(Clone, PartialEq)]
278 pub enum StyleVisibilityParseError<'a> {
279 InvalidValue(InvalidValueErr<'a>),
280 }
281 impl_debug_as_display!(StyleVisibilityParseError<'a>);
282 impl_display! { StyleVisibilityParseError<'a>, {
283 InvalidValue(e) => format!("Invalid visibility value: \"{}\"", e.0),
284 }}
285 impl_from!(InvalidValueErr<'a>, StyleVisibilityParseError::InvalidValue);
286
287 #[derive(Debug, Clone, PartialEq)]
288 pub enum StyleVisibilityParseErrorOwned {
289 InvalidValue(InvalidValueErrOwned),
290 }
291
292 impl<'a> StyleVisibilityParseError<'a> {
293 pub fn to_contained(&self) -> StyleVisibilityParseErrorOwned {
294 match self {
295 Self::InvalidValue(e) => {
296 StyleVisibilityParseErrorOwned::InvalidValue(e.to_contained())
297 }
298 }
299 }
300 }
301
302 impl StyleVisibilityParseErrorOwned {
303 pub fn to_shared<'a>(&'a self) -> StyleVisibilityParseError<'a> {
304 match self {
305 Self::InvalidValue(e) => StyleVisibilityParseError::InvalidValue(e.to_shared()),
306 }
307 }
308 }
309
310 pub fn parse_style_visibility<'a>(
311 input: &'a str,
312 ) -> Result<StyleVisibility, StyleVisibilityParseError<'a>> {
313 let input = input.trim();
314 match input {
315 "visible" => Ok(StyleVisibility::Visible),
316 "hidden" => Ok(StyleVisibility::Hidden),
317 "collapse" => Ok(StyleVisibility::Collapse),
318 _ => Err(InvalidValueErr(input).into()),
319 }
320 }
321
322 #[derive(Clone, PartialEq)]
325 pub enum MixBlendModeParseError<'a> {
326 InvalidValue(InvalidValueErr<'a>),
327 }
328 impl_debug_as_display!(MixBlendModeParseError<'a>);
329 impl_display! { MixBlendModeParseError<'a>, {
330 InvalidValue(e) => format!("Invalid mix-blend-mode value: \"{}\"", e.0),
331 }}
332 impl_from!(InvalidValueErr<'a>, MixBlendModeParseError::InvalidValue);
333
334 #[derive(Debug, Clone, PartialEq)]
335 pub enum MixBlendModeParseErrorOwned {
336 InvalidValue(InvalidValueErrOwned),
337 }
338
339 impl<'a> MixBlendModeParseError<'a> {
340 pub fn to_contained(&self) -> MixBlendModeParseErrorOwned {
341 match self {
342 Self::InvalidValue(e) => {
343 MixBlendModeParseErrorOwned::InvalidValue(e.to_contained())
344 }
345 }
346 }
347 }
348
349 impl MixBlendModeParseErrorOwned {
350 pub fn to_shared<'a>(&'a self) -> MixBlendModeParseError<'a> {
351 match self {
352 Self::InvalidValue(e) => MixBlendModeParseError::InvalidValue(e.to_shared()),
353 }
354 }
355 }
356
357 pub fn parse_style_mix_blend_mode<'a>(
358 input: &'a str,
359 ) -> Result<StyleMixBlendMode, MixBlendModeParseError<'a>> {
360 let input = input.trim();
361 match input {
362 "normal" => Ok(StyleMixBlendMode::Normal),
363 "multiply" => Ok(StyleMixBlendMode::Multiply),
364 "screen" => Ok(StyleMixBlendMode::Screen),
365 "overlay" => Ok(StyleMixBlendMode::Overlay),
366 "darken" => Ok(StyleMixBlendMode::Darken),
367 "lighten" => Ok(StyleMixBlendMode::Lighten),
368 "color-dodge" => Ok(StyleMixBlendMode::ColorDodge),
369 "color-burn" => Ok(StyleMixBlendMode::ColorBurn),
370 "hard-light" => Ok(StyleMixBlendMode::HardLight),
371 "soft-light" => Ok(StyleMixBlendMode::SoftLight),
372 "difference" => Ok(StyleMixBlendMode::Difference),
373 "exclusion" => Ok(StyleMixBlendMode::Exclusion),
374 "hue" => Ok(StyleMixBlendMode::Hue),
375 "saturation" => Ok(StyleMixBlendMode::Saturation),
376 "color" => Ok(StyleMixBlendMode::Color),
377 "luminosity" => Ok(StyleMixBlendMode::Luminosity),
378 _ => Err(InvalidValueErr(input).into()),
379 }
380 }
381
382 #[derive(Clone, PartialEq)]
385 pub enum CursorParseError<'a> {
386 InvalidValue(InvalidValueErr<'a>),
387 }
388 impl_debug_as_display!(CursorParseError<'a>);
389 impl_display! { CursorParseError<'a>, {
390 InvalidValue(e) => format!("Invalid cursor value: \"{}\"", e.0),
391 }}
392 impl_from!(InvalidValueErr<'a>, CursorParseError::InvalidValue);
393
394 #[derive(Debug, Clone, PartialEq)]
395 pub enum CursorParseErrorOwned {
396 InvalidValue(InvalidValueErrOwned),
397 }
398
399 impl<'a> CursorParseError<'a> {
400 pub fn to_contained(&self) -> CursorParseErrorOwned {
401 match self {
402 Self::InvalidValue(e) => CursorParseErrorOwned::InvalidValue(e.to_contained()),
403 }
404 }
405 }
406
407 impl CursorParseErrorOwned {
408 pub fn to_shared<'a>(&'a self) -> CursorParseError<'a> {
409 match self {
410 Self::InvalidValue(e) => CursorParseError::InvalidValue(e.to_shared()),
411 }
412 }
413 }
414
415 pub fn parse_style_cursor<'a>(input: &'a str) -> Result<StyleCursor, CursorParseError<'a>> {
416 let input = input.trim();
417 match input {
418 "alias" => Ok(StyleCursor::Alias),
419 "all-scroll" => Ok(StyleCursor::AllScroll),
420 "cell" => Ok(StyleCursor::Cell),
421 "col-resize" => Ok(StyleCursor::ColResize),
422 "context-menu" => Ok(StyleCursor::ContextMenu),
423 "copy" => Ok(StyleCursor::Copy),
424 "crosshair" => Ok(StyleCursor::Crosshair),
425 "default" => Ok(StyleCursor::Default),
426 "e-resize" => Ok(StyleCursor::EResize),
427 "ew-resize" => Ok(StyleCursor::EwResize),
428 "grab" => Ok(StyleCursor::Grab),
429 "grabbing" => Ok(StyleCursor::Grabbing),
430 "help" => Ok(StyleCursor::Help),
431 "move" => Ok(StyleCursor::Move),
432 "n-resize" => Ok(StyleCursor::NResize),
433 "ns-resize" => Ok(StyleCursor::NsResize),
434 "nesw-resize" => Ok(StyleCursor::NeswResize),
435 "nwse-resize" => Ok(StyleCursor::NwseResize),
436 "pointer" => Ok(StyleCursor::Pointer),
437 "progress" => Ok(StyleCursor::Progress),
438 "row-resize" => Ok(StyleCursor::RowResize),
439 "s-resize" => Ok(StyleCursor::SResize),
440 "se-resize" => Ok(StyleCursor::SeResize),
441 "text" => Ok(StyleCursor::Text),
442 "unset" => Ok(StyleCursor::Unset),
443 "vertical-text" => Ok(StyleCursor::VerticalText),
444 "w-resize" => Ok(StyleCursor::WResize),
445 "wait" => Ok(StyleCursor::Wait),
446 "zoom-in" => Ok(StyleCursor::ZoomIn),
447 "zoom-out" => Ok(StyleCursor::ZoomOut),
448 _ => Err(InvalidValueErr(input).into()),
449 }
450 }
451}
452
453#[cfg(feature = "parser")]
454pub use self::parsers::*;
455
456#[cfg(all(test, feature = "parser"))]
457mod tests {
458 use super::*;
459
460 #[test]
461 fn test_parse_opacity() {
462 assert_eq!(parse_style_opacity("0.5").unwrap().inner.normalized(), 0.5);
463 assert_eq!(parse_style_opacity("1").unwrap().inner.normalized(), 1.0);
464 assert_eq!(parse_style_opacity("50%").unwrap().inner.normalized(), 0.5);
465 assert_eq!(parse_style_opacity("0").unwrap().inner.normalized(), 0.0);
466 assert_eq!(
467 parse_style_opacity(" 75% ").unwrap().inner.normalized(),
468 0.75
469 );
470 assert!(parse_style_opacity("1.1").is_err());
471 assert!(parse_style_opacity("-0.1").is_err());
472 assert!(parse_style_opacity("auto").is_err());
473 }
474
475 #[test]
476 fn test_parse_mix_blend_mode() {
477 assert_eq!(
478 parse_style_mix_blend_mode("multiply").unwrap(),
479 StyleMixBlendMode::Multiply
480 );
481 assert_eq!(
482 parse_style_mix_blend_mode("screen").unwrap(),
483 StyleMixBlendMode::Screen
484 );
485 assert_eq!(
486 parse_style_mix_blend_mode("color-dodge").unwrap(),
487 StyleMixBlendMode::ColorDodge
488 );
489 assert!(parse_style_mix_blend_mode("mix").is_err());
490 }
491
492 #[test]
493 fn test_parse_visibility() {
494 assert_eq!(
495 parse_style_visibility("visible").unwrap(),
496 StyleVisibility::Visible
497 );
498 assert_eq!(
499 parse_style_visibility("hidden").unwrap(),
500 StyleVisibility::Hidden
501 );
502 assert_eq!(
503 parse_style_visibility("collapse").unwrap(),
504 StyleVisibility::Collapse
505 );
506 assert_eq!(
507 parse_style_visibility(" visible ").unwrap(),
508 StyleVisibility::Visible
509 );
510 assert!(parse_style_visibility("none").is_err());
511 assert!(parse_style_visibility("show").is_err());
512 }
513
514 #[test]
515 fn test_parse_cursor() {
516 assert_eq!(parse_style_cursor("pointer").unwrap(), StyleCursor::Pointer);
517 assert_eq!(parse_style_cursor("wait").unwrap(), StyleCursor::Wait);
518 assert_eq!(
519 parse_style_cursor("col-resize").unwrap(),
520 StyleCursor::ColResize
521 );
522 assert_eq!(parse_style_cursor(" text ").unwrap(), StyleCursor::Text);
523 assert!(parse_style_cursor("hand").is_err()); }
525}