pixel_widgets/style/
mod.rs

1#![doc = include_str!("../../style.md")]
2use std::collections::HashMap;
3use std::iter::Peekable;
4
5use crate::bitset::BitSet;
6use crate::cache::Cache;
7use crate::draw::{Background, Color, ImageData, Patch};
8use crate::layout::{Align, Direction, Rectangle, Size};
9use crate::text::{Font, TextWrap};
10
11/// Style building tools
12pub mod builder;
13mod parse;
14mod tokenize;
15pub(crate) mod tree;
16
17use crate::graphics::Graphics;
18use futures::future::Map;
19use futures::FutureExt;
20use parse::*;
21use std::future::Future;
22use std::path::Path;
23use std::sync::{Arc, Mutex};
24use tokenize::*;
25
26use builder::*;
27
28/// Errors that can be encountered while loading a stylesheet
29#[derive(Debug)]
30pub enum Error {
31    /// Syntax error
32    Syntax(String, TokenPos),
33    /// Unexpected end of file error
34    Eof,
35    /// Image loading error
36    Image(image::ImageError),
37    /// File input/output error
38    Io(Box<dyn std::error::Error + Send + Sync>),
39}
40
41/// Container for all styling data.
42pub struct Style {
43    cache: Arc<Mutex<Cache>>,
44    resolved: Mutex<HashMap<BitSet, Arc<Stylesheet>>>,
45    default: Stylesheet,
46    rule_tree: tree::RuleTree,
47}
48
49#[doc(hidden)]
50pub trait ReadFn: 'static + Clone {
51    type Future: Future<Output = anyhow::Result<Vec<u8>>>;
52
53    fn read(&self, path: &Path) -> Self::Future;
54}
55
56impl<T, F, E> ReadFn for T
57where
58    T: 'static + Fn(&Path) -> F + Clone,
59    F: Future<Output = Result<Vec<u8>, E>>,
60    E: Into<anyhow::Error>,
61{
62    #[allow(clippy::type_complexity)]
63    type Future = Map<F, fn(Result<Vec<u8>, E>) -> anyhow::Result<Vec<u8>>>;
64
65    fn read(&self, path: &Path) -> Self::Future {
66        (*self)(path).map(|r| r.map_err(|e| e.into()))
67    }
68}
69
70/// A fully resolved stylesheet, passed by reference to [`Widget::draw`](../widget/trait.Widget.html).
71/// Contains the resolved values of all possible style properties.
72#[derive(Clone, Debug)]
73pub struct Stylesheet {
74    /// Widget width
75    pub width: Size,
76    /// Widget height
77    pub height: Size,
78    /// Background for the widget that full covers the layout rect
79    pub background: Background,
80    /// Amount of padding to use on each side of the content
81    pub padding: Rectangle,
82    /// Size of the margin on each side of the widget
83    pub margin: Rectangle,
84    /// Color to use for foreground drawing, including text
85    pub color: Color,
86    /// Font to use for text rendering
87    pub font: Font,
88    /// Size of text
89    pub text_size: f32,
90    /// Wrapping strategy for text
91    pub text_wrap: TextWrap,
92    /// Layout direction for widgets that support it (atm not text unfortunately..)
93    pub direction: Direction,
94    /// How to align children horizontally
95    pub align_horizontal: Align,
96    /// How to align children vertically
97    pub align_vertical: Align,
98    /// Flags
99    pub flags: Vec<String>,
100}
101
102/// A style property and it's value
103#[derive(Debug)]
104pub enum Declaration<I = ImageId, P = PatchId, F = FontId> {
105    /// no background
106    BackgroundNone,
107    /// background color
108    BackgroundColor(Color),
109    /// background image
110    BackgroundImage(I, Color),
111    /// background patch
112    BackgroundPatch(P, Color),
113    /// font
114    Font(F),
115    /// color
116    Color(Color),
117    /// padding
118    Padding(Rectangle),
119    /// padding left
120    PaddingLeft(f32),
121    /// Padding right
122    PaddingRight(f32),
123    /// Padding top
124    PaddingTop(f32),
125    /// Padding bottom
126    PaddingBottom(f32),
127    /// margin
128    Margin(Rectangle),
129    /// padding left
130    MarginLeft(f32),
131    /// Padding right
132    MarginRight(f32),
133    /// Padding top
134    MarginTop(f32),
135    /// Padding bottom
136    MarginBottom(f32),
137    /// text-size
138    TextSize(f32),
139    /// text-wrap
140    TextWrap(TextWrap),
141    /// width
142    Width(Size),
143    /// height
144    Height(Size),
145    /// layout-direction
146    LayoutDirection(Direction),
147    /// align-horizontal
148    AlignHorizontal(Align),
149    /// align-vertical
150    AlignVertical(Align),
151    /// flag: true;
152    AddFlag(String),
153    /// flag: false;
154    RemoveFlag(String),
155}
156
157/// A selector that selects widgets that match some property.
158#[derive(Debug, Clone, PartialEq)]
159pub enum Selector {
160    /// Should be ignored when building
161    Root,
162    /// Match a widget
163    Widget(SelectorWidget),
164    /// Match a widget that is a direct child of the parent
165    WidgetDirectChild(SelectorWidget),
166    /// Match a widget that follows directly after the previous widget
167    WidgetDirectAfter(SelectorWidget),
168    /// Match a widget that follows after a previous widget
169    WidgetAfter(SelectorWidget),
170    /// Match the nth child widget modulo a number
171    NthMod(usize, usize),
172    /// Match the nth child widget counted from the last child widget modulo a number
173    NthLastMod(usize, usize),
174    /// Match the nth child widget
175    Nth(usize),
176    /// Match the nth child widget counted from the last child widget
177    NthLast(usize),
178    /// Match widgets that are the only child of their parent
179    OnlyChild,
180    /// Match widgets that have a class
181    Class(String),
182    /// Match widgets that are in a state
183    State(StyleState<String>),
184    /// Invert the nested selector
185    Not(Box<Selector>),
186}
187
188/// Widget name as used in a `Selector`.
189#[derive(Debug, Clone, PartialEq, Eq)]
190pub enum SelectorWidget {
191    /// Match any widget
192    Any,
193    /// Match specific widgets
194    Some(String),
195}
196
197/// Widget states
198// !!Note: do not forget to add new variants to the eq impl!!
199#[derive(Debug, Clone)]
200pub enum StyleState<S: AsRef<str>> {
201    /// When the mouse is over the widget
202    Hover,
203    /// When the mouse is clicking on the widget
204    Pressed,
205    /// When the widget is in a checked state (checkbox, radio button)
206    Checked,
207    /// When a widget is disabled
208    Disabled,
209    /// When a widget has input focus
210    Focused,
211    /// When a widget in an expanded state
212    Open,
213    /// When a widget is in a collapsed state
214    Closed,
215    /// When a drag widget is being dragged
216    Drag,
217    /// When a drop widget accepts a dragged widget before it's dropped
218    Drop,
219    /// When a drop widget denies a dragged widget
220    DropDenied,
221    /// Custom state for custom widgets
222    Custom(S),
223}
224
225impl Style {
226    /// Returns a new `StyleBuilder`.
227    pub fn builder() -> StyleBuilder {
228        StyleBuilder::default()
229    }
230
231    pub(crate) fn get(&self, style: &BitSet) -> Arc<Stylesheet> {
232        let mut resolved = self.resolved.lock().unwrap();
233        if let Some(existing) = resolved.get(style) {
234            return existing.clone();
235        }
236        let mut computed = self.default.clone();
237        for rule in self.rule_tree.iter_declarations(style) {
238            rule.apply(&mut computed);
239        }
240        let result = Arc::new(computed);
241        resolved.insert(style.clone(), result.clone());
242        result
243    }
244
245    pub(crate) fn rule_tree(&self) -> &tree::RuleTree {
246        &self.rule_tree
247    }
248
249    pub(crate) fn cache(&self) -> Arc<Mutex<Cache>> {
250        self.cache.clone()
251    }
252
253    /// Retrieve a `Graphics` loader that can be used to load images
254    pub fn graphics(&self) -> Graphics {
255        Graphics { cache: self.cache() }
256    }
257}
258
259impl std::fmt::Debug for Style {
260    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
261        fmt.debug_struct("Style").field("rule_tree", &self.rule_tree).finish()
262    }
263}
264
265impl Stylesheet {
266    /// Returns whether a flag is set in this stylesheet
267    pub fn contains(&self, flag: &str) -> bool {
268        self.flags.binary_search_by_key(&flag, |s| s.as_str()).is_ok()
269    }
270}
271
272impl Declaration<ImageData, Patch, Font> {
273    /// Apply values to a `Stylesheet`.
274    pub fn apply(&self, stylesheet: &mut Stylesheet) {
275        match self {
276            Declaration::BackgroundNone => stylesheet.background = Background::None,
277            Declaration::BackgroundColor(x) => stylesheet.background = Background::Color(*x),
278            Declaration::BackgroundImage(x, y) => stylesheet.background = Background::Image(x.clone(), *y),
279            Declaration::BackgroundPatch(x, y) => stylesheet.background = Background::Patch(x.clone(), *y),
280            Declaration::Font(x) => stylesheet.font = x.clone(),
281            Declaration::Color(x) => stylesheet.color = *x,
282            Declaration::Padding(x) => stylesheet.padding = *x,
283            Declaration::PaddingLeft(x) => stylesheet.padding.left = *x,
284            Declaration::PaddingRight(x) => stylesheet.padding.right = *x,
285            Declaration::PaddingTop(x) => stylesheet.padding.top = *x,
286            Declaration::PaddingBottom(x) => stylesheet.padding.bottom = *x,
287            Declaration::Margin(x) => stylesheet.margin = *x,
288            Declaration::MarginLeft(x) => stylesheet.margin.left = *x,
289            Declaration::MarginRight(x) => stylesheet.margin.right = *x,
290            Declaration::MarginTop(x) => stylesheet.margin.top = *x,
291            Declaration::MarginBottom(x) => stylesheet.margin.bottom = *x,
292            Declaration::TextSize(x) => stylesheet.text_size = *x,
293            Declaration::TextWrap(x) => stylesheet.text_wrap = *x,
294            Declaration::Width(x) => stylesheet.width = *x,
295            Declaration::Height(x) => stylesheet.height = *x,
296            Declaration::LayoutDirection(x) => stylesheet.direction = *x,
297            Declaration::AlignHorizontal(x) => stylesheet.align_horizontal = *x,
298            Declaration::AlignVertical(x) => stylesheet.align_vertical = *x,
299            Declaration::AddFlag(x) => {
300                if let Err(insert_at) = stylesheet.flags.binary_search(x) {
301                    stylesheet.flags.insert(insert_at, x.clone());
302                }
303            }
304            Declaration::RemoveFlag(x) => {
305                if let Ok(exists) = stylesheet.flags.binary_search(x) {
306                    stylesheet.flags.remove(exists);
307                }
308            }
309        }
310    }
311}
312
313impl Selector {
314    /// Match a sibling widget of the current rule. If this selector is not a sibling selector `None` is returned.
315    pub fn match_sibling(&self, direct: bool, widget: &str) -> Option<bool> {
316        match self {
317            Selector::WidgetDirectAfter(ref sel_widget) => Some(direct && sel_widget.matches(widget)),
318            Selector::WidgetAfter(ref sel_widget) => Some(sel_widget.matches(widget)),
319            Selector::Not(ref selector) => selector.match_sibling(direct, widget).map(|b| !b),
320            _ => None,
321        }
322    }
323
324    /// Match a child widget of the current rule. If this selector is not a child selector `None` is returned.
325    pub fn match_child(&self, direct: bool, widget: &str) -> Option<bool> {
326        match self {
327            Selector::Widget(ref sel_widget) => Some(sel_widget.matches(widget)),
328            Selector::WidgetDirectChild(ref sel_widget) => Some(direct && sel_widget.matches(widget)),
329            Selector::Not(ref selector) => selector.match_child(direct, widget).map(|b| !b),
330            _ => None,
331        }
332    }
333
334    /// Match parameters of the widget matched by the current rule.
335    /// If this selector is not a meta selector `None` is returned.
336    pub fn match_meta<S: AsRef<str>>(
337        &self,
338        state: &[StyleState<S>],
339        class: &str,
340        n: usize,
341        len: usize,
342    ) -> Option<bool> {
343        match self {
344            Selector::State(ref sel_state) => Some(state.iter().any(|state| state.eq(sel_state))),
345            Selector::Class(ref sel_class) => Some(sel_class == class),
346            Selector::Nth(num) => Some(n == *num),
347            Selector::NthMod(num, den) => Some((n % *den) == *num),
348            Selector::NthLast(num) => Some(len - 1 - n == *num),
349            Selector::NthLastMod(num, den) => Some(((len - 1 - n) % *den) == *num),
350            Selector::OnlyChild => Some(n == 0 && len == 1),
351            Selector::Not(ref selector) => selector.match_meta(state, class, n, len).map(|b| !b),
352            _ => None,
353        }
354    }
355}
356
357impl SelectorWidget {
358    fn matches(&self, widget: &str) -> bool {
359        match self {
360            Self::Any => true,
361            Self::Some(ref select) => select == widget,
362        }
363    }
364}
365
366impl<A: AsRef<str>, B: AsRef<str>> PartialEq<StyleState<B>> for StyleState<A> {
367    fn eq(&self, other: &StyleState<B>) -> bool {
368        match (self, other) {
369            (StyleState::Hover, StyleState::Hover) => true,
370            (StyleState::Pressed, StyleState::Pressed) => true,
371            (StyleState::Checked, StyleState::Checked) => true,
372            (StyleState::Disabled, StyleState::Disabled) => true,
373            (StyleState::Focused, StyleState::Focused) => true,
374            (StyleState::Open, StyleState::Open) => true,
375            (StyleState::Closed, StyleState::Closed) => true,
376            (StyleState::Drag, StyleState::Drag) => true,
377            (StyleState::Drop, StyleState::Drop) => true,
378            (StyleState::DropDenied, StyleState::DropDenied) => true,
379            (StyleState::Custom(a), StyleState::Custom(b)) => a.as_ref().eq(b.as_ref()),
380
381            _ => false,
382        }
383    }
384}
385
386impl From<image::ImageError> for Error {
387    fn from(error: image::ImageError) -> Self {
388        Error::Image(error)
389    }
390}
391
392impl std::fmt::Display for Error {
393    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
394        match self {
395            Error::Syntax(error, pos) => write!(f, "Syntax error: {} at line {}:{}", error, pos.line, pos.col_start),
396            Error::Eof => write!(f, "Unexpected end of file reached"),
397            Error::Image(error) => write!(f, "Image decode error: {}", error),
398            Error::Io(error) => write!(f, "I/O error: {}", error),
399        }
400    }
401}
402
403impl std::error::Error for Error {}
404
405impl<'a> From<&'a str> for SelectorWidget {
406    fn from(s: &'a str) -> Self {
407        if s == "*" {
408            SelectorWidget::Any
409        } else {
410            SelectorWidget::Some(s.into())
411        }
412    }
413}
414
415impl<'a> From<&'a str> for StyleState<String> {
416    fn from(s: &'a str) -> Self {
417        match s {
418            "hover" => StyleState::Hover,
419            "pressed" => StyleState::Pressed,
420            "checked" => StyleState::Checked,
421            "disabled" => StyleState::Disabled,
422            "open" => StyleState::Open,
423            "closed" => StyleState::Closed,
424            "drag" => StyleState::Drag,
425            "drop" => StyleState::Drop,
426            "drop-denied" => StyleState::DropDenied,
427            other => StyleState::Custom(other.into()),
428        }
429    }
430}