use std::{borrow::Cow, collections::BTreeMap};
pub type ScopeId = Cow<'static, str>;
pub type Style = Cow<'static, str>;
pub type Order = u32;
#[derive(Debug, Default, Clone)]
pub struct LayeredCss {
pub styles: BTreeMap<ScopeId, StyleTree>,
}
impl LayeredCss {
pub fn new() -> Self {
Self {
styles: BTreeMap::new(),
}
}
pub fn root_scope_exist(&self, root_scope_id: impl Into<ScopeId>) -> bool {
self.styles.contains_key(&root_scope_id.into())
}
pub fn layer_exist_in_root_scope(
&self,
root_scope_id: impl Into<ScopeId>,
layer_scope_id: impl Into<ScopeId>,
) -> bool {
let root_scope_id = root_scope_id.into();
let layer_scope_id = layer_scope_id.into();
self.styles
.get(&root_scope_id)
.map(|style_tree| style_tree.uniq_layers.contains_key(&layer_scope_id))
.unwrap_or(false)
}
pub fn add_style_from_parts(
&mut self,
root_scope_id: impl Into<ScopeId>,
order_in_chain: Order,
layer_scope_id: impl Into<ScopeId>,
style: impl Into<Style>,
) -> bool {
let root_scope_id = root_scope_id.into();
let layer_scope_id = layer_scope_id.into();
let style = style.into();
let layers_of_chain = &mut self
.styles
.entry(root_scope_id)
.or_insert_with(Default::default)
.uniq_layers;
if let Some((order, _)) = layers_of_chain.get(&layer_scope_id) {
debug_assert_eq!(*order, order_in_chain);
return false;
}
if style.is_empty() {
return false;
}
let res = layers_of_chain
.insert(layer_scope_id, (order_in_chain, style))
.is_none();
debug_assert!(res);
true
}
#[cfg(feature = "rcss_enable")]
pub fn add_style_with_order<T>(&mut self, root_scope_id: ScopeId, order_in_chain: Order) -> bool
where
T: rcss::ScopeCommon,
{
self.add_style_from_parts(root_scope_id, order_in_chain, T::SCOPE_ID, T::STYLE)
}
#[cfg(feature = "rcss_enable")]
pub fn add_style_chain<C>(&mut self, ts_chain: &C) -> bool
where
C: rcss::extend::in_chain_ops::ScopeChainOps,
{
let root_scope_id = ts_chain.root_scope_id();
let mut chain = vec![];
ts_chain.for_each(|scope_id, style| {
chain.push((scope_id, style));
});
debug_assert_eq!(
chain.last().expect("Chain is empty").0,
ts_chain.root_scope_id()
);
let mut any_update = false;
for (order, (scope_id, style)) in chain.into_iter().rev().enumerate() {
any_update |= self.add_style_from_parts(root_scope_id, order as Order, scope_id, style);
}
any_update
}
}
#[derive(Default, Clone, Debug)]
pub struct StyleTree {
pub uniq_layers: BTreeMap<ScopeId, (Order, Style)>,
}
impl StyleTree {
pub fn render(
&self,
always_output_layer: bool,
root_scope_id: impl Into<ScopeId>,
) -> Option<String> {
let root_scope_id = root_scope_id.into();
let root_scope = self.uniq_layers.get(&root_scope_id)?.clone();
debug_assert_eq!(root_scope.0, 0, "Root layer must have order 0");
let mut rest = self
.uniq_layers
.iter()
.filter(|(layer, _)| **layer != root_scope_id)
.peekable();
if !always_output_layer && rest.peek().is_none() {
return Some(root_scope.1.to_string());
}
let mut ordered_layers: Vec<_> = rest
.map(|(layer, (order, style))| (order, layer, style))
.collect();
ordered_layers.sort_by_key(|(order, _, _)| *order);
ordered_layers.insert(0, (&root_scope.0, &root_scope_id, &root_scope.1));
let mut first: bool = true;
let mut header = String::from("@layer ");
for (_, scope_id, _) in &ordered_layers {
if !first {
header.push(',');
}
header.push_str(scope_id);
first = false;
}
header.push(';');
let mut style = header;
for (_, scope_id, layer_impl) in ordered_layers {
style.push_str("@layer ");
style.push_str(scope_id);
style.push_str("{");
style.push_str(layer_impl);
style.push_str("}");
}
Some(style)
}
}
#[cfg(test)]
#[cfg(feature = "rcss_enable")]
mod test {
use rcss::ScopeCommon;
use super::*;
#[test]
fn check_raw_api() {
let mut chain = LayeredCss::new();
assert!(chain.add_style_from_parts("root", 0, "root", "style1{color:red}"));
assert!(chain.add_style_from_parts("root", 1, "layer2", "style2{color:blue}"));
assert!(chain.add_style_from_parts("root", 2, "layer3", "style3{color:green}"));
assert!(!chain.add_style_from_parts("root", 2, "layer3", "style3{color:green}"));
let representation = chain
.styles
.get("root")
.unwrap()
.render(false, "root")
.unwrap();
let mut expectation = String::from("@layer root,layer2,layer3;");
expectation.push_str("@layer root{style1{color:red}}");
expectation.push_str("@layer layer2{style2{color:blue}}");
expectation.push_str("@layer layer3{style3{color:green}}");
assert_eq!(representation, expectation);
}
#[test]
fn test_chain() {
rcss::css! {
@rcss(pub struct Style1);
.foo{color:red}
}
rcss::css! {
@rcss(pub struct Style2);
@rcss(extend Style1);
.foo{color:orange}
}
rcss::css! {
@rcss(pub struct Style3);
@rcss(extend Style2);
.foo{color:green}
}
let mut chain = LayeredCss::new();
assert!(chain.add_style_chain(&Style3::new()));
let root_id = Style1::SCOPE_ID;
let layer2_id = Style2::SCOPE_ID;
let layer3_id = Style3::SCOPE_ID;
let root_foo = Style1::new().foo;
let layer2_foo = Style2::new().foo.split_whitespace().last().unwrap();
let layer3_foo = Style3::new().foo.split_whitespace().last().unwrap();
let mut expectation = format!("@layer {root_id},{layer2_id},{layer3_id};");
expectation.push_str(&format!("@layer {root_id}{{.{root_foo}{{color:red}}}}"));
expectation.push_str(&format!(
"@layer {layer2_id}{{.{layer2_foo}{{color:orange}}}}"
));
expectation.push_str(&format!(
"@layer {layer3_id}{{.{layer3_foo}{{color:green}}}}"
));
assert_eq!(
chain
.styles
.get(root_id)
.unwrap()
.render(false, root_id)
.unwrap(),
expectation
);
}
}