use std::collections::HashMap;
use std::iter::Peekable;
use crate::bitset::BitSet;
use crate::cache::Cache;
use crate::draw::{Background, Color, Image, Patch};
use crate::layout::{Align, Direction, Rectangle, Size};
use crate::loader::Loader;
use crate::text::{Font, TextWrap};
mod parse;
mod tokenize;
pub(crate) mod tree;
use parse::*;
use std::sync::{Arc, Mutex};
use tokenize::*;
#[derive(Debug)]
pub enum Error {
Syntax(String, TokenPos),
Eof,
Image(image::ImageError),
Io(Box<dyn std::error::Error + Send>),
}
pub struct Style {
resolved: Mutex<HashMap<BitSet, Arc<Stylesheet>>>,
default: Stylesheet,
rule_tree: tree::RuleTree,
}
#[derive(Clone)]
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>,
}
pub enum Declaration {
Background(Background),
Font(Font),
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(Clone, PartialEq)]
pub enum Selector {
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(Clone, PartialEq, Eq)]
pub enum SelectorWidget {
Any,
Some(String),
}
#[derive(Clone)]
pub enum StyleState<S: AsRef<str>> {
Hover,
Pressed,
Checked,
Disabled,
Open,
Closed,
Drag,
Drop,
Custom(S),
}
impl Style {
pub fn new(cache: Arc<Mutex<Cache>>) -> Self {
Style {
resolved: Mutex::new(HashMap::new()),
rule_tree: tree::RuleTree::default(),
default: Stylesheet {
background: Background::None,
font: cache
.lock()
.unwrap()
.load_font(include_bytes!("default_font.ttf").to_vec()),
color: Color::white(),
padding: Rectangle::zero(),
margin: Rectangle::zero(),
text_size: 16.0,
text_wrap: TextWrap::NoWrap,
width: Size::Shrink,
height: Size::Shrink,
direction: Direction::LeftToRight,
align_horizontal: Align::Begin,
align_vertical: Align::Begin,
flags: Vec::new(),
},
}
}
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 async fn load<L: Loader, U: AsRef<str>>(
loader: Arc<L>,
url: U,
cache: Arc<Mutex<Cache>>,
) -> Result<Self, Error> {
let text = String::from_utf8(loader.load(url).await.map_err(|e| Error::Io(Box::new(e)))?).unwrap();
parse(tokenize(text)?, loader, cache).await
}
}
impl Stylesheet {
pub fn contains(&self, flag: &str) -> bool {
self.flags.binary_search_by_key(&flag, |s| s.as_str()).is_ok()
}
}
impl Declaration {
pub fn apply(&self, stylesheet: &mut Stylesheet) {
match self {
&Declaration::Background(ref x) => stylesheet.background = x.clone(),
&Declaration::Font(ref x) => stylesheet.font = x.clone(),
&Declaration::Color(ref x) => stylesheet.color = x.clone(),
&Declaration::Padding(ref x) => stylesheet.padding = x.clone(),
&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(ref x) => stylesheet.margin = x.clone(),
&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.clone(),
&Declaration::TextWrap(ref x) => stylesheet.text_wrap = x.clone(),
&Declaration::Width(ref x) => stylesheet.width = x.clone(),
&Declaration::Height(ref x) => stylesheet.height = x.clone(),
&Declaration::LayoutDirection(x) => stylesheet.direction = x,
&Declaration::AlignHorizontal(ref x) => stylesheet.align_horizontal = x.clone(),
&Declaration::AlignVertical(ref x) => stylesheet.align_vertical = x.clone(),
&Declaration::AddFlag(ref x) => match stylesheet.flags.binary_search(x) {
Err(insert_at) => {
stylesheet.flags.insert(insert_at, x.clone());
}
Ok(_) => (),
},
&Declaration::RemoveFlag(ref x) => match stylesheet.flags.binary_search(x) {
Ok(exists) => {
stylesheet.flags.remove(exists);
}
Err(_) => (),
},
}
}
}
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().find(|&state| state.eq(sel_state)).is_some()),
&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::Open, StyleState::Open) => true,
(StyleState::Closed, StyleState::Closed) => 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 {}