summaryrefslogtreecommitdiff
path: root/src/eval/styles.rs
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2021-12-09 13:42:52 +0100
committerLaurenz <laurmaedje@gmail.com>2021-12-09 13:42:52 +0100
commitfe21c4d399d291e75165b664762f0aa8bdc4724a (patch)
treea3ec954df6e66f6504f4416b37600cedf95dd7e1 /src/eval/styles.rs
parent40b87d4066fe85cb3fde6cf84cd60d748273ae25 (diff)
Set Rules Episode III: Revenge of the packer
Diffstat (limited to 'src/eval/styles.rs')
-rw-r--r--src/eval/styles.rs212
1 files changed, 190 insertions, 22 deletions
diff --git a/src/eval/styles.rs b/src/eval/styles.rs
index 30822edb..9d204843 100644
--- a/src/eval/styles.rs
+++ b/src/eval/styles.rs
@@ -1,6 +1,6 @@
use std::any::{Any, TypeId};
-use std::collections::HashMap;
use std::fmt::{self, Debug, Formatter};
+use std::hash::{Hash, Hasher};
use std::rc::Rc;
// Possible optimizations:
@@ -9,20 +9,48 @@ use std::rc::Rc;
// - Store small properties inline
/// A map of style properties.
-#[derive(Default, Clone)]
+#[derive(Default, Clone, Hash)]
pub struct Styles {
- map: HashMap<TypeId, Rc<dyn Any>>,
+ pub(crate) map: Vec<(StyleId, Entry)>,
}
impl Styles {
/// Create a new, empty style map.
pub fn new() -> Self {
- Self { map: HashMap::new() }
+ Self { map: vec![] }
+ }
+
+ /// Create a style map with a single property-value pair.
+ pub fn one<P: Property>(key: P, value: P::Value) -> Self
+ where
+ P::Value: Debug + Hash + PartialEq + 'static,
+ {
+ let mut styles = Self::new();
+ styles.set(key, value);
+ styles
+ }
+
+ /// Whether this map contains no styles.
+ pub fn is_empty(&self) -> bool {
+ self.map.is_empty()
}
/// Set the value for a style property.
- pub fn set<P: Property>(&mut self, _: P, value: P::Value) {
- self.map.insert(TypeId::of::<P>(), Rc::new(value));
+ pub fn set<P: Property>(&mut self, key: P, value: P::Value)
+ where
+ P::Value: Debug + Hash + PartialEq + 'static,
+ {
+ let id = StyleId::of::<P>();
+ let entry = Entry::new(key, value);
+
+ for pair in &mut self.map {
+ if pair.0 == id {
+ pair.1 = entry;
+ return;
+ }
+ }
+
+ self.map.push((id, entry));
}
/// Get the value of a copyable style property.
@@ -33,7 +61,7 @@ impl Styles {
where
P::Value: Copy,
{
- self.get_inner(key).copied().unwrap_or_else(P::default)
+ self.get_direct(key).copied().unwrap_or_else(P::default)
}
/// Get a reference to a style property.
@@ -41,30 +69,147 @@ impl Styles {
/// Returns a reference to the property's default value if the map does not
/// contain an entry for it.
pub fn get_ref<P: Property>(&self, key: P) -> &P::Value {
- self.get_inner(key).unwrap_or_else(|| P::default_ref())
+ self.get_direct(key).unwrap_or_else(|| P::default_ref())
}
- /// Get a reference to a style directly in this map.
- fn get_inner<P: Property>(&self, _: P) -> Option<&P::Value> {
+ /// Get a reference to a style directly in this map (no default value).
+ pub fn get_direct<P: Property>(&self, _: P) -> Option<&P::Value> {
self.map
- .get(&TypeId::of::<P>())
- .and_then(|boxed| boxed.downcast_ref())
+ .iter()
+ .find(|pair| pair.0 == StyleId::of::<P>())
+ .and_then(|pair| pair.1.downcast())
+ }
+
+ /// Apply styles from `outer` in-place.
+ ///
+ /// Properties from `self` take precedence over the ones from `outer`.
+ pub fn apply(&mut self, outer: &Self) {
+ for pair in &outer.map {
+ if self.map.iter().all(|&(id, _)| pair.0 != id) {
+ self.map.push(pair.clone());
+ }
+ }
+ }
+
+ /// Create new styles combining `self` with `outer`.
+ ///
+ /// Properties from `self` take precedence over the ones from `outer`.
+ pub fn chain(&self, outer: &Self) -> Self {
+ let mut styles = self.clone();
+ styles.apply(outer);
+ styles
+ }
+
+ /// Keep only those styles that are also in `other`.
+ pub fn intersect(&mut self, other: &Self) {
+ self.map.retain(|a| other.map.iter().any(|b| a == b));
+ }
+
+ /// Whether two style maps are equal when filtered down to the given
+ /// properties.
+ pub fn compatible<F>(&self, other: &Self, filter: F) -> bool
+ where
+ F: Fn(StyleId) -> bool,
+ {
+ let f = |e: &&(StyleId, Entry)| filter(e.0);
+ self.map.iter().filter(f).all(|pair| other.map.contains(pair))
+ && other.map.iter().filter(f).all(|pair| self.map.contains(pair))
}
}
impl Debug for Styles {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- // TODO(set): Better debug printing possible?
- f.pad("Styles(..)")
+ f.write_str("Styles ")?;
+ f.debug_set().entries(self.map.iter().map(|pair| &pair.1)).finish()
+ }
+}
+
+/// An entry for a single style property.
+#[derive(Clone)]
+pub(crate) struct Entry {
+ #[cfg(debug_assertions)]
+ name: &'static str,
+ value: Rc<dyn Bounds>,
+}
+
+impl Entry {
+ fn new<P: Property>(_: P, value: P::Value) -> Self
+ where
+ P::Value: Debug + Hash + PartialEq + 'static,
+ {
+ Self {
+ #[cfg(debug_assertions)]
+ name: P::NAME,
+ value: Rc::new(value),
+ }
+ }
+
+ fn downcast<T: 'static>(&self) -> Option<&T> {
+ self.value.as_any().downcast_ref()
+ }
+}
+
+impl Debug for Entry {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ #[cfg(debug_assertions)]
+ write!(f, "{}: ", self.name)?;
+ write!(f, "{:?}", &self.value)
+ }
+}
+
+impl PartialEq for Entry {
+ fn eq(&self, other: &Self) -> bool {
+ self.value.dyn_eq(other)
+ }
+}
+
+impl Hash for Entry {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ state.write_u64(self.value.hash64());
+ }
+}
+
+trait Bounds: Debug + 'static {
+ fn as_any(&self) -> &dyn Any;
+ fn dyn_eq(&self, other: &Entry) -> bool;
+ fn hash64(&self) -> u64;
+}
+
+impl<T> Bounds for T
+where
+ T: Debug + Hash + PartialEq + 'static,
+{
+ fn as_any(&self) -> &dyn Any {
+ self
+ }
+
+ fn dyn_eq(&self, other: &Entry) -> bool {
+ if let Some(other) = other.downcast::<Self>() {
+ self == other
+ } else {
+ false
+ }
+ }
+
+ fn hash64(&self) -> u64 {
+ // No need to hash the TypeId since there's only one
+ // valid value type per property.
+ fxhash::hash64(self)
}
}
-/// Stylistic property keys.
+/// Style property keys.
+///
+/// This trait is not intended to be implemented manually, but rather through
+/// the `properties!` macro.
pub trait Property: 'static {
/// The type of this property, for example, this could be
/// [`Length`](crate::geom::Length) for a `WIDTH` property.
type Value;
+ /// The name of the property, used for debug printing.
+ const NAME: &'static str;
+
/// The default value of the property.
fn default() -> Self::Value;
@@ -76,14 +221,18 @@ pub trait Property: 'static {
fn default_ref() -> &'static Self::Value;
}
-macro_rules! set {
- ($ctx:expr, $target:expr => $source:expr) => {
- if let Some(v) = $source {
- $ctx.styles.set($target, v);
- }
- };
+/// A unique identifier for a style property.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub struct StyleId(TypeId);
+
+impl StyleId {
+ /// The style id of the property.
+ pub fn of<P: Property>() -> Self {
+ Self(TypeId::of::<P>())
+ }
}
+/// Generate the property keys for a node.
macro_rules! properties {
($node:ty, $(
$(#[$attr:meta])*
@@ -92,10 +241,10 @@ macro_rules! properties {
// TODO(set): Fix possible name clash.
mod properties {
use std::marker::PhantomData;
+ use $crate::eval::{Property, StyleId};
use super::*;
$(#[allow(non_snake_case)] mod $name {
- use $crate::eval::Property;
use once_cell::sync::Lazy;
use super::*;
@@ -104,6 +253,10 @@ macro_rules! properties {
impl Property for Key<$type> {
type Value = $type;
+ const NAME: &'static str = concat!(
+ stringify!($node), "::", stringify!($name)
+ );
+
fn default() -> Self::Value {
$default
}
@@ -116,9 +269,24 @@ macro_rules! properties {
})*
impl $node {
+ /// Check whether the property with the given type id belongs to
+ /// `Self`.
+ pub fn has_property(id: StyleId) -> bool {
+ false || $(id == StyleId::of::<$name::Key<$type>>())||*
+ }
+
$($(#[$attr])* pub const $name: $name::Key<$type>
= $name::Key(PhantomData);)*
}
}
};
}
+
+/// Set a style property to a value if the value is `Some`.
+macro_rules! set {
+ ($ctx:expr, $target:expr => $value:expr) => {
+ if let Some(v) = $value {
+ $ctx.styles.set($target, v);
+ }
+ };
+}