use std::sync::{Arc, Mutex};
use crate::cssom::CSSStyleDeclaration;
#[allow(non_camel_case_types)]
#[derive(Debug, Clone, PartialEq)]
pub enum CSSRuleType {
STYLE_RULE,
MEDIA_RULE,
KEYFRAMES_RULE,
IMPORT_RULE,
FONT_FACE_RULE,
SUPPORTS_RULE,
}
#[derive(Debug, Clone)]
pub struct CSSRuleOM {
pub rule_type: CSSRuleType,
css_text: String,
parent_stylesheet: Option<String>,
}
impl CSSRuleOM {
pub fn new(rule_type: CSSRuleType, css_text: &str) -> Self {
Self {
rule_type,
css_text: css_text.to_string(),
parent_stylesheet: None,
}
}
pub fn get_css_text(&self) -> &str {
&self.css_text
}
pub fn set_css_text(&mut self, text: &str) {
self.css_text = text.to_string();
}
pub fn parent_stylesheet(&self) -> Option<&str> {
self.parent_stylesheet.as_deref()
}
}
#[derive(Debug, Clone)]
pub struct CSSStyleRule {
base: CSSRuleOM,
selector_text: String,
style: Arc<Mutex<CSSStyleDeclaration>>,
}
impl CSSStyleRule {
pub fn new(selector: &str) -> Self {
let css_text = format!("{} {{ }}", selector);
Self {
base: CSSRuleOM::new(CSSRuleType::STYLE_RULE, &css_text),
selector_text: selector.to_string(),
style: Arc::new(Mutex::new(CSSStyleDeclaration::new())),
}
}
pub fn from_internal(rule: &crate::css::CSSRule) -> Self {
let selector_text = rule.selector.text.clone();
let style = Arc::new(Mutex::new(
CSSStyleDeclaration::from_declarations(&rule.declarations)
));
let css_text = format!("{} {{ {} }}", selector_text, style.lock().unwrap().get_css_text());
Self {
base: CSSRuleOM::new(CSSRuleType::STYLE_RULE, &css_text),
selector_text,
style,
}
}
pub fn selector_text(&self) -> &str {
&self.selector_text
}
pub fn set_selector_text(&mut self, text: &str) {
self.selector_text = text.to_string();
self.update_css_text();
}
pub fn style(&self) -> Arc<Mutex<CSSStyleDeclaration>> {
Arc::clone(&self.style)
}
fn update_css_text(&mut self) {
let style_text = self.style.lock().unwrap().get_css_text();
self.base.set_css_text(&format!(
"{} {{ {} }}",
self.selector_text, style_text
));
}
pub fn to_internal(&self) -> crate::css::CSSRule {
let selector = crate::css::Selector::new(&self.selector_text);
let declarations = self.style.lock().unwrap().to_declarations();
crate::css::CSSRule::new(selector, declarations)
}
}
#[derive(Clone)]
pub struct CSSMediaRule {
base: CSSRuleOM,
condition_text: String,
css_rules: Vec<Arc<Mutex<dyn CSSRuleTrait>>>,
}
impl std::fmt::Debug for CSSMediaRule {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CSSMediaRule")
.field("base", &self.base)
.field("condition_text", &self.condition_text)
.field("css_rules_count", &self.css_rules.len())
.finish()
}
}
impl CSSMediaRule {
pub fn new(condition: &str) -> Self {
let css_text = format!("@media {} {{ }}", condition);
Self {
base: CSSRuleOM::new(CSSRuleType::MEDIA_RULE, &css_text),
condition_text: condition.to_string(),
css_rules: Vec::new(),
}
}
pub fn condition_text(&self) -> &str {
&self.condition_text
}
pub fn set_condition_text(&mut self, text: &str) {
self.condition_text = text.to_string();
self.update_css_text();
}
pub fn insert_rule(&mut self, rule: Arc<Mutex<dyn CSSRuleTrait>>, index: usize) {
if index > self.css_rules.len() {
self.css_rules.push(rule);
} else {
self.css_rules.insert(index, rule);
}
self.update_css_text();
}
pub fn delete_rule(&mut self, index: usize) -> bool {
if index < self.css_rules.len() {
self.css_rules.remove(index);
self.update_css_text();
true
} else {
false
}
}
pub fn length(&self) -> usize {
self.css_rules.len()
}
pub fn css_rule(&self, index: usize) -> Option<Arc<Mutex<dyn CSSRuleTrait>>> {
if index < self.css_rules.len() {
Some(Arc::clone(&self.css_rules[index]))
} else {
None
}
}
fn update_css_text(&mut self) {
let rules_text = self.css_rules
.iter()
.map(|r| r.lock().unwrap().get_css_text().to_string())
.collect::<Vec<_>>()
.join("\n");
self.base.set_css_text(&format!(
"@media {} {{\n{}\n}}",
self.condition_text, rules_text
));
}
}
pub trait CSSRuleTrait {
fn rule_type(&self) -> CSSRuleType;
fn get_css_text(&self) -> &str;
fn set_css_text(&mut self, text: &str);
}
impl CSSRuleTrait for CSSStyleRule {
fn rule_type(&self) -> CSSRuleType {
CSSRuleType::STYLE_RULE
}
fn get_css_text(&self) -> &str {
self.base.get_css_text()
}
fn set_css_text(&mut self, text: &str) {
self.base.set_css_text(text);
}
}
impl CSSRuleTrait for CSSMediaRule {
fn rule_type(&self) -> CSSRuleType {
CSSRuleType::MEDIA_RULE
}
fn get_css_text(&self) -> &str {
self.base.get_css_text()
}
fn set_css_text(&mut self, text: &str) {
self.base.set_css_text(text);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_css_style_rule() {
let rule = CSSStyleRule::new(".container");
assert_eq!(rule.selector_text(), ".container");
assert_eq!(rule.base.rule_type, CSSRuleType::STYLE_RULE);
}
#[test]
fn test_css_style_rule_set_property() {
let rule = CSSStyleRule::new(".container");
rule.style().lock().unwrap().set_property("color", "red", "");
assert_eq!(rule.style().lock().unwrap().get_property_value("color"), "red");
}
#[test]
fn test_css_style_rule_to_internal() {
let mut rule = CSSStyleRule::new(".container");
rule.style().lock().unwrap().set_property("color", "red", "");
let internal = rule.to_internal();
assert_eq!(internal.selector.text, ".container");
assert_eq!(internal.declarations.len(), 1);
}
#[test]
fn test_css_media_rule() {
let media_rule = CSSMediaRule::new("screen and (max-width: 600px)");
assert_eq!(media_rule.condition_text(), "screen and (max-width: 600px)");
assert_eq!(media_rule.base.rule_type, CSSRuleType::MEDIA_RULE);
}
#[test]
fn test_css_media_rule_insert_delete() {
let mut media_rule = CSSMediaRule::new("screen");
let style_rule = Arc::new(Mutex::new(CSSStyleRule::new(".class")));
media_rule.insert_rule(style_rule.clone(), 0);
assert_eq!(media_rule.length(), 1);
let deleted = media_rule.delete_rule(0);
assert!(deleted);
assert_eq!(media_rule.length(), 0);
}
}