orbtk_utils/
expression.rs

1use crate::prelude::*;
2use std::f64;
3use std::iter::Peekable;
4use std::ops::Neg;
5use std::{convert::TryFrom, str::Chars};
6
7// Describes a String declared expression either be a method, a color, a number or anything.
8/// This object represents an `expression` used to define
9/// something. E.g. brushes are defined using an expression in there themes definition.
10#[derive(Clone, PartialEq, PartialOrd, Debug)]
11pub enum Expression {
12    Method(String, Vec<Expression>),
13    Complex(Vec<Expression>),
14    Number(Number, String),
15    Color(Color),
16    Other(String),
17}
18
19impl Expression {
20    /// Try to convert `self` into a `Number`
21    pub fn number(&self) -> Option<Number> {
22        match self {
23            Expression::Number(number, d) if d.is_empty() => Some(*number),
24            _ => None,
25        }
26    }
27
28    pub fn color(&self) -> Option<Color> {
29        match self {
30            Expression::Color(color) => Some(*color),
31            Expression::Method(name, args) => {
32                let mut values = [0.0f64; 4];
33                for (i, arg) in args.iter().enumerate() {
34                    if i > 3 {
35                        return None;
36                    }
37                    let (mut v, p): (f64, bool) = match arg {
38                        Expression::Number(v, u) if u.is_empty() => ((*v).into(), false),
39                        Expression::Number(v, u) if u == "%" => ((*v).into(), true),
40                        _ => {
41                            return None;
42                        }
43                    };
44                    if name == "rgb" || name == "rgba" {
45                        if p {
46                            v = v * 100.0 / 255.0;
47                        } else if v <= 1.0 {
48                            v = 255.0 * v.fract();
49                        }
50                    } else if i != 0 && p {
51                        v /= 100.0;
52                    }
53                    values[i] = v;
54                }
55                if args.len() == 3 {
56                    Some(match &name[..] {
57                        "rgb" => Color::rgb(values[0] as u8, values[1] as u8, values[2] as u8),
58                        "hsv" | "hsb" => Color::hsv(values[0], values[1], values[2]),
59                        "hsl" => Color::hsl(values[0], values[1], values[2]),
60                        _ => return None,
61                    })
62                } else {
63                    Some(match &name[..] {
64                        "rgba" => Color::rgba(
65                            values[0] as u8,
66                            values[1] as u8,
67                            values[2] as u8,
68                            values[3] as u8,
69                        ),
70                        "hsva" | "hsba" => Color::hsva(values[0], values[1], values[2], values[3]),
71                        "hsla" => Color::hsla(values[0], values[1], values[2], values[3]),
72                        _ => return None,
73                    })
74                }
75            }
76            Expression::Other(s) => Color::from_name(s),
77            _ => None,
78        }
79    }
80
81    fn gradient_stop(&self) -> Option<GradientStop> {
82        if let Some(color) = self.color() {
83            return Some(GradientStop { pos: None, color });
84        }
85        match self {
86            Expression::Complex(v) if v.len() == 2 => {
87                let color = match v[0].color() {
88                    Some(color) => color,
89                    None => return None,
90                };
91                let pos = match v[1] {
92                    Expression::Number(n, ref m) => OnLinePos::try_from((n, &m[..])).ok()?,
93                    _ => return None,
94                };
95                Some(GradientStop {
96                    pos: Some(pos),
97                    color,
98                })
99            }
100            _ => None,
101        }
102    }
103
104    pub fn relative_dir(&self) -> Option<RelativeDir> {
105        match self {
106            Expression::Other(label) => match &label[..] {
107                "to top" => Some(RelativeDir::Top),
108                "to top right" => Some(RelativeDir::TopRight),
109                "to right" => Some(RelativeDir::Right),
110                "to bottom right" => Some(RelativeDir::BottomRight),
111                "to bottom" => Some(RelativeDir::Bottom),
112                "to bottom left" => Some(RelativeDir::BottomLeft),
113                "to left" => Some(RelativeDir::Left),
114                "to top left" => Some(RelativeDir::TopLeft),
115                _ => None,
116            },
117            _ => None,
118        }
119    }
120
121    pub fn angle(&self) -> Option<Angle> {
122        match self {
123            Expression::Number(num, unit) => {
124                let num: f64 = (*num).into();
125                let angle = match &unit[..] {
126                    "rad" => Angle::from_radians(num),
127                    "turn" => Angle::from_turn(num),
128                    "deg" | "" => Angle::from_degrees(num),
129                    _ => {
130                        return None;
131                    }
132                };
133                Some(angle)
134            }
135            _ => None,
136        }
137    }
138
139    pub fn css_gradient(&self) -> Option<Gradient> {
140        let mut displacement = OnPlanePos::new(
141            OnLinePos::new(0.0, OnLinePosKind::Pixels),
142            OnLinePos::new(0.0, OnLinePosKind::Pixels),
143        );
144        let (name, args) = match self {
145            Expression::Method(name, args) => (name, args),
146            Expression::Complex(exprs) if exprs.len() <= 3 + 1 => {
147                let mut i = 0;
148                let (name, args) = match exprs.get(i) {
149                    Some(Expression::Method(name, args)) => {
150                        i += 1;
151                        (name, args)
152                    }
153                    _ => {
154                        return None;
155                    }
156                };
157                *displacement.x_mut() = match exprs.get(i) {
158                    Some(Expression::Number(n, u)) => {
159                        i += 1;
160                        OnLinePos::try_from((*n, &u[..])).ok()?
161                    }
162                    _ => {
163                        return None;
164                    }
165                };
166                *displacement.y_mut() = match exprs.get(i) {
167                    Some(Expression::Number(n, u)) => OnLinePos::try_from((*n, &u[..])).ok()?,
168                    _ => {
169                        return None;
170                    }
171                };
172                (name, args)
173            }
174            _ => return None,
175        };
176        if args.is_empty() {
177            return None;
178        }
179        let (radial, repeat) = match &name[..] {
180            "repeating-linear-gradient" => (false, true),
181            "linear-gradient" => (false, false),
182            "radial-gradient" => (true, false),
183            "repeating-radial-gradient" => (true, true),
184            _ => {
185                return None;
186            }
187        };
188        let mut i = 0;
189        let kind;
190        if radial {
191            // TODO: Implement radial gradients
192            return None;
193        } else {
194            let mut coords = LinearGradientCoords::Angle {
195                displacement,
196                angle: Angle::zero(),
197            };
198            if let Some(direction) = args[0].relative_dir() {
199                coords = LinearGradientCoords::Direction {
200                    direction,
201                    displacement,
202                };
203            } else if let Some(angle) = args[0].angle() {
204                coords = LinearGradientCoords::Angle {
205                    angle,
206                    displacement,
207                };
208                i += 1;
209            }
210            kind = GradientKind::Linear(coords);
211        }
212        let stops: Vec<GradientStop> = args
213            .iter()
214            .skip(i)
215            .filter_map(|stop| stop.gradient_stop())
216            .collect();
217        if stops.is_empty() {
218            return None;
219        }
220        Some(Gradient {
221            kind,
222            stops,
223            repeat,
224        })
225    }
226
227    pub fn brush(&self) -> Option<Brush> {
228        if let Some(color) = self.color() {
229            return Some(Brush::from(color));
230        }
231        if let Some(g) = self.css_gradient() {
232            return Some(Brush::from(g));
233        }
234        None
235    }
236}
237
238impl Default for Expression {
239    fn default() -> Self {
240        Expression::Complex(Vec::new())
241    }
242}
243
244impl From<Expression> for Number {
245    fn from(e: Expression) -> Self {
246        match e {
247            Expression::Number(num, _) => num,
248            _ => Number::default(),
249        }
250    }
251}
252
253pub(crate) fn parse_expression_with_complex(chrs: &mut Peekable<Chars>) -> Option<Expression> {
254    let mut v = Vec::new();
255    while let Some(c) = chrs.peek() {
256        let c = *c;
257        if c == ',' || c == ')' {
258            break;
259        } else if c.is_whitespace() {
260            // Ignore whitespaces
261            chrs.next().unwrap();
262            continue;
263        }
264        let expr = parse_expression(chrs)?;
265        v.push(expr);
266    }
267    if v.is_empty() {
268        None
269    } else if v.len() == 1 {
270        Some(v[0].to_owned())
271    } else {
272        Some(Expression::Complex(v))
273    }
274}
275
276fn is_number_component(c: char) -> bool {
277    c.is_ascii_digit() || c == '.' || c == '-'
278}
279
280fn parse_expression(chrs: &mut Peekable<Chars>) -> Option<Expression> {
281    let mut text = String::new();
282    let method;
283    loop {
284        match chrs.peek() {
285            Some('(') => {
286                chrs.next().unwrap();
287                method = true;
288                break;
289            }
290            Some(c) if *c == ',' || *c == ')' || (c.is_whitespace() && text != "to") => {
291                method = false;
292                break;
293            }
294            Some(c) => {
295                text.push(*c);
296                chrs.next().unwrap();
297            }
298            None => {
299                method = false;
300                break;
301            }
302        }
303    }
304    debug_assert!(!text.is_empty());
305    if method {
306        let mut args = Vec::new();
307        loop {
308            match chrs.peek() {
309                Some(c) if c.is_whitespace() || *c == ',' => {
310                    chrs.next().unwrap();
311                }
312                None | Some(')') => {
313                    let _ = chrs.next();
314                    break;
315                }
316                _ => {
317                    args.push(parse_expression_with_complex(chrs)?);
318                }
319            }
320        }
321        Some(Expression::Method(text, args))
322    } else {
323        if text.starts_with('#') {
324            return Some(Expression::Color(Color::from(text)));
325        } else if text.starts_with(is_number_component) {
326            if let Some(mut ofs) = text.rfind(is_number_component) {
327                ofs += 1; // Moves from before last position digit to after last digit position
328                if text[..ofs]
329                    .find(|x| x == '.' || x == 'e' || x == 'E')
330                    .is_some()
331                {
332                    if let Ok(v) = lexical_core::parse(text[..ofs].as_bytes()) {
333                        return Some(Expression::Number(Number::Float(v), text[ofs..].to_owned()));
334                    }
335                } else if let Ok(v) = lexical_core::parse(text[..ofs].as_bytes()) {
336                    return Some(Expression::Number(Number::Real(v), text[ofs..].to_owned()));
337                }
338            }
339        }
340        Some(Expression::Other(text))
341    }
342}
343
344impl From<&str> for Expression {
345    fn from(s: &str) -> Expression {
346        parse_expression_with_complex(&mut s.chars().peekable()).unwrap_or_default()
347    }
348}
349
350impl From<String> for Expression {
351    fn from(s: String) -> Expression {
352        Expression::from(&s[..])
353    }
354}
355
356/// Describes a position on a plane
357#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
358pub struct OnPlanePos {
359    x: OnLinePos,
360    y: OnLinePos,
361}
362
363impl OnPlanePos {
364    pub fn new(x: OnLinePos, y: OnLinePos) -> OnPlanePos {
365        OnPlanePos { x, y }
366    }
367
368    pub fn x(&self) -> OnLinePos {
369        self.x
370    }
371
372    pub fn y(&self) -> OnLinePos {
373        self.y
374    }
375
376    pub fn x_mut(&mut self) -> &mut OnLinePos {
377        &mut self.x
378    }
379
380    pub fn y_mut(&mut self) -> &mut OnLinePos {
381        &mut self.y
382    }
383
384    /// Returns the position in pixels
385    pub fn pixels(&self, size: Size) -> Point {
386        Point::from((self.x.pixels(size.width()), self.y.pixels(size.height())))
387    }
388
389    /// Returns the position in percent
390    pub fn percent(&self, size: Size) -> Point {
391        Point::from((self.x.percent(size.width()), self.y.percent(size.height())))
392    }
393
394    /// Returns the position in a range from 0.0 to 1.0
395    pub fn unit_percent(&self, size: Size) -> Point {
396        Point::from((
397            self.x.unit_percent(size.width()),
398            self.y.unit_percent(size.height()),
399        ))
400    }
401}
402
403impl Default for OnPlanePos {
404    fn default() -> Self {
405        OnPlanePos::new(Default::default(), Default::default())
406    }
407}
408
409/// Describes a position on a line
410#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
411pub struct OnLinePos {
412    pos: f64,
413    kind: OnLinePosKind,
414}
415
416impl OnLinePos {
417    pub fn new(pos: f64, kind: OnLinePosKind) -> OnLinePos {
418        OnLinePos { pos, kind }
419    }
420
421    pub fn from_unit_percent(pos: f64) -> OnLinePos {
422        Self::new(pos * 100.0, OnLinePosKind::Percentage)
423    }
424
425    pub fn pos(&self) -> f64 {
426        self.pos
427    }
428
429    /// Returns the position in pixels
430    pub fn pixels(&self, line_length: f64) -> f64 {
431        match self.kind {
432            OnLinePosKind::Pixels => self.pos,
433            OnLinePosKind::Percentage => line_length * self.pos / 100.0,
434        }
435    }
436
437    /// Returns the position in percent
438    pub fn percent(&self, line_length: f64) -> f64 {
439        match self.kind {
440            OnLinePosKind::Pixels => self.pos / line_length * 100.0,
441            OnLinePosKind::Percentage => self.pos,
442        }
443    }
444
445    /// Returns the position in a range from 0.0 to 1.0
446    pub fn unit_percent(&self, line_length: f64) -> f64 {
447        self.percent(line_length) / 100.0
448    }
449}
450
451impl Default for OnLinePos {
452    fn default() -> Self {
453        Self {
454            pos: 0.0,
455            kind: OnLinePosKind::default(),
456        }
457    }
458}
459
460impl<N> TryFrom<(N, &str)> for OnLinePos
461where
462    N: Into<f64>,
463{
464    type Error = ();
465
466    fn try_from(value: (N, &str)) -> Result<Self, Self::Error> {
467        let kind = OnLinePosKind::try_from(value.1)?;
468        Ok(OnLinePos {
469            pos: (value.0).into(),
470            kind,
471        })
472    }
473}
474
475/// This only is used to communicate the kind of `OnLinePos` we are using
476#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
477pub enum OnLinePosKind {
478    /// A number from 0.0 to 100.0
479    Percentage,
480    /// This tells to you that `OnLinePos` is storing the position in pixels directly
481    Pixels,
482}
483
484impl TryFrom<&str> for OnLinePosKind {
485    type Error = ();
486
487    fn try_from(value: &str) -> Result<Self, Self::Error> {
488        match value {
489            "px" => Ok(OnLinePosKind::Pixels),
490            "%" => Ok(OnLinePosKind::Percentage),
491            _ => Err(()),
492        }
493    }
494}
495
496impl Default for OnLinePosKind {
497    fn default() -> Self {
498        Self::Pixels
499    }
500}
501
502impl Neg for OnLinePos {
503    type Output = OnLinePos;
504
505    fn neg(mut self) -> Self::Output {
506        self.pos = -self.pos;
507        self
508    }
509}