mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Scoped styles
This commit is contained in:
parent
af014cfe5e
commit
0b62439090
@ -17,37 +17,34 @@ pub fn properties(_: TokenStream, item: TokenStream) -> TokenStream {
|
|||||||
/// Expand a property impl block for a node.
|
/// Expand a property impl block for a node.
|
||||||
fn expand(mut impl_block: syn::ItemImpl) -> Result<TokenStream2> {
|
fn expand(mut impl_block: syn::ItemImpl) -> Result<TokenStream2> {
|
||||||
// Split the node type into name and generic type arguments.
|
// 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.
|
// Rewrite the const items from values to keys.
|
||||||
let mut style_ids = vec![];
|
|
||||||
let mut modules = vec![];
|
let mut modules = vec![];
|
||||||
for item in &mut impl_block.items {
|
for item in &mut impl_block.items {
|
||||||
if let syn::ImplItem::Const(item) = item {
|
if let syn::ImplItem::Const(item) = item {
|
||||||
let (style_id, module) = process_const(item, &self_name, &self_args)?;
|
let module = process_const(
|
||||||
style_ids.push(style_id);
|
item,
|
||||||
|
&impl_block.generics,
|
||||||
|
self_ty,
|
||||||
|
&self_name,
|
||||||
|
&self_args,
|
||||||
|
)?;
|
||||||
modules.push(module);
|
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
|
// Put everything into a module with a hopefully unique type to isolate
|
||||||
// it from the outside.
|
// it from the outside.
|
||||||
let module = quote::format_ident!("{}_types", self_name);
|
let module = quote::format_ident!("{}_types", self_name);
|
||||||
Ok(quote! {
|
Ok(quote! {
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
mod #module {
|
mod #module {
|
||||||
|
use std::any::TypeId;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use crate::eval::{Nonfolding, Property, StyleId};
|
use crate::eval::{Nonfolding, Property};
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#impl_block
|
#impl_block
|
||||||
@ -85,19 +82,21 @@ fn parse_self(self_ty: &syn::Type) -> Result<(String, Vec<&syn::Type>)> {
|
|||||||
/// Process a single const item.
|
/// Process a single const item.
|
||||||
fn process_const(
|
fn process_const(
|
||||||
item: &mut syn::ImplItemConst,
|
item: &mut syn::ImplItemConst,
|
||||||
|
impl_generics: &syn::Generics,
|
||||||
|
self_ty: &syn::Type,
|
||||||
self_name: &str,
|
self_name: &str,
|
||||||
self_args: &[&syn::Type],
|
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
|
// The type of the property's value is what the user of our macro wrote
|
||||||
// as type of the const ...
|
// as type of the const ...
|
||||||
let value_ty = &item.ty;
|
let value_ty = &item.ty;
|
||||||
|
|
||||||
// ... but the real type of the const becomes Key<#key_param>.
|
// ... but the real type of the const becomes Key<#key_args>.
|
||||||
let key_param = if self_args.is_empty() {
|
let key_params = &impl_generics.params;
|
||||||
quote! { #value_ty }
|
let key_args = quote! { #value_ty #(, #self_args)* };
|
||||||
} else {
|
|
||||||
quote! { (#value_ty, #(#self_args),*) }
|
|
||||||
};
|
|
||||||
|
|
||||||
// The display name, e.g. `TextNode::STRONG`.
|
// The display name, e.g. `TextNode::STRONG`.
|
||||||
let name = format!("{}::{}", self_name, &item.ident);
|
let name = format!("{}::{}", self_name, &item.ident);
|
||||||
@ -108,7 +107,7 @@ fn process_const(
|
|||||||
|
|
||||||
let mut folder = None;
|
let mut folder = None;
|
||||||
let mut nonfolding = Some(quote! {
|
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)]`.
|
// Look for a folding function like `#[fold(u64::add)]`.
|
||||||
@ -127,54 +126,50 @@ fn process_const(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The implementation of the `Property` trait.
|
// Generate the module code.
|
||||||
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>>() };
|
|
||||||
let module = parse_quote! {
|
let module = parse_quote! {
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
mod #module_name {
|
mod #module_name {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
pub struct Key<T>(pub PhantomData<T>);
|
pub struct Key<T, #key_params>(pub PhantomData<(T, #key_args)>);
|
||||||
impl<T> Copy for Key<T> {}
|
|
||||||
impl<T> Clone for Key<T> {
|
impl<#key_params> Copy for Key<#key_args> {}
|
||||||
|
impl<#key_params> Clone for Key<#key_args> {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> 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`.
|
// Replace type and initializer expression with the `Key`.
|
||||||
item.attrs.retain(|attr| !attr.path.is_ident("fold"));
|
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) };
|
item.expr = parse_quote! { #module_name::Key(PhantomData) };
|
||||||
|
|
||||||
Ok((style_id, module))
|
Ok(module)
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,7 @@ impl Class {
|
|||||||
let mut styles = StyleMap::new();
|
let mut styles = StyleMap::new();
|
||||||
self.set(args, &mut styles)?;
|
self.set(args, &mut styles)?;
|
||||||
let node = (self.construct)(ctx, args)?;
|
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.
|
/// Execute the class's set rule.
|
||||||
|
@ -302,7 +302,7 @@ impl Packer {
|
|||||||
if let Some(Styled { item: ParChild::Text(left), map }) =
|
if let Some(Styled { item: ParChild::Text(left), map }) =
|
||||||
self.par.children.last_mut()
|
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);
|
left.0.push_str(&right.0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -380,7 +380,7 @@ impl Packer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.par.styles.compatible(styles, ParNode::has_property) {
|
if !self.par.styles.compatible::<ParNode>(styles) {
|
||||||
self.parbreak(None);
|
self.parbreak(None);
|
||||||
self.par.styles = styles.clone();
|
self.par.styles = styles.clone();
|
||||||
return;
|
return;
|
||||||
@ -397,7 +397,7 @@ impl Packer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.top && !self.flow.styles.compatible(styles, PageNode::has_property) {
|
if self.top && !self.flow.styles.compatible::<PageNode>(styles) {
|
||||||
self.pagebreak();
|
self.pagebreak();
|
||||||
self.flow.styles = styles.clone();
|
self.flow.styles = styles.clone();
|
||||||
return;
|
return;
|
||||||
|
@ -100,6 +100,16 @@ impl StyleMap {
|
|||||||
self.0.push(Entry::new(key, true));
|
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`.
|
/// Make `self` the first link of the style chain `outer`.
|
||||||
///
|
///
|
||||||
/// The resulting style chain contains styles from `self` as well as
|
/// The resulting style chain contains styles from `self` as well as
|
||||||
@ -109,7 +119,10 @@ impl StyleMap {
|
|||||||
if self.is_empty() {
|
if self.is_empty() {
|
||||||
*outer
|
*outer
|
||||||
} else {
|
} 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.
|
/// immutable style maps from different levels of the hierarchy.
|
||||||
pub fn apply(&mut self, outer: &Self) {
|
pub fn apply(&mut self, outer: &Self) {
|
||||||
for outer in &outer.0 {
|
for outer in &outer.0 {
|
||||||
if let Some(inner) =
|
if let Some(inner) = self.0.iter_mut().find(|inner| inner.is_same(outer)) {
|
||||||
self.0.iter_mut().find(|inner| inner.style_id() == outer.style_id())
|
|
||||||
{
|
|
||||||
*inner = inner.fold(outer);
|
*inner = inner.fold(outer);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -145,13 +156,10 @@ impl StyleMap {
|
|||||||
self.0.retain(|x| other.0.contains(x));
|
self.0.retain(|x| other.0.contains(x));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether two style maps are equal when filtered down to the given
|
/// Whether two style maps are equal when filtered down to properties of the
|
||||||
/// properties.
|
/// node `T`.
|
||||||
pub fn compatible<F>(&self, other: &Self, filter: F) -> bool
|
pub fn compatible<T: 'static>(&self, other: &Self) -> bool {
|
||||||
where
|
let f = |entry: &&Entry| entry.is_of::<T>();
|
||||||
F: Fn(StyleId) -> bool,
|
|
||||||
{
|
|
||||||
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).count() == other.0.iter().filter(f).count()
|
||||||
&& self.0.iter().filter(f).all(|x| other.0.contains(x))
|
&& self.0.iter().filter(f).all(|x| other.0.contains(x))
|
||||||
}
|
}
|
||||||
@ -168,7 +176,7 @@ impl Debug for StyleMap {
|
|||||||
|
|
||||||
impl PartialEq for StyleMap {
|
impl PartialEq for StyleMap {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
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)]
|
#[derive(Clone, Copy, Hash)]
|
||||||
pub struct StyleChain<'a> {
|
pub struct StyleChain<'a> {
|
||||||
/// The first map in the chain.
|
/// The first map in the chain.
|
||||||
inner: &'a StyleMap,
|
first: Link<'a>,
|
||||||
/// The remaining maps in the chain.
|
/// The remaining maps in the chain.
|
||||||
outer: Option<&'a Self>,
|
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> {
|
impl<'a> StyleChain<'a> {
|
||||||
/// Start a new style chain with a root map.
|
/// Start a new style chain with a root map.
|
||||||
pub fn new(map: &'a StyleMap) -> Self {
|
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.
|
/// Get the (folded) value of a copyable style property.
|
||||||
@ -205,7 +220,7 @@ impl<'a> StyleChain<'a> {
|
|||||||
where
|
where
|
||||||
P::Value: Copy,
|
P::Value: Copy,
|
||||||
{
|
{
|
||||||
self.get_cloned(key)
|
self.get_impl(key, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a reference to a style property's value.
|
/// Get a reference to a style property's value.
|
||||||
@ -220,13 +235,7 @@ impl<'a> StyleChain<'a> {
|
|||||||
where
|
where
|
||||||
P: Nonfolding,
|
P: Nonfolding,
|
||||||
{
|
{
|
||||||
if let Some(value) = self.find(key) {
|
self.get_ref_impl(key, 0)
|
||||||
value
|
|
||||||
} else if let Some(outer) = self.outer {
|
|
||||||
outer.get_ref(key)
|
|
||||||
} else {
|
|
||||||
P::default_ref()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the (folded) value of any style property.
|
/// 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
|
/// Returns the property's default value if no map in the chain contains an
|
||||||
/// entry for it.
|
/// entry for it.
|
||||||
pub fn get_cloned<P: Property>(self, key: P) -> P::Value {
|
pub fn get_cloned<P: Property>(self, key: P) -> P::Value {
|
||||||
if let Some(value) = self.find(key).cloned() {
|
self.get_impl(key, 0)
|
||||||
if !P::FOLDABLE {
|
}
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
match self.outer {
|
/// Insert a barrier into the style chain.
|
||||||
Some(outer) => P::fold(value, outer.get_cloned(key)),
|
///
|
||||||
None => P::fold(value, P::default()),
|
/// 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 {
|
} else if let Some(outer) = self.outer {
|
||||||
outer.get_cloned(key)
|
outer.get_impl(key, depth)
|
||||||
} else {
|
} else {
|
||||||
P::default()
|
P::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find a property directly in the localmost map.
|
fn get_ref_impl<P: Property>(self, key: P, depth: usize) -> &'a P::Value
|
||||||
fn find<P: Property>(self, _: P) -> Option<&'a P::Value> {
|
where
|
||||||
self.inner
|
P: Nonfolding,
|
||||||
.0
|
{
|
||||||
.iter()
|
let (value, depth) = self.process(key, depth);
|
||||||
.find(|entry| entry.is::<P>())
|
if let Some(value) = value {
|
||||||
.and_then(|entry| entry.downcast::<P>())
|
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<'_> {
|
impl Debug for StyleChain<'_> {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
self.inner.fmt(f)?;
|
self.first.fmt(f)?;
|
||||||
if let Some(outer) = self.outer {
|
if let Some(outer) = self.outer {
|
||||||
outer.fmt(f)?;
|
outer.fmt(f)?;
|
||||||
}
|
}
|
||||||
@ -274,14 +335,67 @@ impl Debug for StyleChain<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A unique identifier for a style property.
|
impl Debug for Link<'_> {
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
pub struct StyleId(TypeId);
|
match self {
|
||||||
|
Self::Map(map) => map.fmt(f),
|
||||||
|
Self::Barrier(id) => writeln!(f, "Barrier({:?})", id),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl StyleId {
|
/// An entry for a single style property.
|
||||||
/// The style id of the property.
|
#[derive(Clone)]
|
||||||
pub fn of<P: Property>() -> Self {
|
struct Entry {
|
||||||
Self(TypeId::of::<P>())
|
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.
|
/// The name of the property, used for debug printing.
|
||||||
const NAME: &'static str;
|
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.
|
/// The default value of the property.
|
||||||
fn default() -> Self::Value;
|
fn default() -> Self::Value;
|
||||||
|
|
||||||
@ -308,9 +428,6 @@ pub trait Property: Copy + 'static {
|
|||||||
/// recreated all the time.
|
/// recreated all the time.
|
||||||
fn default_ref() -> &'static Self::Value;
|
fn default_ref() -> &'static Self::Value;
|
||||||
|
|
||||||
/// Whether the property needs folding.
|
|
||||||
const FOLDABLE: bool = false;
|
|
||||||
|
|
||||||
/// Fold the property with an outer value.
|
/// Fold the property with an outer value.
|
||||||
///
|
///
|
||||||
/// For example, this would fold a relative font size with an outer
|
/// 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.
|
/// Marker trait that indicates that a property doesn't need folding.
|
||||||
pub trait Nonfolding {}
|
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
|
/// 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
|
/// 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
|
/// of the implementing type so that we can use it in the methods (it must be a
|
||||||
/// constrained type parameter).
|
/// constrained type parameter).
|
||||||
trait Bounds: 'static {
|
trait Bounds: 'static {
|
||||||
fn style_id(&self) -> StyleId;
|
|
||||||
fn fold(&self, outer: &Entry) -> Entry;
|
|
||||||
fn as_any(&self) -> &dyn Any;
|
fn as_any(&self) -> &dyn Any;
|
||||||
fn dyn_fmt(&self, f: &mut Formatter) -> fmt::Result;
|
fn dyn_fmt(&self, f: &mut Formatter) -> fmt::Result;
|
||||||
fn dyn_eq(&self, other: &Entry) -> bool;
|
fn dyn_eq(&self, other: &Entry) -> bool;
|
||||||
fn hash64(&self) -> u64;
|
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) {
|
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 {
|
fn as_any(&self) -> &dyn Any {
|
||||||
&self.1
|
&self.1
|
||||||
}
|
}
|
||||||
@ -401,7 +465,7 @@ impl<P: Property> Bounds for (P, P::Value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn dyn_eq(&self, other: &Entry) -> bool {
|
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>() {
|
&& if let Some(other) = other.downcast::<P>() {
|
||||||
&self.1 == other
|
&self.1 == other
|
||||||
} else {
|
} else {
|
||||||
@ -415,4 +479,18 @@ impl<P: Property> Bounds for (P, P::Value) {
|
|||||||
self.1.hash(&mut state);
|
self.1.hash(&mut state);
|
||||||
state.finish()
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -197,6 +197,8 @@ impl Layout for PackedNode {
|
|||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> Vec<Constrained<Rc<Frame>>> {
|
) -> Vec<Constrained<Rc<Frame>>> {
|
||||||
|
let styles = styles.barred(self.node.as_any().type_id());
|
||||||
|
|
||||||
#[cfg(not(feature = "layout-cache"))]
|
#[cfg(not(feature = "layout-cache"))]
|
||||||
return self.node.layout(ctx, regions, styles);
|
return self.node.layout(ctx, regions, styles);
|
||||||
|
|
||||||
|
BIN
tests/ref/style/construct.png
Normal file
BIN
tests/ref/style/construct.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 710 B |
10
tests/typ/style/construct.typ
Normal file
10
tests/typ/style/construct.typ
Normal 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])
|
||||||
|
)
|
Loading…
x
Reference in New Issue
Block a user