#![doc = include_str!("../../style.md")]
use std::collections::HashMap;
use std::iter::Peekable;
use crate::bitset::BitSet;
use crate::cache::Cache;
use crate::draw::{Background, Color, ImageData, Patch};
use crate::layout::{Align, Direction, Rectangle, Size};
use crate::text::{Font, TextWrap};
pub mod builder;
mod parse;
mod tokenize;
pub(crate) mod tree;
use crate::graphics::Graphics;
use futures::future::Map;
use futures::FutureExt;
use parse::*;
use std::future::Future;
use std::path::Path;
use std::sync::{Arc, Mutex};
use tokenize::*;
use builder::*;
#[derive(Debug)]
pub enum Error {
Syntax(String, TokenPos),
Eof,
Image(image::ImageError),
Io(Box<dyn std::error::Error + Send + Sync>),
}
pub struct Style {
cache: Arc<Mutex<Cache>>,
resolved: Mutex<HashMap<BitSet, Arc<Stylesheet>>>,
default: Stylesheet,
rule_tree: tree::RuleTree,
}
#[doc(hidden)]
pub trait ReadFn: 'static + Clone {
type Future: Future<Output = anyhow::Result<Vec<u8>>>;
fn read(&self, path: &Path) -> Self::Future;
}
impl<T, F, E> ReadFn for T
where
T: 'static + Fn(&Path) -> F + Clone,
F: Future<Output = Result<Vec<u8>, E>>,
E: Into<anyhow::Error>,
{
#[allow(clippy::type_complexity)]
type Future = Map<F, fn(Result<Vec<u8>, E>) -> anyhow::Result<Vec<u8>>>;
fn read(&self, path: &Path) -> Self::Future {
(*self)(path).map(|r| r.map_err(|e| e.into()))
}
}
#[derive(Clone, Debug)]
pub struct Stylesheet {
pub width: Size,
pub height: Size,
pub background: Background,
pub padding: Rectangle,
pub margin: Rectangle,
pub color: Color,
pub font: Font,
pub text_size: f32,
pub text_wrap: TextWrap,
pub direction: Direction,
pub align_horizontal: Align,
pub align_vertical: Align,
pub flags: Vec<String>,
}
#[derive(Debug)]
pub enum Declaration<I = ImageId, P = PatchId, F = FontId> {
BackgroundNone,
BackgroundColor(Color),
BackgroundImage(I, Color),
BackgroundPatch(P, Color),
Font(F),
Color(Color),
Padding(Rectangle),
PaddingLeft(f32),
PaddingRight(f32),
PaddingTop(f32),
PaddingBottom(f32),
Margin(Rectangle),
MarginLeft(f32),
MarginRight(f32),
MarginTop(f32),
MarginBottom(f32),
TextSize(f32),
TextWrap(TextWrap),
Width(Size),
Height(Size),
LayoutDirection(Direction),
AlignHorizontal(Align),
AlignVertical(Align),
AddFlag(String),
RemoveFlag(String),
}
#[derive(Debug, Clone, PartialEq)]
pub enum Selector {
Root,
Widget(SelectorWidget),
WidgetDirectChild(SelectorWidget),
WidgetDirectAfter(SelectorWidget),
WidgetAfter(SelectorWidget),
NthMod(usize, usize),
NthLastMod(usize, usize),
Nth(usize),
NthLast(usize),
OnlyChild,
Class(String),
State(StyleState<String>),
Not(Box<Selector>),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SelectorWidget {
Any,
Some(String),
}
#[derive(Debug, Clone)]
pub enum StyleState<S: AsRef<str>> {
Hover,
Pressed,
Checked,
Disabled,
Focused,
Open,
Closed,
Drag,
Drop,
DropDenied,
Custom(S),
}
impl Style {
pub fn builder() -> StyleBuilder {
StyleBuilder::default()
}
pub(crate) fn get(&self, style: &BitSet) -> Arc<Stylesheet> {
let mut resolved = self.resolved.lock().unwrap();
if let Some(existing) = resolved.get(style) {
return existing.clone();
}
let mut computed = self.default.clone();
for rule in self.rule_tree.iter_declarations(style) {
rule.apply(&mut computed);
}
let result = Arc::new(computed);
resolved.insert(style.clone(), result.clone());
result
}
pub(crate) fn rule_tree(&self) -> &tree::RuleTree {
&self.rule_tree
}
pub(crate) fn cache(&self) -> Arc<Mutex<Cache>> {
self.cache.clone()
}
pub fn graphics(&self) -> Graphics {
Graphics { cache: self.cache() }
}
}
impl std::fmt::Debug for Style {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
fmt.debug_struct("Style").field("rule_tree", &self.rule_tree).finish()
}
}
impl Stylesheet {
pub fn contains(&self, flag: &str) -> bool {
self.flags.binary_search_by_key(&flag, |s| s.as_str()).is_ok()
}
}
impl Declaration<ImageData, Patch, Font> {
pub fn apply(&self, stylesheet: &mut Stylesheet) {
match self {
Declaration::BackgroundNone => stylesheet.background = Background::None,
Declaration::BackgroundColor(x) => stylesheet.background = Background::Color(*x),
Declaration::BackgroundImage(x, y) => stylesheet.background = Background::Image(x.clone(), *y),
Declaration::BackgroundPatch(x, y) => stylesheet.background = Background::Patch(x.clone(), *y),
Declaration::Font(x) => stylesheet.font = x.clone(),
Declaration::Color(x) => stylesheet.color = *x,
Declaration::Padding(x) => stylesheet.padding = *x,
Declaration::PaddingLeft(x) => stylesheet.padding.left = *x,
Declaration::PaddingRight(x) => stylesheet.padding.right = *x,
Declaration::PaddingTop(x) => stylesheet.padding.top = *x,
Declaration::PaddingBottom(x) => stylesheet.padding.bottom = *x,
Declaration::Margin(x) => stylesheet.margin = *x,
Declaration::MarginLeft(x) => stylesheet.margin.left = *x,
Declaration::MarginRight(x) => stylesheet.margin.right = *x,
Declaration::MarginTop(x) => stylesheet.margin.top = *x,
Declaration::MarginBottom(x) => stylesheet.margin.bottom = *x,
Declaration::TextSize(x) => stylesheet.text_size = *x,
Declaration::TextWrap(x) => stylesheet.text_wrap = *x,
Declaration::Width(x) => stylesheet.width = *x,
Declaration::Height(x) => stylesheet.height = *x,
Declaration::LayoutDirection(x) => stylesheet.direction = *x,
Declaration::AlignHorizontal(x) => stylesheet.align_horizontal = *x,
Declaration::AlignVertical(x) => stylesheet.align_vertical = *x,
Declaration::AddFlag(x) => {
if let Err(insert_at) = stylesheet.flags.binary_search(x) {
stylesheet.flags.insert(insert_at, x.clone());
}
}
Declaration::RemoveFlag(x) => {
if let Ok(exists) = stylesheet.flags.binary_search(x) {
stylesheet.flags.remove(exists);
}
}
}
}
}
impl Selector {
pub fn match_sibling(&self, direct: bool, widget: &str) -> Option<bool> {
match self {
Selector::WidgetDirectAfter(ref sel_widget) => Some(direct && sel_widget.matches(widget)),
Selector::WidgetAfter(ref sel_widget) => Some(sel_widget.matches(widget)),
Selector::Not(ref selector) => selector.match_sibling(direct, widget).map(|b| !b),
_ => None,
}
}
pub fn match_child(&self, direct: bool, widget: &str) -> Option<bool> {
match self {
Selector::Widget(ref sel_widget) => Some(sel_widget.matches(widget)),
Selector::WidgetDirectChild(ref sel_widget) => Some(direct && sel_widget.matches(widget)),
Selector::Not(ref selector) => selector.match_child(direct, widget).map(|b| !b),
_ => None,
}
}
pub fn match_meta<S: AsRef<str>>(
&self,
state: &[StyleState<S>],
class: &str,
n: usize,
len: usize,
) -> Option<bool> {
match self {
Selector::State(ref sel_state) => Some(state.iter().any(|state| state.eq(sel_state))),
Selector::Class(ref sel_class) => Some(sel_class == class),
Selector::Nth(num) => Some(n == *num),
Selector::NthMod(num, den) => Some((n % *den) == *num),
Selector::NthLast(num) => Some(len - 1 - n == *num),
Selector::NthLastMod(num, den) => Some(((len - 1 - n) % *den) == *num),
Selector::OnlyChild => Some(n == 0 && len == 1),
Selector::Not(ref selector) => selector.match_meta(state, class, n, len).map(|b| !b),
_ => None,
}
}
}
impl SelectorWidget {
fn matches(&self, widget: &str) -> bool {
match self {
Self::Any => true,
Self::Some(ref select) => select == widget,
}
}
}
impl<A: AsRef<str>, B: AsRef<str>> PartialEq<StyleState<B>> for StyleState<A> {
fn eq(&self, other: &StyleState<B>) -> bool {
match (self, other) {
(StyleState::Hover, StyleState::Hover) => true,
(StyleState::Pressed, StyleState::Pressed) => true,
(StyleState::Checked, StyleState::Checked) => true,
(StyleState::Disabled, StyleState::Disabled) => true,
(StyleState::Focused, StyleState::Focused) => true,
(StyleState::Open, StyleState::Open) => true,
(StyleState::Closed, StyleState::Closed) => true,
(StyleState::Drag, StyleState::Drag) => true,
(StyleState::Drop, StyleState::Drop) => true,
(StyleState::DropDenied, StyleState::DropDenied) => true,
(StyleState::Custom(a), StyleState::Custom(b)) => a.as_ref().eq(b.as_ref()),
_ => false,
}
}
}
impl From<image::ImageError> for Error {
fn from(error: image::ImageError) -> Self {
Error::Image(error)
}
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::Syntax(error, pos) => write!(f, "Syntax error: {} at line {}:{}", error, pos.line, pos.col_start),
Error::Eof => write!(f, "Unexpected end of file reached"),
Error::Image(error) => write!(f, "Image decode error: {}", error),
Error::Io(error) => write!(f, "I/O error: {}", error),
}
}
}
impl std::error::Error for Error {}
impl<'a> From<&'a str> for SelectorWidget {
fn from(s: &'a str) -> Self {
if s == "*" {
SelectorWidget::Any
} else {
SelectorWidget::Some(s.into())
}
}
}
impl<'a> From<&'a str> for StyleState<String> {
fn from(s: &'a str) -> Self {
match s {
"hover" => StyleState::Hover,
"pressed" => StyleState::Pressed,
"checked" => StyleState::Checked,
"disabled" => StyleState::Disabled,
"open" => StyleState::Open,
"closed" => StyleState::Closed,
"drag" => StyleState::Drag,
"drop" => StyleState::Drop,
"drop-denied" => StyleState::DropDenied,
other => StyleState::Custom(other.into()),
}
}
}