summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-01-07 13:11:46 +0100
committerLaurenz <laurmaedje@gmail.com>2022-01-07 13:26:51 +0100
commit0b624390906e911bde325b487b2710b67c8205c8 (patch)
tree43fbfc28082add3dcb1398ae0b6f71202fe51efe /src
parentaf014cfe5eea4233d8034c79c1a5f898c972396c (diff)
Scoped styles
Diffstat (limited to 'src')
-rw-r--r--src/eval/class.rs2
-rw-r--r--src/eval/node.rs6
-rw-r--r--src/eval/styles.rs286
-rw-r--r--src/layout/mod.rs2
4 files changed, 188 insertions, 108 deletions
diff --git a/src/eval/class.rs b/src/eval/class.rs
index b7b4d012..307bebcc 100644
--- a/src/eval/class.rs
+++ b/src/eval/class.rs
@@ -66,7 +66,7 @@ impl Class {
let mut styles = StyleMap::new();
self.set(args, &mut styles)?;
let node = (self.construct)(ctx, args)?;
- Ok(node.styled_with_map(styles))
+ Ok(node.styled_with_map(styles.scoped()))
}
/// Execute the class's set rule.
diff --git a/src/eval/node.rs b/src/eval/node.rs
index 37f230ee..ecbee8ee 100644
--- a/src/eval/node.rs
+++ b/src/eval/node.rs
@@ -302,7 +302,7 @@ impl Packer {
if let Some(Styled { item: ParChild::Text(left), map }) =
self.par.children.last_mut()
{
- if child.map.compatible(map, TextNode::has_property) {
+ if child.map.compatible::<TextNode>(map) {
left.0.push_str(&right.0);
return;
}
@@ -380,7 +380,7 @@ impl Packer {
return;
}
- if !self.par.styles.compatible(styles, ParNode::has_property) {
+ if !self.par.styles.compatible::<ParNode>(styles) {
self.parbreak(None);
self.par.styles = styles.clone();
return;
@@ -397,7 +397,7 @@ impl Packer {
return;
}
- if self.top && !self.flow.styles.compatible(styles, PageNode::has_property) {
+ if self.top && !self.flow.styles.compatible::<PageNode>(styles) {
self.pagebreak();
self.flow.styles = styles.clone();
return;
diff --git a/src/eval/styles.rs b/src/eval/styles.rs
index 0f3d694a..bdc01f1f 100644
--- a/src/eval/styles.rs
+++ b/src/eval/styles.rs
@@ -100,6 +100,16 @@ impl StyleMap {
self.0.push(Entry::new(key, true));
}
+ /// Mark all contained properties as _scoped_. This means that they only
+ /// apply to the first descendant node (of their type) in the hierarchy and
+ /// not its children, too. This is used by class constructors.
+ pub fn scoped(mut self) -> Self {
+ for entry in &mut self.0 {
+ entry.scoped = true;
+ }
+ self
+ }
+
/// Make `self` the first link of the style chain `outer`.
///
/// The resulting style chain contains styles from `self` as well as
@@ -109,7 +119,10 @@ impl StyleMap {
if self.is_empty() {
*outer
} else {
- StyleChain { inner: self, outer: Some(outer) }
+ StyleChain {
+ first: Link::Map(self),
+ outer: Some(outer),
+ }
}
}
@@ -122,9 +135,7 @@ impl StyleMap {
/// immutable style maps from different levels of the hierarchy.
pub fn apply(&mut self, outer: &Self) {
for outer in &outer.0 {
- if let Some(inner) =
- self.0.iter_mut().find(|inner| inner.style_id() == outer.style_id())
- {
+ if let Some(inner) = self.0.iter_mut().find(|inner| inner.is_same(outer)) {
*inner = inner.fold(outer);
continue;
}
@@ -145,13 +156,10 @@ impl StyleMap {
self.0.retain(|x| other.0.contains(x));
}
- /// 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 = |entry: &&Entry| filter(entry.style_id());
+ /// Whether two style maps are equal when filtered down to properties of the
+ /// node `T`.
+ pub fn compatible<T: 'static>(&self, other: &Self) -> bool {
+ let f = |entry: &&Entry| entry.is_of::<T>();
self.0.iter().filter(f).count() == other.0.iter().filter(f).count()
&& self.0.iter().filter(f).all(|x| other.0.contains(x))
}
@@ -168,7 +176,7 @@ impl Debug for StyleMap {
impl PartialEq for StyleMap {
fn eq(&self, other: &Self) -> bool {
- self.compatible(other, |_| true)
+ self.0.len() == other.0.len() && self.0.iter().all(|x| other.0.contains(x))
}
}
@@ -182,15 +190,22 @@ impl PartialEq for StyleMap {
#[derive(Clone, Copy, Hash)]
pub struct StyleChain<'a> {
/// The first map in the chain.
- inner: &'a StyleMap,
+ first: Link<'a>,
/// The remaining maps in the chain.
outer: Option<&'a Self>,
}
+/// The two kinds of links in the chain.
+#[derive(Clone, Copy, Hash)]
+enum Link<'a> {
+ Map(&'a StyleMap),
+ Barrier(TypeId),
+}
+
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 }
+ Self { first: Link::Map(map), outer: None }
}
/// Get the (folded) value of a copyable style property.
@@ -205,7 +220,7 @@ impl<'a> StyleChain<'a> {
where
P::Value: Copy,
{
- self.get_cloned(key)
+ self.get_impl(key, 0)
}
/// Get a reference to a style property's value.
@@ -220,13 +235,7 @@ impl<'a> StyleChain<'a> {
where
P: Nonfolding,
{
- if let Some(value) = self.find(key) {
- value
- } else if let Some(outer) = self.outer {
- outer.get_ref(key)
- } else {
- P::default_ref()
- }
+ self.get_ref_impl(key, 0)
}
/// Get the (folded) value of any style property.
@@ -238,35 +247,87 @@ impl<'a> StyleChain<'a> {
/// Returns the property's default value if no map in the chain contains an
/// entry for it.
pub fn get_cloned<P: Property>(self, key: P) -> P::Value {
- if let Some(value) = self.find(key).cloned() {
- if !P::FOLDABLE {
- return value;
+ self.get_impl(key, 0)
+ }
+
+ /// Insert a barrier into the style chain.
+ ///
+ /// Barriers interact with [scoped](StyleMap::scoped) styles: A scoped style
+ /// can still be read through a single barrier (the one of the node it
+ /// _should_ apply to), but a second barrier will make it invisible.
+ pub fn barred<'b>(&'b self, node: TypeId) -> StyleChain<'b> {
+ if self.needs_barrier(node) {
+ StyleChain {
+ first: Link::Barrier(node),
+ outer: Some(self),
}
+ } else {
+ *self
+ }
+ }
+}
- match self.outer {
- Some(outer) => P::fold(value, outer.get_cloned(key)),
- None => P::fold(value, P::default()),
+impl<'a> StyleChain<'a> {
+ fn get_impl<P: Property>(self, key: P, depth: usize) -> P::Value {
+ let (value, depth) = self.process(key, depth);
+ if let Some(value) = value.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)
+ outer.get_impl(key, depth)
} else {
P::default()
}
}
- /// Find a property directly in the localmost map.
- fn find<P: Property>(self, _: P) -> Option<&'a P::Value> {
- self.inner
- .0
- .iter()
- .find(|entry| entry.is::<P>())
- .and_then(|entry| entry.downcast::<P>())
+ fn get_ref_impl<P: Property>(self, key: P, depth: usize) -> &'a P::Value
+ where
+ P: Nonfolding,
+ {
+ let (value, depth) = self.process(key, depth);
+ if let Some(value) = value {
+ value
+ } else if let Some(outer) = self.outer {
+ outer.get_ref_impl(key, depth)
+ } else {
+ P::default_ref()
+ }
+ }
+
+ fn process<P: Property>(self, _: P, depth: usize) -> (Option<&'a P::Value>, usize) {
+ match self.first {
+ Link::Map(map) => (
+ map.0
+ .iter()
+ .find(|entry| entry.is::<P>() && (!entry.scoped || depth <= 1))
+ .and_then(|entry| entry.downcast::<P>()),
+ depth,
+ ),
+ Link::Barrier(node) => (None, depth + (P::node_id() == node) as usize),
+ }
+ }
+
+ fn needs_barrier(self, node: TypeId) -> bool {
+ if let Link::Map(map) = self.first {
+ if map.0.iter().any(|entry| entry.is_of_same(node)) {
+ return true;
+ }
+ }
+
+ self.outer.map_or(false, |outer| outer.needs_barrier(node))
}
}
impl Debug for StyleChain<'_> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- self.inner.fmt(f)?;
+ self.first.fmt(f)?;
if let Some(outer) = self.outer {
outer.fmt(f)?;
}
@@ -274,14 +335,67 @@ impl Debug for StyleChain<'_> {
}
}
-/// A unique identifier for a style property.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub struct StyleId(TypeId);
+impl Debug for Link<'_> {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ match self {
+ Self::Map(map) => map.fmt(f),
+ Self::Barrier(id) => writeln!(f, "Barrier({:?})", id),
+ }
+ }
+}
+
+/// An entry for a single style property.
+#[derive(Clone)]
+struct Entry {
+ p: Rc<dyn Bounds>,
+ scoped: bool,
+}
+
+impl Entry {
+ fn new<P: Property>(key: P, value: P::Value) -> Self {
+ Self { p: Rc::new((key, value)), scoped: false }
+ }
+
+ fn is<P: Property>(&self) -> bool {
+ self.p.style_id() == TypeId::of::<P>()
+ }
+
+ fn is_same(&self, other: &Self) -> bool {
+ self.p.style_id() == other.p.style_id()
+ }
+
+ fn is_of<T: 'static>(&self) -> bool {
+ self.p.node_id() == TypeId::of::<T>()
+ }
+
+ fn is_of_same(&self, node: TypeId) -> bool {
+ self.p.node_id() == node
+ }
+
+ fn downcast<P: Property>(&self) -> Option<&P::Value> {
+ self.p.as_any().downcast_ref()
+ }
+
+ fn fold(&self, outer: &Self) -> Self {
+ self.p.fold(outer)
+ }
+}
+
+impl Debug for Entry {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ self.p.dyn_fmt(f)
+ }
+}
+
+impl PartialEq for Entry {
+ fn eq(&self, other: &Self) -> bool {
+ self.p.dyn_eq(other)
+ }
+}
-impl StyleId {
- /// The style id of the property.
- pub fn of<P: Property>() -> Self {
- Self(TypeId::of::<P>())
+impl Hash for Entry {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ state.write_u64(self.p.hash64());
}
}
@@ -298,6 +412,12 @@ pub trait Property: Copy + 'static {
/// The name of the property, used for debug printing.
const NAME: &'static str;
+ /// Whether the property needs folding.
+ const FOLDABLE: bool = false;
+
+ /// The type id of the node this property belongs to.
+ fn node_id() -> TypeId;
+
/// The default value of the property.
fn default() -> Self::Value;
@@ -308,9 +428,6 @@ 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 fold a relative font size with an outer
@@ -324,74 +441,21 @@ pub trait Property: Copy + 'static {
/// Marker trait that indicates that a property doesn't need folding.
pub trait Nonfolding {}
-/// 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(&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;
+ fn node_id(&self) -> TypeId;
+ fn style_id(&self) -> TypeId;
+ fn fold(&self, outer: &Entry) -> Entry;
}
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
}
@@ -401,7 +465,7 @@ impl<P: Property> Bounds for (P, P::Value) {
}
fn dyn_eq(&self, other: &Entry) -> bool {
- self.style_id() == other.style_id()
+ self.style_id() == other.p.style_id()
&& if let Some(other) = other.downcast::<P>() {
&self.1 == other
} else {
@@ -415,4 +479,18 @@ impl<P: Property> Bounds for (P, P::Value) {
self.1.hash(&mut state);
state.finish()
}
+
+ fn node_id(&self) -> TypeId {
+ P::node_id()
+ }
+
+ fn style_id(&self) -> TypeId {
+ TypeId::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)
+ }
}
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index a00687eb..6935afc2 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -197,6 +197,8 @@ impl Layout for PackedNode {
regions: &Regions,
styles: StyleChain,
) -> Vec<Constrained<Rc<Frame>>> {
+ let styles = styles.barred(self.node.as_any().type_id());
+
#[cfg(not(feature = "layout-cache"))]
return self.node.layout(ctx, regions, styles);