Skip to content

Commit

Permalink
Merge branch 'css-style-engine' of github.com:jaytaph/gosub-engine in…
Browse files Browse the repository at this point in the history
…to css-style-engine
  • Loading branch information
jaytaph committed Mar 18, 2024
2 parents 556ab43 + d6e7a57 commit 4b051c9
Show file tree
Hide file tree
Showing 4 changed files with 259 additions and 229 deletions.
6 changes: 6 additions & 0 deletions crates/gosub_html5/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,12 @@ pub struct Node {
pub is_registered: bool,
}

impl Node {
pub fn is_root(&self) -> bool {
self.id.is_root()
}
}

impl PartialEq for Node {
fn eq(&self, other: &Node) -> bool {
self.id == other.id
Expand Down
202 changes: 139 additions & 63 deletions crates/gosub_styling/src/css_node_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ use gosub_css3::stylesheet::{
use gosub_html5::node::NodeId;
use gosub_html5::parser::document::{DocumentHandle, TreeIterator};
use gosub_shared::types::Result;
use std::cell::RefCell;
use std::cmp::Ordering;
use std::collections::HashMap;

Expand Down Expand Up @@ -38,25 +37,25 @@ pub fn generate_css_node_tree(document: DocumentHandle) -> Result<CssNodeTree> {

// Selector matched, so we add all declared values to the map
for declaration in rule.declarations().iter() {
let property = declaration.property.clone();
let prop_name = declaration.property.clone();

let declaration = DeclarationProperty {
value: CssValue::String(declaration.value.clone()), // @TODO: parse the value into the correct CSSValue
origin: sheet.origin.clone(),
important: declaration.important,
location: "".into(),
location: sheet.location.clone(),
specificity: selector.specificity(),
};

if let std::collections::hash_map::Entry::Vacant(e) =
css_map_entry.properties.entry(property.clone())
css_map_entry.properties.entry(prop_name.clone())
{
let mut entry = CssProperty::new();
let mut entry = CssProperty::new(prop_name.as_str());
entry.declared.push(declaration);
e.insert(RefCell::new(entry));
e.insert(entry);
} else {
let entry = css_map_entry.properties.get_mut(&property).unwrap();
entry.get_mut().declared.push(declaration);
let entry = css_map_entry.properties.get_mut(&prop_name).unwrap();
entry.declared.push(declaration);
}
}
}
Expand All @@ -66,18 +65,28 @@ pub fn generate_css_node_tree(document: DocumentHandle) -> Result<CssNodeTree> {
css_node_tree.nodes.insert(current_node_id, css_map_entry);
}

for (node_id, props) in css_node_tree.nodes.iter() {
println!("Node: {:?}", node_id);
for (prop, values) in props.properties.iter() {
println!(" {}", prop);
if prop == "color" {
for decl in values.declared.iter() {
println!(" {:?} {:?} {:?} {:?}", decl.origin, decl.location, decl.value, decl.specificity);
}
}
}
}

Ok(css_node_tree)
}

#[allow(dead_code)]
// Matches a complete selector (all parts) against the given node(id)
fn match_selector(document: DocumentHandle, node_id: NodeId, selector: &CssSelector) -> bool {
let mut parts = selector.parts.clone();
parts.reverse();
match_selector_part(document, node_id, &mut parts)
}

#[allow(dead_code)]
/// Returns true when the given node matches the part(s)
fn match_selector_part(
document: DocumentHandle,
Expand All @@ -92,6 +101,10 @@ fn match_selector_part(
return false;
}
let current_node = next_current_node.expect("current_node not found");
if current_node.is_root() {
return false;
}

let part = selector_parts.remove(0);

match part.type_ {
Expand Down Expand Up @@ -303,6 +316,8 @@ impl Ord for DeclarationProperty {
/// all the computed values.
#[derive(Debug, Clone)]
pub struct CssProperty {
/// The name of the property
pub name: String,
/// True when this property needs to be recalculated
pub dirty: bool,
/// List of all declared values for this property
Expand All @@ -318,15 +333,10 @@ pub struct CssProperty {
pub actual: CssValue,
}

impl Default for CssProperty {
fn default() -> Self {
Self::new()
}
}

impl CssProperty {
pub fn new() -> Self {
pub fn new(prop_name: &str) -> Self {
Self {
name: prop_name.to_string(),
dirty: true,
declared: Vec::new(),
cascaded: None,
Expand All @@ -345,38 +355,99 @@ impl CssProperty {
self.dirty = false;
}

pub fn get_value(&mut self) -> &CssValue {
/// Returns the actual value of the property. Will compute the value when needed
pub fn compute_value(&mut self) -> &CssValue {
if self.dirty {
self.dirty = false;
self.calculate_value();
self.dirty = false;
}

&self.actual
}

fn calculate_value(&mut self) {
// Step 1: Find cascaded value
self.cascaded = self.find_cascaded_value();
self.specified = self.find_specified_value();
self.computed = self.find_computed_value();
self.used = self.find_used_value();
self.actual = self.find_actual_value();
}

// Step 2: Find specified value
fn find_cascaded_value(&self) -> Option<CssValue> {
let mut declared = self.declared.clone();

// Step 3: Find computed value (needs viewport?)
declared.sort();
declared.sort_by(|a, b| {
if a.priority() == b.priority() {
return Ordering::Equal;
}

// Step 4: Find used value (??)
a.specificity.cmp(&b.specificity)
});

// Step 5: Find actual value by rounding numbers (when possible)
declared.last().map(|d| d.value.clone())
}

fn find_specified_value(&self) -> CssValue {
match self.declared.iter().max() {
Some(decl) => decl.value.clone(),
None => CssValue::None,
}
}

fn find_computed_value(&self) -> CssValue {
if self.specified != CssValue::None {
return self.specified.clone();
}

if self.is_inheritable() {
while let Some(parent) = self.get_parent() {
if let Some(parent_value) = parent {
return parent_value.find_computed_value();
}
}
}

self.get_initial_value()
}

fn find_used_value(&self) -> CssValue {
self.computed.clone()
}

fn find_actual_value(&self) -> CssValue {
// @TODO: stuff like clipping and such should occur as well
self.actual = match &self.used {
match &self.used {
CssValue::Number(len) => CssValue::Number(len.round()),
CssValue::Percentage(perc) => CssValue::Percentage(perc.round()),
CssValue::Unit(value, unit) => CssValue::Unit(value.round(), unit.clone()),
_ => self.used.clone(),
}
}


// Returns true when the property is inheritable, false otherwise
fn is_inheritable(&self) -> bool {
crate::property_list::PROPERTY_TABLE
.iter()
.find(|entry| entry.name == self.name)
.map(|entry| entry.inheritable)
.unwrap_or(false)
}

// Returns the initial value for the property, if any
fn get_initial_value(&self) -> Option<CssValue> {
crate::property_list::PROPERTY_TABLE
.iter()
.find(|entry| entry.name == self.name)
.map(|entry| entry.initial)
}
}

/// Map of all declared values for a single node
/// Map of all declared values for a single node. Note that these are only the defined properties, not
/// the non-existing properties.
pub struct CssProperties {
properties: HashMap<String, RefCell<CssProperty>>,
properties: HashMap<String, CssProperty>,
}

impl Default for CssProperties {
Expand All @@ -395,7 +466,7 @@ impl CssProperties {

/// Actual CSS value, can be a color, length, percentage, string or unit. Some relative values will be computed
/// from other values (ie: Percent(50) will convert to Length(100) when the parent width is 200)
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq)]
pub enum CssValue {
None,
Color(RgbColor),
Expand All @@ -405,15 +476,27 @@ pub enum CssValue {
Unit(f32, String),
}

impl CssValue {
/// Converts the value to a string
pub fn to_string(&self) -> String {
match self {
CssValue::None => "".into(),
CssValue::Color(col) => format!("#{:02x}{:02x}{:02x}{:02x}", col.r, col.g, col.b, col.a),
CssValue::Number(num) => format!("{}", num),
CssValue::Percentage(p) => format!("{}%", p),
CssValue::String(s) => s.clone(),
CssValue::Unit(val, unit) => format!("{}{}", val, unit),
}
}
}

/// Map of all declared values for all nodes in the document
pub struct CssNodeTree {
nodes: HashMap<NodeId, CssProperties>,
#[allow(dead_code)]
document: DocumentHandle,
}

impl CssNodeTree {
#[allow(dead_code)]
/// Creates a new CssNodeTree for the given document
pub fn new(doc: DocumentHandle) -> Self {
Self {
Expand All @@ -422,34 +505,44 @@ impl CssNodeTree {
}
}

/// Returns a clone of the document handle
pub fn get_document(&self) -> DocumentHandle {
DocumentHandle::clone(&self.document)
}

/// Mark the given node as dirty, so it will be recalculated
pub fn mark_dirty(&mut self, node_id: NodeId) {
for prop in self.nodes[&node_id].properties.values() {
prop.borrow_mut().mark_dirty();
match self.nodes.get_mut(&node_id) {
Some(props) => {
for prop in props.properties.values_mut() {
prop.mark_dirty();
}
}
None => {}
}
}

/// Mark the given node as clean, so it will not be recalculated
pub fn mark_clean(&self, node_id: NodeId) {
for prop in self.nodes[&node_id].properties.values() {
prop.borrow_mut().mark_clean();
pub fn mark_clean(&mut self, node_id: NodeId) {
match self.nodes.get_mut(&node_id) {
Some(props) => {
for prop in props.properties.values_mut() {
prop.mark_clean();
}
}
None => {}
}
}

/// Retrieves the property for the given node, or None when not found
pub fn get_property(&self, node_id: NodeId, prop_name: &str) -> Option<CssProperty> {
let props = self.nodes.get(&node_id);
match props {
Some(p) => {
let entry = p.properties.get(prop_name);
match entry {
Some(e) => {
let prop = e.take().clone();
Some(prop)
}
None => None,
}
}
if props.is_none() {
return None;
}

match props.expect("props").properties.get(prop_name) {
Some(entry) => Some(entry.clone()),
None => None,
}
}
Expand All @@ -458,23 +551,6 @@ impl CssNodeTree {
pub fn get_all_properties(&self, node_id: NodeId) -> Option<&CssProperties> {
self.nodes.get(&node_id)
}

// Returns true when the property is inheritable, false otherwise
pub fn is_inheritable(&self, prop_name: &str) -> bool {
crate::property_list::PROPERTY_TABLE
.iter()
.find(|entry| entry.name == prop_name)
.map(|entry| entry.inheritable)
.unwrap_or(false)
}

// Returns the initial value for the property, if any
pub fn get_initial_value(&self, prop_name: &str) -> Option<&str> {
crate::property_list::PROPERTY_TABLE
.iter()
.find(|entry| entry.name == prop_name)
.map(|entry| entry.initial)
}
}

#[cfg(test)]
Expand Down
Loading

0 comments on commit 4b051c9

Please sign in to comment.