use std::fmt::{self, Debug}; use std::hash::Hash; use std::marker::PhantomData; use std::sync::OnceLock; use ecow::{eco_format, EcoString}; use crate::foundations::{ Container, Content, FieldVtable, Fold, FoldFn, IntoValue, NativeElement, Packed, Property, Reflect, Repr, Resolve, StyleChain, }; /// An accessor for the `I`-th field of the element `E`. Values of this type are /// generated for each field of an element can be used to interact with this /// field programmatically, for example to access the style chain, as in /// `styles.get(TextElem::size)`. #[derive(Copy, Clone)] pub struct Field(pub PhantomData); impl Field { /// Creates a new zero-sized accessor. pub const fn new() -> Self { Self(PhantomData) } /// The index of the projected field. pub const fn index(self) -> u8 { I } /// Creates a dynamic property instance for this field. /// /// Prefer [`Content::set`] or /// [`Styles::set`](crate::foundations::Styles::set) when working with /// existing content or style value. pub fn set(self, value: E::Type) -> Property where E: SettableProperty, E::Type: Debug + Clone + Hash + Send + Sync + 'static, { Property::new(self, value) } } impl Default for Field { fn default() -> Self { Self::new() } } /// A field that is present on every instance of the element. pub trait RequiredField: NativeElement { type Type: Clone; const FIELD: RequiredFieldData; } /// Metadata and routines for a [`RequiredField`]. pub struct RequiredFieldData, const I: u8> { name: &'static str, docs: &'static str, get: fn(&E) -> &E::Type, } impl, const I: u8> RequiredFieldData { /// Creates the data from its parts. This is called in the `#[elem]` macro. pub const fn new( name: &'static str, docs: &'static str, get: fn(&E) -> &E::Type, ) -> Self { Self { name, docs, get } } /// Creates the vtable for a `#[required]` field. pub const fn vtable() -> FieldVtable> where E: RequiredField, E::Type: Reflect + IntoValue + PartialEq, { FieldVtable { name: E::FIELD.name, docs: E::FIELD.docs, positional: true, required: true, variadic: false, settable: false, synthesized: false, input: || ::input(), default: None, has: |_| true, get: |elem| Some((E::FIELD.get)(elem).clone().into_value()), get_with_styles: |elem, _| Some((E::FIELD.get)(elem).clone().into_value()), get_from_styles: |_| None, materialize: |_, _| {}, eq: |a, b| (E::FIELD.get)(a) == (E::FIELD.get)(b), } } /// Creates the vtable for a `#[variadic]` field. pub const fn vtable_variadic() -> FieldVtable> where E: RequiredField, E::Type: Container + IntoValue + PartialEq, ::Inner: Reflect, { FieldVtable { name: E::FIELD.name, docs: E::FIELD.docs, positional: true, required: true, variadic: true, settable: false, synthesized: false, input: || <::Inner as Reflect>::input(), default: None, has: |_| true, get: |elem| Some((E::FIELD.get)(elem).clone().into_value()), get_with_styles: |elem, _| Some((E::FIELD.get)(elem).clone().into_value()), get_from_styles: |_| None, materialize: |_, _| {}, eq: |a, b| (E::FIELD.get)(a) == (E::FIELD.get)(b), } } } /// A field that is initially unset, but may be set through a /// [`Synthesize`](crate::foundations::Synthesize) implementation. pub trait SynthesizedField: NativeElement { type Type: Clone; const FIELD: SynthesizedFieldData; } /// Metadata and routines for a [`SynthesizedField`]. pub struct SynthesizedFieldData, const I: u8> { name: &'static str, docs: &'static str, get: fn(&E) -> &Option, } impl, const I: u8> SynthesizedFieldData { /// Creates the data from its parts. This is called in the `#[elem]` macro. pub const fn new( name: &'static str, docs: &'static str, get: fn(&E) -> &Option, ) -> Self { Self { name, docs, get } } /// Creates type-erased metadata and routines for a `#[synthesized]` field. pub const fn vtable() -> FieldVtable> where E: SynthesizedField, E::Type: Reflect + IntoValue + PartialEq, { FieldVtable { name: E::FIELD.name, docs: E::FIELD.docs, positional: false, required: false, variadic: false, settable: false, synthesized: true, input: || ::input(), default: None, has: |elem| (E::FIELD.get)(elem).is_some(), get: |elem| (E::FIELD.get)(elem).clone().map(|v| v.into_value()), get_with_styles: |elem, _| { (E::FIELD.get)(elem).clone().map(|v| v.into_value()) }, get_from_styles: |_| None, materialize: |_, _| {}, // Synthesized fields don't affect equality. eq: |_, _| true, } } } /// A field that is not actually there. It's only visible in the docs. pub trait ExternalField: NativeElement { type Type; const FIELD: ExternalFieldData; } /// Metadata for an [`ExternalField`]. pub struct ExternalFieldData, const I: u8> { name: &'static str, docs: &'static str, default: fn() -> E::Type, } impl, const I: u8> ExternalFieldData { /// Creates the data from its parts. This is called in the `#[elem]` macro. pub const fn new( name: &'static str, docs: &'static str, default: fn() -> E::Type, ) -> Self { Self { name, docs, default } } /// Creates type-erased metadata and routines for an `#[external]` field. pub const fn vtable() -> FieldVtable> where E: ExternalField, E::Type: Reflect + IntoValue, { FieldVtable { name: E::FIELD.name, docs: E::FIELD.docs, positional: false, required: false, variadic: false, settable: false, synthesized: false, input: || ::input(), default: Some(|| (E::FIELD.default)().into_value()), has: |_| false, get: |_| None, get_with_styles: |_, _| None, get_from_styles: |_| None, materialize: |_, _| {}, eq: |_, _| true, } } } /// A field that has a default value and can be configured via a set rule, but /// can also present on elements and be present in the constructor. pub trait SettableField: NativeElement { type Type: Clone; const FIELD: SettableFieldData; } /// Metadata and routines for a [`SettableField`]. pub struct SettableFieldData, const I: u8> { get: fn(&E) -> &Settable, get_mut: fn(&mut E) -> &mut Settable, property: SettablePropertyData, } impl, const I: u8> SettableFieldData { /// Creates the data from its parts. This is called in the `#[elem]` macro. pub const fn new( name: &'static str, docs: &'static str, positional: bool, get: fn(&E) -> &Settable, get_mut: fn(&mut E) -> &mut Settable, default: fn() -> E::Type, slot: fn() -> &'static OnceLock, ) -> Self { Self { get, get_mut, property: SettablePropertyData::new(name, docs, positional, default, slot), } } /// Ensures that the property is folded on every access. See the /// documentation of the [`Fold`] trait for more details. pub const fn with_fold(mut self) -> Self where E::Type: Fold, { self.property.fold = Some(E::Type::fold); self } /// Creates type-erased metadata and routines for a normal settable field. pub const fn vtable() -> FieldVtable> where E: SettableField, E::Type: Reflect + IntoValue + PartialEq, { FieldVtable { name: E::FIELD.property.name, docs: E::FIELD.property.docs, positional: E::FIELD.property.positional, required: false, variadic: false, settable: true, synthesized: false, input: || ::input(), default: Some(|| E::default().into_value()), has: |elem| (E::FIELD.get)(elem).is_set(), get: |elem| (E::FIELD.get)(elem).as_option().clone().map(|v| v.into_value()), get_with_styles: |elem, styles| { Some((E::FIELD.get)(elem).get_cloned(styles).into_value()) }, get_from_styles: |styles| { Some(styles.get_cloned::(Field::new()).into_value()) }, materialize: |elem, styles| { if !(E::FIELD.get)(elem).is_set() { (E::FIELD.get_mut)(elem).set(styles.get_cloned::(Field::new())); } }, eq: |a, b| (E::FIELD.get)(a).as_option() == (E::FIELD.get)(b).as_option(), } } } /// A field that has a default value and can be configured via a set rule, but /// is never present on elements. /// /// This is provided for all `SettableField` impls through a blanket impl. In /// the case of `#[ghost]` fields, which only live in the style chain and not in /// elements, it is also implemented manually. pub trait SettableProperty: NativeElement { type Type: Clone; const FIELD: SettablePropertyData; const FOLD: Option> = Self::FIELD.fold; /// Produces an instance of the property's default value. fn default() -> Self::Type { // Avoid recreating an expensive instance over and over, but also // avoid unnecessary lazy initialization for cheap types. if std::mem::needs_drop::() { Self::default_ref().clone() } else { (Self::FIELD.default)() } } /// Produces a static reference to this property's default value. fn default_ref() -> &'static Self::Type { (Self::FIELD.slot)().get_or_init(Self::FIELD.default) } } impl SettableProperty for T where T: SettableField, { type Type = >::Type; const FIELD: SettablePropertyData = >::FIELD.property; } /// Metadata and routines for a [`SettableProperty`]. pub struct SettablePropertyData, const I: u8> { name: &'static str, docs: &'static str, positional: bool, default: fn() -> E::Type, slot: fn() -> &'static OnceLock, fold: Option>, } impl, const I: u8> SettablePropertyData { /// Creates the data from its parts. This is called in the `#[elem]` macro. pub const fn new( name: &'static str, docs: &'static str, positional: bool, default: fn() -> E::Type, slot: fn() -> &'static OnceLock, ) -> Self { Self { name, docs, positional, default, slot, fold: None } } /// Ensures that the property is folded on every access. See the /// documentation of the [`Fold`] trait for more details. pub const fn with_fold(self) -> Self where E::Type: Fold, { Self { fold: Some(E::Type::fold), ..self } } /// Creates type-erased metadata and routines for a `#[ghost]` field. pub const fn vtable() -> FieldVtable> where E: SettableProperty, E::Type: Reflect + IntoValue + PartialEq, { FieldVtable { name: E::FIELD.name, docs: E::FIELD.docs, positional: E::FIELD.positional, required: false, variadic: false, settable: true, synthesized: false, input: || ::input(), default: Some(|| E::default().into_value()), has: |_| false, get: |_| None, get_with_styles: |_, styles| { Some(styles.get_cloned::(Field::new()).into_value()) }, get_from_styles: |styles| { Some(styles.get_cloned::(Field::new()).into_value()) }, materialize: |_, _| {}, eq: |_, _| true, } } } /// A settable property that can be accessed by reference (because it is not /// folded). pub trait RefableProperty: SettableProperty {} /// A settable field of an element. /// /// The field can be in two states: Unset or present. /// /// See [`StyleChain`] for more details about the available accessor methods. #[derive(Copy, Clone, Hash)] pub struct Settable(Option) where E: SettableProperty; impl Settable where E: SettableProperty, { /// Creates a new unset instance. pub fn new() -> Self { Self(None) } /// Sets the instance to a value. pub fn set(&mut self, value: E::Type) { self.0 = Some(value); } /// Clears the value from the instance. pub fn unset(&mut self) { self.0 = None; } /// Views the type as an [`Option`] which is `Some` if the type is set /// and `None` if it is unset. pub fn as_option(&self) -> &Option { &self.0 } /// Views the type as a mutable [`Option`]. pub fn as_option_mut(&mut self) -> &mut Option { &mut self.0 } /// Whether the field is set. pub fn is_set(&self) -> bool { self.0.is_some() } /// Retrieves the value given styles. The styles are used if the value is /// unset. pub fn get<'a>(&'a self, styles: StyleChain<'a>) -> E::Type where E::Type: Copy, { self.get_cloned(styles) } /// Retrieves and clones the value given styles. The styles are used if the /// value is unset or if it needs folding. pub fn get_cloned<'a>(&'a self, styles: StyleChain<'a>) -> E::Type { if let Some(fold) = E::FOLD { let mut res = styles.get_cloned::(Field::new()); if let Some(value) = &self.0 { res = fold(value.clone(), res); } res } else if let Some(value) = &self.0 { value.clone() } else { styles.get_cloned::(Field::new()) } } /// Retrieves a reference to the value given styles. The styles are used if /// the value is unset. pub fn get_ref<'a>(&'a self, styles: StyleChain<'a>) -> &'a E::Type where E: RefableProperty, { if let Some(value) = &self.0 { value } else { styles.get_ref::(Field::new()) } } /// Retrieves the value and then immediately [resolves](Resolve) it. pub fn resolve<'a>(&'a self, styles: StyleChain<'a>) -> ::Output where E::Type: Resolve, { self.get_cloned(styles).resolve(styles) } } impl Debug for Settable where E: SettableProperty, E::Type: Debug, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) } } impl Default for Settable where E: SettableProperty, { fn default() -> Self { Self(None) } } impl From> for Settable where E: SettableProperty, { fn from(value: Option) -> Self { Self(value) } } /// An error arising when trying to access a field of content. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum FieldAccessError { Unknown, Unset, } impl FieldAccessError { /// Formats the error message given the content and the field name. #[cold] pub fn message(self, content: &Content, field: &str) -> EcoString { let elem_name = content.elem().name(); match self { FieldAccessError::Unknown => { eco_format!("{elem_name} does not have field {}", field.repr()) } FieldAccessError::Unset => { eco_format!( "field {} in {elem_name} is not known at this point", field.repr() ) } } } /// Formats the error message for an `at` calls without a default value. #[cold] pub fn message_no_default(self, content: &Content, field: &str) -> EcoString { let mut msg = self.message(content, field); msg.push_str(" and no default was specified"); msg } }