summaryrefslogtreecommitdiff
path: root/src/eval/styles.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/eval/styles.rs')
-rw-r--r--src/eval/styles.rs398
1 files changed, 244 insertions, 154 deletions
diff --git a/src/eval/styles.rs b/src/eval/styles.rs
index 1c4b17ae..f8413feb 100644
--- a/src/eval/styles.rs
+++ b/src/eval/styles.rs
@@ -10,23 +10,21 @@ use std::rc::Rc;
/// A map of style properties.
#[derive(Default, Clone, Hash)]
-pub struct Styles {
- map: Vec<(StyleId, Entry)>,
-}
+pub struct StyleMap(Vec<Entry>);
-impl Styles {
+impl StyleMap {
/// Create a new, empty style map.
pub fn new() -> Self {
- Self { map: vec![] }
+ Self(vec![])
}
/// Whether this map contains no styles.
pub fn is_empty(&self) -> bool {
- self.map.is_empty()
+ self.0.is_empty()
}
- /// Create a style map with a single property-value pair.
- pub fn one<P: Property>(key: P, value: P::Value) -> Self {
+ /// Create a style map from a single property-value pair.
+ pub fn with<P: Property>(key: P, value: P::Value) -> Self {
let mut styles = Self::new();
styles.set(key, value);
styles
@@ -34,17 +32,16 @@ impl Styles {
/// Set the value for a style property.
pub fn set<P: Property>(&mut self, key: P, value: P::Value) {
- let id = StyleId::of::<P>();
- for pair in &mut self.map {
- if pair.0 == id {
- let prev = pair.1.downcast::<P::Value>().unwrap();
- let folded = P::combine(value, prev.clone());
- pair.1 = Entry::new(key, folded);
+ for entry in &mut self.0 {
+ if entry.is::<P>() {
+ let prev = entry.downcast::<P>().unwrap();
+ let folded = P::fold(value, prev.clone());
+ *entry = Entry::new(key, folded);
return;
}
}
- self.map.push((id, Entry::new(key, value)));
+ self.0.push(Entry::new(key, value));
}
/// Set a value for a style property if it is `Some(_)`.
@@ -54,81 +51,63 @@ impl Styles {
}
}
- /// Toggle a boolean style property.
+ /// Toggle a boolean style property, removing it if it exists and inserting
+ /// it with `true` if it doesn't.
pub fn toggle<P: Property<Value = bool>>(&mut self, key: P) {
- let id = StyleId::of::<P>();
- for (i, pair) in self.map.iter_mut().enumerate() {
- if pair.0 == id {
- self.map.swap_remove(i);
+ for (i, entry) in self.0.iter_mut().enumerate() {
+ if entry.is::<P>() {
+ self.0.swap_remove(i);
return;
}
}
- self.map.push((id, Entry::new(key, true)));
- }
-
- /// Get the value of a copyable style property.
- ///
- /// Returns the property's default value if the map does not contain an
- /// entry for it.
- pub fn get<P: Property>(&self, key: P) -> P::Value
- where
- P::Value: Copy,
- {
- self.get_direct(key)
- .map(|&v| P::combine(v, P::default()))
- .unwrap_or_else(P::default)
- }
-
- /// Get a reference to a style property.
- ///
- /// 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_direct(key).unwrap_or_else(|| P::default_ref())
+ self.0.push(Entry::new(key, true));
}
- /// Get a reference to a style directly in this map (no default value).
- fn get_direct<P: Property>(&self, _: P) -> Option<&P::Value> {
- self.map
- .iter()
- .find(|pair| pair.0 == StyleId::of::<P>())
- .and_then(|pair| pair.1.downcast())
- }
-
- /// Create new styles combining `self` with `outer`.
+ /// Make `self` the first link of the style chain `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
+ /// The resulting style chain contains styles from `self` as well as
+ /// `outer`. The ones from `self` take precedence over the ones from
+ /// `outer`. For folded properties `self` contributes the inner value.
+ pub fn chain<'a>(&'a self, outer: &'a StyleChain<'a>) -> StyleChain<'a> {
+ if self.is_empty() {
+ // No need to chain an empty map.
+ *outer
+ } else {
+ StyleChain { inner: self, outer: Some(outer) }
+ }
}
- /// Apply styles from `outer` in-place.
+ /// Apply styles from `outer` in-place. The resulting style map is
+ /// equivalent to the style chain created by
+ /// `self.chain(StyleChain::new(outer))`.
///
- /// Properties from `self` take precedence over the ones from `outer`.
+ /// This is useful in the evaluation phase while building nodes and their
+ /// style maps, whereas `chain` would be used during layouting to combine
+ /// immutable style maps from different levels of the hierarchy.
pub fn apply(&mut self, outer: &Self) {
- 'outer: for pair in &outer.map {
- for (id, entry) in &mut self.map {
- if pair.0 == *id {
- entry.apply(&pair.1);
+ 'outer: for outer in &outer.0 {
+ for inner in &mut self.0 {
+ if inner.style_id() == outer.style_id() {
+ inner.fold(outer);
continue 'outer;
}
}
- self.map.push(pair.clone());
+ self.0.push(outer.clone());
}
}
- /// Keep only those styles that are not also in `other`.
+ /// Subtract `other` from `self` in-place, keeping only styles that are in
+ /// `self` but not in `other`.
pub fn erase(&mut self, other: &Self) {
- self.map.retain(|a| other.map.iter().all(|b| a != b));
+ self.0.retain(|x| !other.0.contains(x));
}
- /// Keep only those styles that are also in `other`.
+ /// Intersect `self` with `other` in-place, keeping only styles that are
+ /// both in `self` and `other`.
pub fn intersect(&mut self, other: &Self) {
- self.map.retain(|a| other.map.iter().any(|b| a == b));
+ self.0.retain(|x| other.0.contains(x));
}
/// Whether two style maps are equal when filtered down to the given
@@ -137,113 +116,136 @@ impl Styles {
where
F: Fn(StyleId) -> bool,
{
- // TODO(style): Filtered length + one direction equal should suffice.
- 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))
+ let f = |entry: &&Entry| filter(entry.style_id());
+ self.0.iter().filter(f).count() == other.0.iter().filter(f).count()
+ && self.0.iter().filter(f).all(|x| other.0.contains(x))
}
}
-impl Debug for Styles {
+impl Debug for StyleMap {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- if f.alternate() {
- for pair in &self.map {
- writeln!(f, "{:#?}", pair.1)?;
- }
- Ok(())
- } else {
- f.write_str("Styles ")?;
- f.debug_set().entries(self.map.iter().map(|pair| &pair.1)).finish()
+ for entry in &self.0 {
+ writeln!(f, "{:#?}", entry)?;
}
+ Ok(())
}
}
-impl PartialEq for Styles {
+impl PartialEq for StyleMap {
fn eq(&self, other: &Self) -> bool {
self.compatible(other, |_| true)
}
}
-/// An entry for a single style property.
-#[derive(Clone)]
-pub(crate) struct Entry(Rc<dyn Bounds>);
-
-impl Entry {
- fn new<P: Property>(key: P, value: P::Value) -> Self {
- Self(Rc::new((key, value)))
- }
-
- fn downcast<T: 'static>(&self) -> Option<&T> {
- self.0.as_any().downcast_ref()
- }
-
- fn apply(&mut self, outer: &Self) {
- *self = self.0.combine(outer);
- }
-}
-
-impl Debug for Entry {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- self.0.dyn_fmt(f)
- }
-}
-
-impl PartialEq for Entry {
- fn eq(&self, other: &Self) -> bool {
- self.0.dyn_eq(other)
- }
+/// A chain of style maps, similar to a linked list.
+///
+/// A style chain allows to conceptually merge (and fold) properties from
+/// multiple style maps in a node hierarchy in a non-allocating way. Rather than
+/// eagerly merging the maps, each access walks the hierarchy from the innermost
+/// to the outermost map, trying to find a match and then folding it with
+/// matches further up the chain.
+#[derive(Clone, Copy, Hash)]
+pub struct StyleChain<'a> {
+ inner: &'a StyleMap,
+ outer: Option<&'a Self>,
}
-impl Hash for Entry {
- fn hash<H: Hasher>(&self, state: &mut H) {
- state.write_u64(self.0.hash64());
+impl<'a> StyleChain<'a> {
+ /// Start a new style chain with a root map.
+ pub fn new(map: &'a StyleMap) -> Self {
+ Self { inner: map, outer: None }
}
-}
-
-trait Bounds: 'static {
- fn as_any(&self) -> &dyn Any;
- fn dyn_fmt(&self, f: &mut Formatter) -> fmt::Result;
- fn dyn_eq(&self, other: &Entry) -> bool;
- fn hash64(&self) -> u64;
- fn combine(&self, outer: &Entry) -> Entry;
-}
-// `P` is always zero-sized. We only implement the trait for a pair of key and
-// associated value so that `P` is a constrained type parameter that we can use
-// in `dyn_fmt` to access the property's name. This way, we can effectively
-// store the property's name in its vtable instead of having an actual runtime
-// string somewhere in `Entry`.
-impl<P: Property> Bounds for (P, P::Value) {
- fn as_any(&self) -> &dyn Any {
- &self.1
+ /// Get the (folded) value of a copyable style property.
+ ///
+ /// Returns the property's default value if no map in the chain contains an
+ /// entry for it.
+ pub fn get<P>(self, key: P) -> P::Value
+ where
+ P: Property,
+ P::Value: Copy,
+ {
+ // This exists separately to `get_cloned` for `Copy` types so that
+ // people don't just naively use `get` / `get_cloned` where they should
+ // use `get_ref`.
+ self.get_cloned(key)
}
- fn dyn_fmt(&self, f: &mut Formatter) -> fmt::Result {
- if f.alternate() {
- write!(f, "#[{} = {:?}]", P::NAME, self.1)
+ /// Get a reference to a style property's value.
+ ///
+ /// This is naturally only possible for properties that don't need folding.
+ /// Prefer `get` if possible or resort to `get_cloned` for non-`Copy`
+ /// properties that need folding.
+ ///
+ /// Returns a reference to the property's default value if no map in the
+ /// chain contains an entry for it.
+ pub fn get_ref<P>(self, key: P) -> &'a P::Value
+ where
+ P: Property + Nonfolding,
+ {
+ if let Some(value) = self.get_locally(key) {
+ value
+ } else if let Some(outer) = self.outer {
+ outer.get_ref(key)
} else {
- write!(f, "{}: {:?}", P::NAME, self.1)
+ P::default_ref()
}
}
- fn dyn_eq(&self, other: &Entry) -> bool {
- if let Some(other) = other.downcast::<P::Value>() {
- &self.1 == other
+ /// Get the (folded) value of any style property.
+ ///
+ /// While this works for all properties, you should prefer `get` or
+ /// `get_ref` where possible. This is only needed for non-`Copy` properties
+ /// that need folding.
+ pub fn get_cloned<P>(self, key: P) -> P::Value
+ where
+ P: Property,
+ {
+ if let Some(value) = self.get_locally(key).cloned() {
+ if P::FOLDABLE {
+ if let Some(outer) = self.outer {
+ P::fold(value, outer.get_cloned(key))
+ } else {
+ P::fold(value, P::default())
+ }
+ } else {
+ value
+ }
+ } else if let Some(outer) = self.outer {
+ outer.get_cloned(key)
} else {
- false
+ P::default()
}
}
- fn hash64(&self) -> u64 {
- // No need to hash the TypeId since there's only one
- // valid value type per property.
- fxhash::hash64(&self.1)
+ /// Find a property directly in the most local map.
+ fn get_locally<P: Property>(&self, _: P) -> Option<&'a P::Value> {
+ self.inner
+ .0
+ .iter()
+ .find(|entry| entry.is::<P>())
+ .and_then(|entry| entry.downcast::<P>())
}
+}
- fn combine(&self, outer: &Entry) -> Entry {
- let outer = outer.downcast::<P::Value>().unwrap();
- let combined = P::combine(self.1.clone(), outer.clone());
- Entry::new(self.0, combined)
+impl Debug for StyleChain<'_> {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ self.inner.fmt(f)?;
+ if let Some(outer) = self.outer {
+ outer.fmt(f)?;
+ }
+ Ok(())
+ }
+}
+
+/// 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>())
}
}
@@ -270,23 +272,111 @@ pub trait Property: Copy + 'static {
/// recreated all the time.
fn default_ref() -> &'static Self::Value;
+ /// Whether the property needs folding.
+ const FOLDABLE: bool = false;
+
/// Fold the property with an outer value.
///
- /// For example, this would combine a relative font size with an outer
+ /// For example, this would fold a relative font size with an outer
/// absolute font size.
#[allow(unused_variables)]
- fn combine(inner: Self::Value, outer: Self::Value) -> Self::Value {
+ fn fold(inner: Self::Value, outer: Self::Value) -> Self::Value {
inner
}
}
-/// A unique identifier for a style property.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub struct StyleId(TypeId);
+/// Marker trait that indicates that a property doesn't need folding.
+pub trait Nonfolding {}
-impl StyleId {
- /// The style id of the property.
- pub fn of<P: Property>() -> Self {
- Self(TypeId::of::<P>())
+/// An entry for a single style property.
+#[derive(Clone)]
+struct Entry(Rc<dyn Bounds>);
+
+impl Entry {
+ fn new<P: Property>(key: P, value: P::Value) -> Self {
+ Self(Rc::new((key, value)))
+ }
+
+ fn style_id(&self) -> StyleId {
+ self.0.style_id()
+ }
+
+ fn is<P: Property>(&self) -> bool {
+ self.style_id() == StyleId::of::<P>()
+ }
+
+ fn downcast<P: Property>(&self) -> Option<&P::Value> {
+ self.0.as_any().downcast_ref()
+ }
+
+ fn fold(&mut self, outer: &Self) {
+ *self = self.0.fold(outer);
+ }
+}
+
+impl Debug for Entry {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ self.0.dyn_fmt(f)
+ }
+}
+
+impl PartialEq for Entry {
+ fn eq(&self, other: &Self) -> bool {
+ self.0.dyn_eq(other)
+ }
+}
+
+impl Hash for Entry {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ state.write_u64(self.0.hash64());
+ }
+}
+
+/// This trait is implemented for pairs of zero-sized property keys and their
+/// value types below. Although it is zero-sized, the property `P` must be part
+/// of the implementing type so that we can use it in the methods (it must be a
+/// constrained type parameter).
+trait Bounds: 'static {
+ fn style_id(&self) -> StyleId;
+ fn fold(&self, outer: &Entry) -> Entry;
+ fn as_any(&self) -> &dyn Any;
+ fn dyn_fmt(&self, f: &mut Formatter) -> fmt::Result;
+ fn dyn_eq(&self, other: &Entry) -> bool;
+ fn hash64(&self) -> u64;
+}
+
+impl<P: Property> Bounds for (P, P::Value) {
+ fn style_id(&self) -> StyleId {
+ StyleId::of::<P>()
+ }
+
+ fn fold(&self, outer: &Entry) -> Entry {
+ let outer = outer.downcast::<P>().unwrap();
+ let combined = P::fold(self.1.clone(), outer.clone());
+ Entry::new(self.0, combined)
+ }
+
+ fn as_any(&self) -> &dyn Any {
+ &self.1
+ }
+
+ fn dyn_fmt(&self, f: &mut Formatter) -> fmt::Result {
+ write!(f, "#[{} = {:?}]", P::NAME, self.1)
+ }
+
+ fn dyn_eq(&self, other: &Entry) -> bool {
+ self.style_id() == other.style_id()
+ && if let Some(other) = other.downcast::<P>() {
+ &self.1 == other
+ } else {
+ false
+ }
+ }
+
+ fn hash64(&self) -> u64 {
+ let mut state = fxhash::FxHasher64::default();
+ self.style_id().hash(&mut state);
+ self.1.hash(&mut state);
+ state.finish()
}
}