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
11pub 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#[derive(Debug)]
30pub enum Error {
31 Syntax(String, TokenPos),
33 Eof,
35 Image(image::ImageError),
37 Io(Box<dyn std::error::Error + Send + Sync>),
39}
40
41pub 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#[derive(Clone, Debug)]
73pub struct Stylesheet {
74 pub width: Size,
76 pub height: Size,
78 pub background: Background,
80 pub padding: Rectangle,
82 pub margin: Rectangle,
84 pub color: Color,
86 pub font: Font,
88 pub text_size: f32,
90 pub text_wrap: TextWrap,
92 pub direction: Direction,
94 pub align_horizontal: Align,
96 pub align_vertical: Align,
98 pub flags: Vec<String>,
100}
101
102#[derive(Debug)]
104pub enum Declaration<I = ImageId, P = PatchId, F = FontId> {
105 BackgroundNone,
107 BackgroundColor(Color),
109 BackgroundImage(I, Color),
111 BackgroundPatch(P, Color),
113 Font(F),
115 Color(Color),
117 Padding(Rectangle),
119 PaddingLeft(f32),
121 PaddingRight(f32),
123 PaddingTop(f32),
125 PaddingBottom(f32),
127 Margin(Rectangle),
129 MarginLeft(f32),
131 MarginRight(f32),
133 MarginTop(f32),
135 MarginBottom(f32),
137 TextSize(f32),
139 TextWrap(TextWrap),
141 Width(Size),
143 Height(Size),
145 LayoutDirection(Direction),
147 AlignHorizontal(Align),
149 AlignVertical(Align),
151 AddFlag(String),
153 RemoveFlag(String),
155}
156
157#[derive(Debug, Clone, PartialEq)]
159pub enum Selector {
160 Root,
162 Widget(SelectorWidget),
164 WidgetDirectChild(SelectorWidget),
166 WidgetDirectAfter(SelectorWidget),
168 WidgetAfter(SelectorWidget),
170 NthMod(usize, usize),
172 NthLastMod(usize, usize),
174 Nth(usize),
176 NthLast(usize),
178 OnlyChild,
180 Class(String),
182 State(StyleState<String>),
184 Not(Box<Selector>),
186}
187
188#[derive(Debug, Clone, PartialEq, Eq)]
190pub enum SelectorWidget {
191 Any,
193 Some(String),
195}
196
197#[derive(Debug, Clone)]
200pub enum StyleState<S: AsRef<str>> {
201 Hover,
203 Pressed,
205 Checked,
207 Disabled,
209 Focused,
211 Open,
213 Closed,
215 Drag,
217 Drop,
219 DropDenied,
221 Custom(S),
223}
224
225impl Style {
226 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 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 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 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 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 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 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}