use crate::css_properties::{CssProperty, CssPropertyType};
use std::fmt;
#[derive(Debug, Default, PartialEq, PartialOrd, Clone)]
pub struct Css {
pub stylesheets: Vec<Stylesheet>,
}
impl Css {
pub const fn new(stylesheets: Vec<Stylesheet>) -> Self {
Self { stylesheets }
}
}
#[derive(Debug, Default, PartialEq, PartialOrd, Clone)]
pub struct Stylesheet {
pub rules: Vec<CssRuleBlock>,
}
impl Stylesheet {
pub const fn new(rules: Vec<CssRuleBlock>) -> Self {
Self { rules }
}
}
impl From<Vec<CssRuleBlock>> for Stylesheet {
fn from(rules: Vec<CssRuleBlock>) -> Self {
Self { rules }
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum CssDeclaration {
Static(CssProperty),
Dynamic(DynamicCssProperty),
}
impl CssDeclaration {
pub const fn new_static(prop: CssProperty) -> Self {
CssDeclaration::Static(prop)
}
pub const fn new_dynamic(prop: DynamicCssProperty) -> Self {
CssDeclaration::Dynamic(prop)
}
pub fn get_type(&self) -> CssPropertyType {
use self::CssDeclaration::*;
match self {
Static(s) => s.get_type(),
Dynamic(d) => d.default_value.get_type(),
}
}
pub fn is_inheritable(&self) -> bool {
use self::CssDeclaration::*;
match self {
Static(s) => s.get_type().is_inheritable(),
Dynamic(d) => d.is_inheritable(),
}
}
pub fn can_trigger_relayout(&self) -> bool {
use self::CssDeclaration::*;
match self {
Static(s) => s.get_type().can_trigger_relayout(),
Dynamic(d) => d.can_trigger_relayout(),
}
}
pub fn to_str(&self) -> String {
use self::CssDeclaration::*;
match self {
Static(s) => format!("{:?}", s),
Dynamic(d) => format!("var(--{}, {:?})", d.dynamic_id, d.default_value),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct DynamicCssProperty {
pub dynamic_id: String,
pub default_value: CssProperty,
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum CssPropertyValue<T> {
Auto,
None,
Initial,
Inherit,
Exact(T),
}
impl<T: fmt::Debug> fmt::Debug for CssPropertyValue<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::CssPropertyValue::*;
match self {
Auto => write!(f, "auto"),
None => write!(f, "none"),
Initial => write!(f, "initial"),
Inherit => write!(f, "inherit"),
Exact(e) => write!(f, "{:?}", e),
}
}
}
impl<T: fmt::Display> fmt::Display for CssPropertyValue<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::CssPropertyValue::*;
match self {
Auto => write!(f, "auto"),
None => write!(f, "none"),
Initial => write!(f, "initial"),
Inherit => write!(f, "inherit"),
Exact(e) => write!(f, "{}", e),
}
}
}
impl<T> From<T> for CssPropertyValue<T> {
fn from(c: T) -> Self { CssPropertyValue::Exact(c) }
}
impl<T> CssPropertyValue<T> {
#[inline]
pub fn map_property<F: Fn(T) -> U, U>(self, map_fn: F) -> CssPropertyValue<U> {
match self {
CssPropertyValue::Exact(c) => CssPropertyValue::Exact(map_fn(c)),
CssPropertyValue::Auto => CssPropertyValue::Auto,
CssPropertyValue::None => CssPropertyValue::None,
CssPropertyValue::Initial => CssPropertyValue::Initial,
CssPropertyValue::Inherit => CssPropertyValue::Inherit,
}
}
#[inline]
pub fn get_property(&self) -> Option<&T> {
match self {
CssPropertyValue::Exact(c) => Some(c),
_ => None,
}
}
#[inline]
pub fn get_property_owned(self) -> Option<T> {
match self {
CssPropertyValue::Exact(c) => Some(c),
_ => None,
}
}
#[inline]
pub fn is_auto(&self) -> bool {
match self {
CssPropertyValue::Auto => true,
_ => false,
}
}
#[inline]
pub fn is_none(&self) -> bool {
match self {
CssPropertyValue::None => true,
_ => false,
}
}
#[inline]
pub fn is_initial(&self) -> bool {
match self {
CssPropertyValue::Initial => true,
_ => false,
}
}
#[inline]
pub fn is_inherit(&self) -> bool {
match self {
CssPropertyValue::Inherit => true,
_ => false,
}
}
}
impl<T: Default> CssPropertyValue<T> {
#[inline]
pub fn get_property_or_default(self) -> Option<T> {
match self {
CssPropertyValue::Auto | CssPropertyValue::Initial => Some(T::default()),
CssPropertyValue::Exact(c) => Some(c),
CssPropertyValue::None | CssPropertyValue::Inherit => None,
}
}
}
impl<T: Default> Default for CssPropertyValue<T> {
#[inline]
fn default() -> Self {
CssPropertyValue::Exact(T::default())
}
}
impl DynamicCssProperty {
pub fn is_inheritable(&self) -> bool {
false
}
pub fn can_trigger_relayout(&self) -> bool {
self.default_value.get_type().can_trigger_relayout()
}
}
#[derive(Debug, Clone, PartialOrd, PartialEq)]
pub struct CssRuleBlock {
pub path: CssPath,
pub declarations: Vec<CssDeclaration>,
}
impl CssRuleBlock {
pub const fn new(path: CssPath, declarations: Vec<CssDeclaration>) -> Self {
Self {
path,
declarations,
}
}
}
pub type CssContentGroup<'a> = Vec<&'a CssPathSelector>;
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum NodeTypePath {
Body,
Div,
P,
Img,
Texture,
IFrame,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum NodeTypePathParseError<'a> {
Invalid(&'a str),
}
impl<'a> fmt::Display for NodeTypePathParseError<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &self {
NodeTypePathParseError::Invalid(e) => write!(f, "Invalid node type: {}", e),
}
}
}
const NODE_TYPE_PATH_MAP: [(NodeTypePath, &'static str); 6] = [
(NodeTypePath::Body, "body"),
(NodeTypePath::Div, "div"),
(NodeTypePath::P, "p"),
(NodeTypePath::Img, "img"),
(NodeTypePath::Texture, "texture"),
(NodeTypePath::IFrame, "iframe"),
];
impl NodeTypePath {
pub fn from_str(css_key: &str) -> Result<Self, NodeTypePathParseError> {
NODE_TYPE_PATH_MAP.iter()
.find(|(_, k)| css_key == *k)
.and_then(|(v, _)| Some(*v))
.ok_or(NodeTypePathParseError::Invalid(css_key))
}
}
impl fmt::Display for NodeTypePath {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
let display_string = NODE_TYPE_PATH_MAP.iter()
.find(|(v, _)| *self == *v)
.and_then(|(_, k)| Some(*k))
.unwrap();
write!(f, "{}", display_string)?;
Ok(())
}
}
#[derive(Clone, Hash, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct CssPath {
pub selectors: Vec<CssPathSelector>,
}
impl CssPath {
pub const fn new(selectors: Vec<CssPathSelector>) -> Self {
Self { selectors }
}
}
impl fmt::Display for CssPath {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
for selector in &self.selectors {
write!(f, "{}", selector)?;
}
Ok(())
}
}
impl fmt::Debug for CssPath {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
write!(f, "{}", self)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum CssPathSelector {
Global,
Type(NodeTypePath),
Class(String),
Id(String),
PseudoSelector(CssPathPseudoSelector),
DirectChildren,
Children,
}
impl Default for CssPathSelector {
fn default() -> Self {
CssPathSelector::Global
}
}
impl fmt::Display for CssPathSelector {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::CssPathSelector::*;
match &self {
Global => write!(f, "*"),
Type(n) => write!(f, "{}", n),
Class(c) => write!(f, ".{}", c),
Id(i) => write!(f, "#{}", i),
PseudoSelector(p) => write!(f, ":{}", p),
DirectChildren => write!(f, ">"),
Children => write!(f, " "),
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum CssPathPseudoSelector {
First,
Last,
NthChild(CssNthChildSelector),
Hover,
Active,
Focus,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum CssNthChildSelector {
Number(usize),
Even,
Odd,
Pattern { repeat: usize, offset: usize },
}
impl fmt::Display for CssNthChildSelector {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::CssNthChildSelector::*;
match &self {
Number(u) => write!(f, "{}", u),
Even => write!(f, "even"),
Odd => write!(f, "odd"),
Pattern { repeat, offset } => write!(f, "{}n + {}", repeat, offset),
}
}
}
impl fmt::Display for CssPathPseudoSelector {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::CssPathPseudoSelector::*;
match &self {
First => write!(f, "first"),
Last => write!(f, "last"),
NthChild(u) => write!(f, "nth-child({})", u),
Hover => write!(f, "hover"),
Active => write!(f, "active"),
Focus => write!(f, "focus"),
}
}
}
impl Css {
pub fn empty() -> Self {
Default::default()
}
pub fn append(&mut self, css: Self) {
for stylesheet in css.stylesheets {
self.append_stylesheet(stylesheet);
}
}
pub fn append_stylesheet(&mut self, styles: Stylesheet) {
self.stylesheets.push(styles);
}
pub fn sort_by_specificity(&mut self) {
for stylesheet in &mut self.stylesheets {
stylesheet.sort_by_specificity()
}
}
pub fn rules<'a>(&'a self) -> RuleIterator<'a> {
RuleIterator {
current_stylesheet: 0,
current_rule: 0,
css: self,
}
}
}
pub struct RuleIterator<'a> {
current_stylesheet: usize,
current_rule: usize,
css: &'a Css,
}
impl<'a> Iterator for RuleIterator<'a> {
type Item = &'a CssRuleBlock;
fn next(&mut self) -> Option<&'a CssRuleBlock> {
let current_stylesheet = self.css.stylesheets.get(self.current_stylesheet)?;
match current_stylesheet.rules.get(self.current_rule) {
Some(s) => {
self.current_rule += 1;
Some(s)
},
None => {
self.current_rule = 0;
self.current_stylesheet += 1;
self.next()
}
}
}
}
impl Stylesheet {
pub fn empty() -> Self {
Default::default()
}
pub fn sort_by_specificity(&mut self) {
self.rules.sort_by(|a, b| get_specificity(&a.path).cmp(&get_specificity(&b.path)));
}
}
fn get_specificity(path: &CssPath) -> (usize, usize, usize, usize) {
let id_count = path.selectors.iter().filter(|x| if let CssPathSelector::Id(_) = x { true } else { false }).count();
let class_count = path.selectors.iter().filter(|x| if let CssPathSelector::Class(_) = x { true } else { false }).count();
let div_count = path.selectors.iter().filter(|x| if let CssPathSelector::Type(_) = x { true } else { false }).count();
(id_count, class_count, div_count, path.selectors.len())
}
#[test]
fn test_specificity() {
use self::CssPathSelector::*;
assert_eq!(get_specificity(&CssPath { selectors: vec![Id("hello".into())] }), (1, 0, 0, 1));
assert_eq!(get_specificity(&CssPath { selectors: vec![Class("hello".into())] }), (0, 1, 0, 1));
assert_eq!(get_specificity(&CssPath { selectors: vec![Type(NodeTypePath::Div)] }), (0, 0, 1, 1));
assert_eq!(get_specificity(&CssPath { selectors: vec![Id("hello".into()), Type(NodeTypePath::Div)] }), (1, 0, 1, 2));
}
#[test]
fn test_specificity_sort() {
use self::CssPathSelector::*;
use crate::NodeTypePath::*;
let mut input_style = Stylesheet {
rules: vec![
CssRuleBlock { path: CssPath { selectors: vec![Global] }, declarations: Vec::new() },
CssRuleBlock { path: CssPath { selectors: vec![Global, Type(Div), Class("my_class".into()), Id("my_id".into())] }, declarations: Vec::new() },
CssRuleBlock { path: CssPath { selectors: vec![Global, Type(Div), Id("my_id".into())] }, declarations: Vec::new() },
CssRuleBlock { path: CssPath { selectors: vec![Global, Id("my_id".into())] }, declarations: Vec::new() },
CssRuleBlock { path: CssPath { selectors: vec![Type(Div), Class("my_class".into()), Class("specific".into()), Id("my_id".into())] }, declarations: Vec::new() },
],
};
input_style.sort_by_specificity();
let expected_style = Stylesheet {
rules: vec![
CssRuleBlock { path: CssPath { selectors: vec![Global] }, declarations: Vec::new() },
CssRuleBlock { path: CssPath { selectors: vec![Global, Id("my_id".into())] }, declarations: Vec::new() },
CssRuleBlock { path: CssPath { selectors: vec![Global, Type(Div), Id("my_id".into())] }, declarations: Vec::new() },
CssRuleBlock { path: CssPath { selectors: vec![Global, Type(Div), Class("my_class".into()), Id("my_id".into())] }, declarations: Vec::new() },
CssRuleBlock { path: CssPath { selectors: vec![Type(Div), Class("my_class".into()), Class("specific".into()), Id("my_id".into())] }, declarations: Vec::new() },
],
};
assert_eq!(input_style, expected_style);
}