pixel_widgets/style/
builder.rs

1use image::RgbaImage;
2
3use super::*;
4use crate::component::Component;
5use anyhow::{Context, Error, Result};
6use std::pin::Pin;
7
8type RgbaImageFuture = Pin<Box<dyn Future<Output = Result<RgbaImage>>>>;
9type DataFuture = Pin<Box<dyn Future<Output = Result<Vec<u8>>>>>;
10
11/// Builds a style.
12#[derive(Default)]
13pub struct StyleBuilder {
14    pub(crate) images: HashMap<String, RgbaImageFuture>,
15    pub(crate) patches: HashMap<String, RgbaImageFuture>,
16    pub(crate) fonts: HashMap<String, DataFuture>,
17    pub(crate) rule_tree: tree::RuleTreeBuilder,
18}
19
20/// Handle to an image in a `StyleBuilder`.
21#[derive(Debug)]
22pub struct ImageId(pub(crate) String);
23/// Handle to a patch in a `StyleBuilder`.
24#[derive(Debug)]
25pub struct PatchId(pub(crate) String);
26/// Handle to a font in a `StyleBuilder`.
27#[derive(Debug)]
28pub struct FontId(pub(crate) String);
29
30/// Builder that adds style declarations to a selected rule.
31pub struct RuleBuilder {
32    selector: Vec<Selector>,
33    declarations: Vec<Declaration<ImageId, PatchId, FontId>>,
34}
35
36impl StyleBuilder {
37    fn base(foreground: Color, background: Color, primary: Color) -> Self {
38        Self::default()
39            .rule(RuleBuilder::new("*").color(foreground))
40            .rule(
41                RuleBuilder::new("button")
42                    .padding_all(5.0)
43                    .margin_all(5.0)
44                    .background_color(background),
45            )
46            .rule(RuleBuilder::new("button:hover").background_color(background.blend(primary, 0.5)))
47            .rule(RuleBuilder::new("button:pressed").background_color(primary))
48            .rule(
49                RuleBuilder::new("dropdown")
50                    .background_color(background)
51                    .color(background.blend(primary, 0.5))
52                    .padding_all(5.0)
53                    .margin_all(5.0),
54            )
55            .rule(
56                RuleBuilder::new("input")
57                    .width(300.0)
58                    .background_color(Color::white())
59                    .color(Color::black())
60                    .padding_all(5.0)
61                    .margin_all(5.0),
62            )
63            .rule(RuleBuilder::new("layers").fill_width().fill_height())
64            .rule(
65                RuleBuilder::new("menu")
66                    .background_color(background)
67                    .color(background.blend(primary, 0.5))
68                    .padding_all(5.0),
69            )
70            .rule(RuleBuilder::new("spacer").fill_width().fill_height())
71            .rule(
72                RuleBuilder::new("window")
73                    .background_color(background.blend(foreground, 0.2))
74                    .padding_all(2.0),
75            )
76            .rule(RuleBuilder::new("window > *:nth-child(0)").background_color(background.blend(primary, 0.2)))
77    }
78
79    /// Add a rule defined in a [`RuleBuilder`](struct.RuleBuilder.html) to the `StyleBuilder`.
80    pub fn rule(mut self, builder: RuleBuilder) -> Self {
81        self.rule_tree.insert(builder.selector.as_slice(), builder.declarations);
82        self
83    }
84
85    /// Prepend the given selector to all rules in this `StyleBuilder`.
86    pub fn scope<S: AsRef<str>>(mut self, selector: S) -> Self {
87        let mut old = std::mem::take(&mut self.rule_tree);
88
89        let selector = parse_selectors(tokenize(selector.as_ref().to_string()).unwrap()).unwrap();
90        if let Some(new_root) = selector.as_slice().last() {
91            old.selector = new_root.clone();
92        }
93        self.rule_tree.select(selector.as_slice()).merge(old);
94
95        self
96    }
97
98    /// Merge with another `StyleBuilder`.
99    pub fn merge(mut self, builder: StyleBuilder) -> Self {
100        self.images.extend(builder.images);
101        self.patches.extend(builder.patches);
102        self.fonts.extend(builder.fonts);
103        self.rule_tree.merge(builder.rule_tree);
104        self
105    }
106
107    /// Include the scoped style of a `Component` in this `StyleBuilder`.
108    pub fn component<C: Component>(mut self) -> Self {
109        let mut builder = C::style();
110        self.images.extend(builder.images);
111        self.patches.extend(builder.patches);
112        self.fonts.extend(builder.fonts);
113        let name = std::any::type_name::<C>().to_string();
114        builder.rule_tree.selector = Selector::Widget(SelectorWidget::Some(name.clone()));
115        self.rule_tree
116            .select(&[Selector::Widget(SelectorWidget::Some(name))])
117            .merge(builder.rule_tree);
118        self
119    }
120
121    /// Asynchronously load a stylesheet from a .pwss file. See the [style module documentation](../index.html) on how to write
122    /// .pwss files.
123    pub async fn from_read_fn<P, R>(path: P, read: R) -> anyhow::Result<Self>
124    where
125        P: AsRef<Path>,
126        R: ReadFn,
127    {
128        let text = String::from_utf8(read.read(path.as_ref()).await?).unwrap();
129        Ok(parse(tokenize(text)?, read).await?)
130    }
131
132    /// Synchronously load a stylesheet from a .pwss file. See the [style module documentation](../index.html) on how to write
133    /// .pwss files.
134    pub fn from_file<P>(path: P) -> anyhow::Result<Self>
135    where
136        P: AsRef<Path>,
137    {
138        futures::executor::block_on(Self::from_read_fn(path, |path: &Path| {
139            std::future::ready(std::fs::read(path))
140        }))
141    }
142
143    /// Returns an `ImageId` for the `key`.
144    /// When the style is built, the image is loaded using the closure.
145    pub fn load_image(
146        &mut self,
147        key: impl Into<String>,
148        load: impl FnOnce() -> Result<RgbaImage> + 'static,
149    ) -> ImageId {
150        self.load_image_async(key, async move { load() })
151    }
152
153    /// Returns a `PatchId` for the `key`.
154    /// When the style is built, the 9-patch is loaded using the closure.
155    pub fn load_patch(
156        &mut self,
157        key: impl Into<String>,
158        load: impl FnOnce() -> Result<RgbaImage> + 'static,
159    ) -> PatchId {
160        self.load_patch_async(key, async move { load() })
161    }
162
163    /// Returns a `FontId` for the `key`.
164    /// When the style is built, the font is loaded using the closure.
165    /// The closure must return the bytes of a .ttf file.
166    pub fn load_font(&mut self, key: impl Into<String>, load: impl FnOnce() -> Result<Vec<u8>> + 'static) -> FontId {
167        self.load_font_async(key, async move { load() })
168    }
169
170    /// Returns an `ImageId` for the `key`.
171    /// When the style is built, the image is loaded by awaiting the future.
172    pub fn load_image_async(
173        &mut self,
174        key: impl Into<String>,
175        fut: impl Future<Output = Result<RgbaImage>> + 'static,
176    ) -> ImageId {
177        let key = key.into();
178        if let std::collections::hash_map::Entry::Vacant(v) = self.images.entry(key.clone()) {
179            v.insert(Box::pin(fut));
180        }
181        ImageId(key)
182    }
183
184    /// Returns a `PatchId` for the `key`.
185    /// When the style is built, the 9-patch is loaded by awaiting the future.
186    pub fn load_patch_async(
187        &mut self,
188        key: impl Into<String>,
189        fut: impl Future<Output = Result<RgbaImage>> + 'static,
190    ) -> PatchId {
191        let key = key.into();
192        if let std::collections::hash_map::Entry::Vacant(v) = self.patches.entry(key.clone()) {
193            v.insert(Box::pin(fut));
194        }
195        PatchId(key)
196    }
197
198    /// Returns a `FontId` for the `key`.
199    /// When the style is built, the font is loaded by awaiting the future.
200    /// The future must output the bytes of a .ttf file.
201    pub fn load_font_async(
202        &mut self,
203        key: impl Into<String>,
204        fut: impl Future<Output = Result<Vec<u8>>> + 'static,
205    ) -> FontId {
206        let key = key.into();
207        if let std::collections::hash_map::Entry::Vacant(v) = self.fonts.entry(key.clone()) {
208            v.insert(Box::pin(fut));
209        }
210        FontId(key)
211    }
212
213    /// Builds the `Style`. All loading of images, 9 patches and fonts happens in this method.
214    /// If any of them fail, an error is returned.
215    pub async fn build_async(mut self) -> Result<Style> {
216        self = Self::base(Color::white(), Color::rgb(0.3, 0.3, 0.3), Color::blue()).merge(self);
217
218        let mut cache = Cache::new(512);
219
220        let font = cache.load_font(include_bytes!("default_font.ttf").to_vec()).unwrap();
221
222        let mut images = HashMap::new();
223        for (key, value) in self.images {
224            images.insert(
225                key.clone(),
226                cache.load_image(
227                    value
228                        .await
229                        .with_context(|| format!("Failed to load image \"{}\": ", key))?,
230                ),
231            );
232        }
233
234        let mut patches = HashMap::new();
235        for (key, value) in self.patches {
236            patches.insert(
237                key.clone(),
238                cache.load_patch(
239                    value
240                        .await
241                        .with_context(|| format!("Failed to load 9 patch \"{}\": ", key))?,
242                ),
243            );
244        }
245
246        let mut fonts = HashMap::new();
247        for (key, value) in self.fonts {
248            let load = async { Result::<_, Error>::Ok(cache.load_font(value.await?)?) };
249            fonts.insert(
250                key.clone(),
251                load.await
252                    .with_context(|| format!("Failed to load font \"{}\": ", key))?,
253            );
254        }
255
256        Ok(Style {
257            cache: Arc::new(Mutex::new(cache)),
258            resolved: Default::default(),
259            default: Stylesheet {
260                background: Background::None,
261                font,
262                color: Color::white(),
263                padding: Rectangle::zero(),
264                margin: Rectangle::zero(),
265                text_size: 16.0,
266                text_wrap: TextWrap::NoWrap,
267                width: Size::Shrink,
268                height: Size::Shrink,
269                direction: Direction::LeftToRight,
270                align_horizontal: Align::Begin,
271                align_vertical: Align::Begin,
272                flags: Vec::new(),
273            },
274            rule_tree: self.rule_tree.build(&images, &patches, &fonts),
275        })
276    }
277
278    /// Builds the `Style`. All loading of images, 9 patches and fonts happens in this method.
279    /// If any of them fail, an error is returned.
280    pub fn build(self) -> Result<Style> {
281        futures::executor::block_on(self.build_async())
282    }
283}
284
285impl TryInto<Style> for StyleBuilder {
286    type Error = Error;
287
288    fn try_into(self) -> Result<Style> {
289        self.build()
290    }
291}
292
293impl RuleBuilder {
294    /// Constructs a new `RuleBuilder` for the given selector.
295    /// The selector must follow the same syntax as the [.pwss file format](../index.html).
296    ///
297    /// Panics if the selector can't be parsed.
298    ///
299    /// ```rust
300    /// use pixel_widgets::prelude::*;
301    ///
302    /// // Sets the background of the first direct child of any window widget
303    /// RuleBuilder::new("window > * :nth-child(0)").background_color(Color::red());
304    /// ```
305    pub fn new<S: AsRef<str>>(selector: S) -> Self {
306        Self {
307            selector: parse_selectors(tokenize(selector.as_ref().to_string()).unwrap()).unwrap(),
308            declarations: Vec::new(),
309        }
310    }
311    /// Clears the background
312    pub fn background_none(mut self) -> Self {
313        self.declarations.push(Declaration::BackgroundNone);
314        self
315    }
316    /// Sets the background to a color
317    pub fn background_color(mut self, color: Color) -> Self {
318        self.declarations.push(Declaration::BackgroundColor(color));
319        self
320    }
321    /// Sets the background to a colored image
322    pub fn background_image(mut self, image_data: ImageId, color: Color) -> Self {
323        self.declarations.push(Declaration::BackgroundImage(image_data, color));
324        self
325    }
326    /// Sets the background to a colored patch
327    pub fn background_patch(mut self, patch: PatchId, color: Color) -> Self {
328        self.declarations.push(Declaration::BackgroundPatch(patch, color));
329        self
330    }
331    /// Sets the font
332    pub fn font(mut self, value: FontId) -> Self {
333        self.declarations.push(Declaration::Font(value));
334        self
335    }
336    /// Sets the foreground color
337    pub fn color(mut self, value: Color) -> Self {
338        self.declarations.push(Declaration::Color(value));
339        self
340    }
341    /// Sets padding
342    pub fn padding(mut self, value: Rectangle) -> Self {
343        self.declarations.push(Declaration::Padding(value));
344        self
345    }
346    /// Sets all padding values to the same value
347    pub fn padding_all(self, value: f32) -> Self {
348        self.padding(Rectangle {
349            left: value,
350            top: value,
351            right: value,
352            bottom: value,
353        })
354    }
355    /// Sets horizontal padding values to the same value
356    pub fn padding_horizontal(self, value: f32) -> Self {
357        self.padding_left(value).padding_right(value)
358    }
359    /// Sets vertical padding values to the same value
360    pub fn padding_vertical(self, value: f32) -> Self {
361        self.padding_top(value).padding_bottom(value)
362    }
363    /// Sets left padding
364    pub fn padding_left(mut self, value: f32) -> Self {
365        self.declarations.push(Declaration::PaddingLeft(value));
366        self
367    }
368    /// Sets right padding
369    pub fn padding_right(mut self, value: f32) -> Self {
370        self.declarations.push(Declaration::PaddingRight(value));
371        self
372    }
373    /// Sets top padding
374    pub fn padding_top(mut self, value: f32) -> Self {
375        self.declarations.push(Declaration::PaddingTop(value));
376        self
377    }
378    /// Sets bottom padding
379    pub fn padding_bottom(mut self, value: f32) -> Self {
380        self.declarations.push(Declaration::PaddingBottom(value));
381        self
382    }
383    /// Sets the margins
384    pub fn margin(mut self, value: Rectangle) -> Self {
385        self.declarations.push(Declaration::Margin(value));
386        self
387    }
388    /// Sets all margin values to the same value
389    pub fn margin_all(self, value: f32) -> Self {
390        self.margin(Rectangle {
391            left: value,
392            top: value,
393            right: value,
394            bottom: value,
395        })
396    }
397    /// Sets horizontal margin values to the same value
398    pub fn margin_horizontal(self, value: f32) -> Self {
399        self.margin_left(value).margin_right(value)
400    }
401    /// Sets vertical margin values to the same value
402    pub fn margin_vertical(self, value: f32) -> Self {
403        self.margin_top(value).margin_bottom(value)
404    }
405    /// Sets the left margin
406    pub fn margin_left(mut self, value: f32) -> Self {
407        self.declarations.push(Declaration::MarginLeft(value));
408        self
409    }
410    /// Sets the right margin
411    pub fn margin_right(mut self, value: f32) -> Self {
412        self.declarations.push(Declaration::MarginRight(value));
413        self
414    }
415    /// Sets the top margin
416    pub fn margin_top(mut self, value: f32) -> Self {
417        self.declarations.push(Declaration::MarginTop(value));
418        self
419    }
420    /// Sets the bottom margin
421    pub fn margin_bottom(mut self, value: f32) -> Self {
422        self.declarations.push(Declaration::MarginBottom(value));
423        self
424    }
425    /// Sets the text size
426    pub fn text_size(mut self, value: f32) -> Self {
427        self.declarations.push(Declaration::TextSize(value));
428        self
429    }
430    /// Sets the way text wraps
431    pub fn text_wrap(mut self, value: TextWrap) -> Self {
432        self.declarations.push(Declaration::TextWrap(value));
433        self
434    }
435    /// Sets the preferred width
436    pub fn width(mut self, value: impl Into<Size>) -> Self {
437        self.declarations.push(Declaration::Width(value.into()));
438        self
439    }
440    /// Sets the preferred width to Size::Fill(1)
441    pub fn fill_width(mut self) -> Self {
442        self.declarations.push(Declaration::Width(Size::Fill(1)));
443        self
444    }
445    /// Sets the preferred height
446    pub fn height(mut self, value: impl Into<Size>) -> Self {
447        self.declarations.push(Declaration::Height(value.into()));
448        self
449    }
450    /// Sets the preferred height to Size::Fill(1)
451    pub fn fill_height(mut self) -> Self {
452        self.declarations.push(Declaration::Height(Size::Fill(1)));
453        self
454    }
455    /// Sets the direction for layouting
456    pub fn layout_direction(mut self, value: Direction) -> Self {
457        self.declarations.push(Declaration::LayoutDirection(value));
458        self
459    }
460    /// Sets the horizontal alignment
461    pub fn align_horizontal(mut self, value: Align) -> Self {
462        self.declarations.push(Declaration::AlignHorizontal(value));
463        self
464    }
465    /// Sets the vertical alignment
466    pub fn align_vertical(mut self, value: Align) -> Self {
467        self.declarations.push(Declaration::AlignVertical(value));
468        self
469    }
470    /// Adds a flag to the stylesheet
471    pub fn add_flag(mut self, value: String) -> Self {
472        self.declarations.push(Declaration::AddFlag(value));
473        self
474    }
475    /// Removes a flag from the stylesheet
476    pub fn remove_flag(mut self, value: String) -> Self {
477        self.declarations.push(Declaration::RemoveFlag(value));
478        self
479    }
480}