Scoped styles

This commit is contained in:
Laurenz 2022-01-07 13:11:46 +01:00
parent af014cfe5e
commit 0b62439090
7 changed files with 249 additions and 164 deletions

View File

@ -17,37 +17,34 @@ pub fn properties(_: TokenStream, item: TokenStream) -> TokenStream {
/// Expand a property impl block for a node.
fn expand(mut impl_block: syn::ItemImpl) -> Result<TokenStream2> {
// Split the node type into name and generic type arguments.
let (self_name, self_args) = parse_self(&*impl_block.self_ty)?;
let self_ty = &*impl_block.self_ty;
let (self_name, self_args) = parse_self(self_ty)?;
// Rewrite the const items from values to keys.
let mut style_ids = vec![];
let mut modules = vec![];
for item in &mut impl_block.items {
if let syn::ImplItem::Const(item) = item {
let (style_id, module) = process_const(item, &self_name, &self_args)?;
style_ids.push(style_id);
let module = process_const(
item,
&impl_block.generics,
self_ty,
&self_name,
&self_args,
)?;
modules.push(module);
}
}
// Here, we use the collected `style_ids` to provide a function that checks
// whether a property belongs to the node.
impl_block.items.insert(0, parse_quote! {
/// Check whether the property with the given type id belongs to `Self`.
pub fn has_property(id: StyleId) -> bool {
[#(#style_ids),*].contains(&id)
}
});
// Put everything into a module with a hopefully unique type to isolate
// it from the outside.
let module = quote::format_ident!("{}_types", self_name);
Ok(quote! {
#[allow(non_snake_case)]
mod #module {
use std::any::TypeId;
use std::marker::PhantomData;
use once_cell::sync::Lazy;
use crate::eval::{Nonfolding, Property, StyleId};
use crate::eval::{Nonfolding, Property};
use super::*;
#impl_block
@ -85,19 +82,21 @@ fn parse_self(self_ty: &syn::Type) -> Result<(String, Vec<&syn::Type>)> {
/// Process a single const item.
fn process_const(
item: &mut syn::ImplItemConst,
impl_generics: &syn::Generics,
self_ty: &syn::Type,
self_name: &str,
self_args: &[&syn::Type],
) -> Result<(syn::Expr, syn::ItemMod)> {
) -> Result<syn::ItemMod> {
// The module that will contain the `Key` type.
let module_name = &item.ident;
// The type of the property's value is what the user of our macro wrote
// as type of the const ...
let value_ty = &item.ty;
// ... but the real type of the const becomes Key<#key_param>.
let key_param = if self_args.is_empty() {
quote! { #value_ty }
} else {
quote! { (#value_ty, #(#self_args),*) }
};
// ... but the real type of the const becomes Key<#key_args>.
let key_params = &impl_generics.params;
let key_args = quote! { #value_ty #(, #self_args)* };
// The display name, e.g. `TextNode::STRONG`.
let name = format!("{}::{}", self_name, &item.ident);
@ -108,7 +107,7 @@ fn process_const(
let mut folder = None;
let mut nonfolding = Some(quote! {
impl<T: 'static> Nonfolding for Key<T> {}
impl<#key_params> Nonfolding for Key<#key_args> {}
});
// Look for a folding function like `#[fold(u64::add)]`.
@ -127,54 +126,50 @@ fn process_const(
}
}
// The implementation of the `Property` trait.
let property_impl = quote! {
impl<T: 'static> Property for Key<T> {
type Value = #value_ty;
const NAME: &'static str = #name;
fn default() -> Self::Value {
#default
}
fn default_ref() -> &'static Self::Value {
static LAZY: Lazy<#value_ty> = Lazy::new(|| #default);
&*LAZY
}
#folder
}
#nonfolding
};
// The module that will contain the `Key` type.
let module_name = &item.ident;
// Generate the style id and module code.
let style_id = parse_quote! { StyleId::of::<#module_name::Key<#key_param>>() };
// Generate the module code.
let module = parse_quote! {
#[allow(non_snake_case)]
mod #module_name {
use super::*;
pub struct Key<T>(pub PhantomData<T>);
impl<T> Copy for Key<T> {}
impl<T> Clone for Key<T> {
pub struct Key<T, #key_params>(pub PhantomData<(T, #key_args)>);
impl<#key_params> Copy for Key<#key_args> {}
impl<#key_params> Clone for Key<#key_args> {
fn clone(&self) -> Self {
*self
}
}
#property_impl
impl<#key_params> Property for Key<#key_args> {
type Value = #value_ty;
const NAME: &'static str = #name;
fn node_id() -> TypeId {
TypeId::of::<#self_ty>()
}
fn default() -> Self::Value {
#default
}
fn default_ref() -> &'static Self::Value {
static LAZY: Lazy<#value_ty> = Lazy::new(|| #default);
&*LAZY
}
#folder
}
#nonfolding
}
};
// Replace type and initializer expression with the `Key`.
item.attrs.retain(|attr| !attr.path.is_ident("fold"));
item.ty = parse_quote! { #module_name::Key<#key_param> };
item.ty = parse_quote! { #module_name::Key<#key_args> };
item.expr = parse_quote! { #module_name::Key(PhantomData) };
Ok((style_id, module))
Ok(module)
}

View File

@ -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.

View File

@ -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;

View File

@ -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)
}
match self.outer {
Some(outer) => P::fold(value, outer.get_cloned(key)),
None => P::fold(value, P::default()),
/// 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
}
}
}
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),
}
}
}
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 {
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 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)
}
}

View File

@ -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);

Binary file not shown.

After

Width:  |  Height:  |  Size: 710 B

View File

@ -0,0 +1,10 @@
// Test class construction.
---
// Ensure that constructor styles aren't passed down the tree.
#set par(spacing: 2pt)
#list(
body-indent: 20pt,
[First],
list([A], [B])
)