Switch to const generics for nodes

This commit is contained in:
Laurenz 2022-02-17 11:55:28 +01:00
parent 5965515a1e
commit ab95627d87
11 changed files with 159 additions and 279 deletions

View File

@ -4,6 +4,7 @@ use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::parse_quote;
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::{Error, Ident, Result};
@ -118,7 +119,9 @@ struct Property {
}
/// Parse the name and generic type arguments of the node type.
fn parse_self(self_ty: &syn::Type) -> Result<(String, Vec<&syn::Type>)> {
fn parse_self(
self_ty: &syn::Type,
) -> Result<(String, Punctuated<syn::GenericArgument, syn::Token![,]>)> {
// Extract the node type for which we want to generate properties.
let path = match self_ty {
syn::Type::Path(path) => path,
@ -129,15 +132,8 @@ fn parse_self(self_ty: &syn::Type) -> Result<(String, Vec<&syn::Type>)> {
let last = path.path.segments.last().unwrap();
let self_name = last.ident.to_string();
let self_args = match &last.arguments {
syn::PathArguments::AngleBracketed(args) => args
.args
.iter()
.filter_map(|arg| match arg {
syn::GenericArgument::Type(ty) => Some(ty),
_ => None,
})
.collect(),
_ => vec![],
syn::PathArguments::AngleBracketed(args) => args.args.clone(),
_ => Punctuated::new(),
};
Ok((self_name, self_args))
@ -146,29 +142,35 @@ fn parse_self(self_ty: &syn::Type) -> Result<(String, Vec<&syn::Type>)> {
/// Process a single const item.
fn process_const(
item: &mut syn::ImplItemConst,
params: &syn::punctuated::Punctuated<syn::GenericParam, syn::Token![,]>,
params: &Punctuated<syn::GenericParam, syn::Token![,]>,
self_ty: &syn::Type,
self_name: &str,
self_args: &[&syn::Type],
self_args: &Punctuated<syn::GenericArgument, syn::Token![,]>,
) -> Result<(Property, syn::ItemMod)> {
// The module that will contain the `Key` type.
let module_name = &item.ident;
// The display name, e.g. `TextNode::STRONG`.
let name = format!("{}::{}", self_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_args>.
let key_args = quote! { #value_ty #(, #self_args)* };
// The display name, e.g. `TextNode::STRONG`.
let name = format!("{}::{}", self_name, &item.ident);
// ... but the real type of the const becomes this..
let key = quote! { Key<#value_ty, #self_args> };
let phantom_args = self_args.iter().filter(|arg| match arg {
syn::GenericArgument::Type(syn::Type::Path(path)) => {
params.iter().all(|param| match param {
syn::GenericParam::Const(c) => !path.path.is_ident(&c.ident),
_ => true,
})
}
_ => true,
});
// The default value of the property is what the user wrote as
// initialization value of the const.
let default = &item.expr;
let mut folder = None;
let mut fold = None;
let mut property = Property {
name: item.ident.clone(),
shorthand: false,
@ -177,25 +179,23 @@ fn process_const(
};
for attr in std::mem::take(&mut item.attrs) {
if attr.path.is_ident("fold") {
// Look for a folding function like `#[fold(u64::add)]`.
let func: syn::Expr = attr.parse_args()?;
folder = Some(quote! {
const FOLDING: bool = true;
match attr.path.get_ident().map(ToString::to_string).as_deref() {
Some("fold") => {
// Look for a folding function like `#[fold(u64::add)]`.
let func: syn::Expr = attr.parse_args()?;
fold = Some(quote! {
const FOLDING: bool = true;
fn fold(inner: Self::Value, outer: Self::Value) -> Self::Value {
let f: fn(Self::Value, Self::Value) -> Self::Value = #func;
f(inner, outer)
}
});
} else if attr.path.is_ident("shorthand") {
property.shorthand = true;
} else if attr.path.is_ident("variadic") {
property.variadic = true;
} else if attr.path.is_ident("skip") {
property.skip = true;
} else {
item.attrs.push(attr);
fn fold(inner: Self::Value, outer: Self::Value) -> Self::Value {
let f: fn(Self::Value, Self::Value) -> Self::Value = #func;
f(inner, outer)
}
});
}
Some("shorthand") => property.shorthand = true,
Some("variadic") => property.variadic = true,
Some("skip") => property.skip = true,
_ => item.attrs.push(attr),
}
}
@ -206,28 +206,27 @@ fn process_const(
));
}
let nonfolding = folder.is_none().then(|| {
quote! {
impl<#params> Nonfolding for Key<#key_args> {}
}
let nonfolding = fold.is_none().then(|| {
quote! { impl<#params> Nonfolding for #key {} }
});
// Generate the module code.
let module_name = &item.ident;
let module = parse_quote! {
#[allow(non_snake_case)]
mod #module_name {
use super::*;
pub struct Key<VALUE, #params>(pub PhantomData<(VALUE, #key_args)>);
pub struct Key<VALUE, #params>(pub PhantomData<(VALUE, #(#phantom_args,)*)>);
impl<#params> Copy for Key<#key_args> {}
impl<#params> Clone for Key<#key_args> {
impl<#params> Copy for #key {}
impl<#params> Clone for #key {
fn clone(&self) -> Self {
*self
}
}
impl<#params> Property for Key<#key_args> {
impl<#params> Property for #key {
type Value = #value_ty;
const NAME: &'static str = #name;
@ -245,7 +244,7 @@ fn process_const(
&*LAZY
}
#folder
#fold
}
#nonfolding
@ -253,8 +252,7 @@ fn process_const(
};
// 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_args> };
item.ty = parse_quote! { #module_name::#key };
item.expr = parse_quote! { #module_name::Key(PhantomData) };
Ok((property, module))

View File

@ -7,7 +7,7 @@ use crate::syntax::{Span, Spanned};
/// Early-return with a vec-boxed [`Error`].
macro_rules! bail {
($span:expr, $message:expr $(,)?) => {
return Err($crate::diag::Error::boxed($span, $message,))
return Err($crate::diag::Error::boxed($span, $message))
};
($span:expr, $fmt:expr, $($arg:expr),+ $(,)?) => {

View File

@ -41,7 +41,7 @@ use crate::diag::{At, Error, StrResult, Trace, Tracepoint, TypResult};
use crate::geom::{Angle, Fractional, Length, Relative};
use crate::image::ImageStore;
use crate::layout::Layout;
use crate::library::{self};
use crate::library::{self, ORDERED, UNORDERED};
use crate::loading::Loader;
use crate::source::{SourceId, SourceStore};
use crate::syntax::ast::*;
@ -272,9 +272,9 @@ impl Eval for ListNode {
type Output = Template;
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
Ok(Template::show(library::ListNode {
Ok(Template::show(library::ListNode::<UNORDERED> {
number: None,
child: self.body().eval(ctx)?.pack(),
label: library::Unordered,
}))
}
}
@ -283,9 +283,9 @@ impl Eval for EnumNode {
type Output = Template;
fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
Ok(Template::show(library::ListNode {
Ok(Template::show(library::ListNode::<ORDERED> {
number: self.number(),
child: self.body().eval(ctx)?.pack(),
label: library::Ordered(self.number()),
}))
}
}

View File

@ -14,7 +14,7 @@ use crate::layout::{Layout, LayoutNode};
use crate::library::prelude::*;
use crate::library::{
DecoNode, FlowChild, FlowNode, PageNode, ParChild, ParNode, PlaceNode, SpacingKind,
TextNode, Underline,
TextNode, UNDERLINE,
};
use crate::util::EcoString;
use crate::Context;
@ -145,7 +145,7 @@ impl Template {
/// Underline this template.
pub fn underlined(self) -> Self {
Self::show(DecoNode { kind: Underline, body: self })
Self::show(DecoNode::<UNDERLINE>(self))
}
/// Create a new sequence template.

View File

@ -18,9 +18,9 @@ use std::sync::Arc;
use crate::eval::StyleChain;
use crate::font::FontStore;
use crate::frame::{Element, Frame, Geometry, Shape, Stroke};
use crate::geom::{Align, Linear, Paint, Point, Sides, Size, Spec};
use crate::geom::{Align, Linear, Paint, Point, Sides, Size, Spec, Transform};
use crate::image::ImageStore;
use crate::library::{AlignNode, Move, PadNode, TransformNode};
use crate::library::{AlignNode, PadNode, TransformNode, MOVE};
use crate::util::Prehashed;
use crate::Context;
@ -150,8 +150,8 @@ impl LayoutNode {
/// Transform this node's contents without affecting layout.
pub fn moved(self, offset: Point) -> Self {
if !offset.is_zero() {
TransformNode {
kind: Move(offset.x, offset.y),
TransformNode::<MOVE> {
transform: Transform::translation(offset.x, offset.y),
child: self,
}
.pack()

View File

@ -5,15 +5,10 @@ use super::TextNode;
/// Typeset underline, striken-through or overlined text.
#[derive(Debug, Hash)]
pub struct DecoNode<L: LineKind> {
/// The kind of line.
pub kind: L,
/// The decorated contents.
pub body: Template,
}
pub struct DecoNode<const L: DecoLine>(pub Template);
#[class]
impl<L: LineKind> DecoNode<L> {
impl<const L: DecoLine> DecoNode<L> {
/// Stroke color of the line, defaults to the text color if `None`.
#[shorthand]
pub const STROKE: Option<Paint> = None;
@ -32,17 +27,14 @@ impl<L: LineKind> DecoNode<L> {
pub const EVADE: bool = true;
fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Template> {
Ok(Template::show(Self {
kind: L::default(),
body: args.expect::<Template>("body")?,
}))
Ok(Template::show(Self(args.expect::<Template>("body")?)))
}
}
impl<L: LineKind> Show for DecoNode<L> {
impl<const L: DecoLine> Show for DecoNode<L> {
fn show(&self, styles: StyleChain) -> Template {
self.body.clone().styled(TextNode::LINES, vec![Decoration {
line: L::LINE,
self.0.clone().styled(TextNode::LINES, vec![Decoration {
line: L,
stroke: styles.get(Self::STROKE),
thickness: styles.get(Self::THICKNESS),
offset: styles.get(Self::OFFSET),
@ -65,42 +57,14 @@ pub struct Decoration {
pub evade: bool,
}
/// The kind of decorative line.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum DecoLine {
/// A line under text.
Underline,
/// A line through text.
Strikethrough,
/// A line over text.
Overline,
}
/// Different kinds of decorative lines for text.
pub trait LineKind: Debug + Default + Hash + Sync + Send + 'static {
const LINE: DecoLine;
}
/// A kind of decorative line.
pub type DecoLine = usize;
/// A line under text.
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
pub struct Underline;
impl LineKind for Underline {
const LINE: DecoLine = DecoLine::Underline;
}
pub const UNDERLINE: DecoLine = 0;
/// A line through text.
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
pub struct Strikethrough;
impl LineKind for Strikethrough {
const LINE: DecoLine = DecoLine::Strikethrough;
}
pub const STRIKETHROUGH: DecoLine = 1;
/// A line over text.
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
pub struct Overline;
impl LineKind for Overline {
const LINE: DecoLine = DecoLine::Overline;
}
pub const OVERLINE: DecoLine = 2;

View File

@ -5,15 +5,15 @@ use super::{GridNode, TextNode, TrackSizing};
/// An unordered or ordered list.
#[derive(Debug, Hash)]
pub struct ListNode<L: ListLabel> {
/// The list label -- unordered or ordered with index.
pub label: L,
pub struct ListNode<const L: Labelling> {
/// The number of the item.
pub number: Option<usize>,
/// The node that produces the item's body.
pub child: LayoutNode,
}
#[class]
impl<L: ListLabel> ListNode<L> {
impl<const L: Labelling> ListNode<L> {
/// The indentation of each item's label.
pub const LABEL_INDENT: Linear = Relative::new(0.0).into();
/// The space between the label and the body of each item.
@ -24,17 +24,22 @@ impl<L: ListLabel> ListNode<L> {
.all()?
.into_iter()
.enumerate()
.map(|(i, child)| Template::show(Self { label: L::new(1 + i), child }))
.map(|(i, child)| Template::show(Self { number: Some(1 + i), child }))
.sum())
}
}
impl<L: ListLabel> Show for ListNode<L> {
impl<const L: Labelling> Show for ListNode<L> {
fn show(&self, styles: StyleChain) -> Template {
let em = styles.get(TextNode::SIZE).abs;
let label_indent = styles.get(Self::LABEL_INDENT).resolve(em);
let body_indent = styles.get(Self::BODY_INDENT).resolve(em);
let label = match L {
UNORDERED => '•'.into(),
ORDERED | _ => format_eco!("{}.", self.number.unwrap_or(1)),
};
Template::block(GridNode {
tracks: Spec::with_x(vec![
TrackSizing::Linear(label_indent.into()),
@ -45,7 +50,7 @@ impl<L: ListLabel> Show for ListNode<L> {
gutter: Spec::default(),
children: vec![
LayoutNode::default(),
Template::Text(self.label.label()).pack(),
Template::Text(label).pack(),
LayoutNode::default(),
self.child.clone(),
],
@ -54,38 +59,10 @@ impl<L: ListLabel> Show for ListNode<L> {
}
/// How to label a list.
pub trait ListLabel: Debug + Default + Hash + Sync + Send + 'static {
/// Create a new list label.
fn new(number: usize) -> Self;
/// Return the item's label.
fn label(&self) -> EcoString;
}
pub type Labelling = usize;
/// Unordered list labelling style.
#[derive(Debug, Default, Hash)]
pub struct Unordered;
impl ListLabel for Unordered {
fn new(_: usize) -> Self {
Self
}
fn label(&self) -> EcoString {
'•'.into()
}
}
pub const UNORDERED: Labelling = 0;
/// Ordered list labelling style.
#[derive(Debug, Default, Hash)]
pub struct Ordered(pub Option<usize>);
impl ListLabel for Ordered {
fn new(number: usize) -> Self {
Self(Some(number))
}
fn label(&self) -> EcoString {
format_eco!("{}.", self.0.unwrap_or(1))
}
}
pub const ORDERED: Labelling = 1;

View File

@ -53,16 +53,8 @@ pub use text::*;
pub use transform::*;
pub use utility::*;
macro_rules! prelude {
($($reexport:item)*) => {
/// Helpful imports for creating library functionality.
pub mod prelude {
$(#[doc(no_inline)] $reexport)*
}
};
}
prelude! {
/// Helpful imports for creating library functionality.
pub mod prelude {
pub use std::fmt::{self, Debug, Formatter};
pub use std::hash::Hash;
pub use std::num::NonZeroUsize;
@ -101,19 +93,19 @@ pub fn new() -> Scope {
std.def_class::<EmphNode>("emph");
std.def_class::<RawNode>("raw");
std.def_class::<MathNode>("math");
std.def_class::<DecoNode<Underline>>("underline");
std.def_class::<DecoNode<Strikethrough>>("strike");
std.def_class::<DecoNode<Overline>>("overline");
std.def_class::<DecoNode<UNDERLINE>>("underline");
std.def_class::<DecoNode<STRIKETHROUGH>>("strike");
std.def_class::<DecoNode<OVERLINE>>("overline");
std.def_class::<LinkNode>("link");
std.def_class::<HeadingNode>("heading");
std.def_class::<ListNode<Unordered>>("list");
std.def_class::<ListNode<Ordered>>("enum");
std.def_class::<ListNode<UNORDERED>>("list");
std.def_class::<ListNode<ORDERED>>("enum");
std.def_class::<TableNode>("table");
std.def_class::<ImageNode>("image");
std.def_class::<ShapeNode<Rect>>("rect");
std.def_class::<ShapeNode<Square>>("square");
std.def_class::<ShapeNode<Ellipse>>("ellipse");
std.def_class::<ShapeNode<Circle>>("circle");
std.def_class::<ShapeNode<RECT>>("rect");
std.def_class::<ShapeNode<SQUARE>>("square");
std.def_class::<ShapeNode<ELLIPSE>>("ellipse");
std.def_class::<ShapeNode<CIRCLE>>("circle");
// Layout.
std.def_class::<HNode>("h");
@ -123,9 +115,9 @@ pub fn new() -> Scope {
std.def_class::<AlignNode>("align");
std.def_class::<PadNode>("pad");
std.def_class::<PlaceNode>("place");
std.def_class::<TransformNode<Move>>("move");
std.def_class::<TransformNode<Scale>>("scale");
std.def_class::<TransformNode<Rotate>>("rotate");
std.def_class::<TransformNode<MOVE>>("move");
std.def_class::<TransformNode<SCALE>>("scale");
std.def_class::<TransformNode<ROTATE>>("rotate");
std.def_class::<HideNode>("hide");
std.def_class::<StackNode>("stack");
std.def_class::<GridNode>("grid");

View File

@ -7,15 +7,10 @@ use super::TextNode;
/// Place a node into a sizable and fillable shape.
#[derive(Debug, Hash)]
pub struct ShapeNode<S: ShapeKind> {
/// Which shape to place the child into.
pub kind: S,
/// The child node to place into the shape, if any.
pub child: Option<LayoutNode>,
}
pub struct ShapeNode<const S: ShapeKind>(pub Option<LayoutNode>);
#[class]
impl<S: ShapeKind> ShapeNode<S> {
impl<const S: ShapeKind> ShapeNode<S> {
/// How to fill the shape.
pub const FILL: Option<Paint> = None;
/// How the stroke the shape.
@ -26,12 +21,10 @@ impl<S: ShapeKind> ShapeNode<S> {
pub const PADDING: Linear = Linear::zero();
fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Template> {
let size = if !S::ROUND && S::QUADRATIC {
args.named::<Length>("size")?.map(Linear::from)
} else if S::ROUND && S::QUADRATIC {
args.named("radius")?.map(|r: Length| 2.0 * Linear::from(r))
} else {
None
let size = match S {
SQUARE => args.named::<Length>("size")?.map(Linear::from),
CIRCLE => args.named::<Length>("radius")?.map(|r| 2.0 * Linear::from(r)),
_ => None,
};
let width = match size {
@ -45,14 +38,12 @@ impl<S: ShapeKind> ShapeNode<S> {
};
Ok(Template::inline(
ShapeNode { kind: S::default(), child: args.find()? }
.pack()
.sized(Spec::new(width, height)),
Self(args.find()?).pack().sized(Spec::new(width, height)),
))
}
}
impl<S: ShapeKind> Layout for ShapeNode<S> {
impl<const S: ShapeKind> Layout for ShapeNode<S> {
fn layout(
&self,
ctx: &mut LayoutContext,
@ -60,9 +51,9 @@ impl<S: ShapeKind> Layout for ShapeNode<S> {
styles: StyleChain,
) -> Vec<Constrained<Arc<Frame>>> {
let mut frames;
if let Some(child) = &self.child {
if let Some(child) = &self.0 {
let mut padding = styles.get(Self::PADDING);
if S::ROUND {
if is_round(S) {
padding.rel += Relative::new(0.5 - SQRT_2 / 4.0);
}
@ -74,7 +65,7 @@ impl<S: ShapeKind> Layout for ShapeNode<S> {
// Relayout with full expansion into square region to make sure
// the result is really a square or circle.
if S::QUADRATIC {
if is_quadratic(S) {
let length = if regions.expand.x || regions.expand.y {
let target = regions.expand.select(regions.current, Size::zero());
target.x.max(target.y)
@ -95,7 +86,7 @@ impl<S: ShapeKind> Layout for ShapeNode<S> {
let mut size =
Size::new(Length::pt(45.0), Length::pt(30.0)).min(regions.current);
if S::QUADRATIC {
if is_quadratic(S) {
let length = if regions.expand.x || regions.expand.y {
let target = regions.expand.select(regions.current, Size::zero());
target.x.max(target.y)
@ -121,7 +112,7 @@ impl<S: ShapeKind> Layout for ShapeNode<S> {
.map(|paint| Stroke { paint, thickness });
if fill.is_some() || stroke.is_some() {
let geometry = if S::ROUND {
let geometry = if is_round(S) {
Geometry::Ellipse(frame.size)
} else {
Geometry::Rect(frame.size)
@ -140,44 +131,27 @@ impl<S: ShapeKind> Layout for ShapeNode<S> {
}
}
/// Categorizes shapes.
pub trait ShapeKind: Debug + Default + Hash + Sync + Send + 'static {
const ROUND: bool;
const QUADRATIC: bool;
}
/// A category of shape.
pub type ShapeKind = usize;
/// A rectangle with equal side lengths.
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
pub struct Square;
impl ShapeKind for Square {
const ROUND: bool = false;
const QUADRATIC: bool = true;
}
pub const SQUARE: ShapeKind = 0;
/// A quadrilateral with four right angles.
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
pub struct Rect;
impl ShapeKind for Rect {
const ROUND: bool = false;
const QUADRATIC: bool = false;
}
pub const RECT: ShapeKind = 1;
/// An ellipse with coinciding foci.
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
pub struct Circle;
impl ShapeKind for Circle {
const ROUND: bool = true;
const QUADRATIC: bool = true;
}
pub const CIRCLE: ShapeKind = 2;
/// A curve around two focal points.
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
pub struct Ellipse;
pub const ELLIPSE: ShapeKind = 3;
impl ShapeKind for Ellipse {
const ROUND: bool = true;
const QUADRATIC: bool = false;
/// Whether a shape kind is curvy.
fn is_round(kind: ShapeKind) -> bool {
matches!(kind, CIRCLE | ELLIPSE)
}
/// Whether a shape kind has equal side length.
fn is_quadratic(kind: ShapeKind) -> bool {
matches!(kind, SQUARE | CIRCLE)
}

View File

@ -9,7 +9,7 @@ use rustybuzz::{Feature, UnicodeBuffer};
use ttf_parser::{GlyphId, OutlineBuilder, Tag};
use super::prelude::*;
use super::{DecoLine, Decoration};
use super::Decoration;
use crate::font::{
Face, FaceId, FontStore, FontStretch, FontStyle, FontVariant, FontWeight,
VerticalFontMetric,
@ -826,12 +826,12 @@ impl<'a> ShapedText<'a> {
) {
let face = fonts.get(text.face_id);
let metrics = match deco.line {
DecoLine::Underline => face.underline,
DecoLine::Strikethrough => face.strikethrough,
DecoLine::Overline => face.overline,
super::STRIKETHROUGH => face.strikethrough,
super::OVERLINE => face.overline,
super::UNDERLINE | _ => face.underline,
};
let evade = deco.evade && deco.line != DecoLine::Strikethrough;
let evade = deco.evade && deco.line != super::STRIKETHROUGH;
let extent = deco.extent.resolve(text.size);
let offset = deco
.offset

View File

@ -5,27 +5,45 @@ use crate::geom::Transform;
/// Transform a node without affecting layout.
#[derive(Debug, Hash)]
pub struct TransformNode<T: TransformKind> {
pub struct TransformNode<const T: TransformKind> {
/// Transformation to apply to the contents.
pub kind: T,
pub transform: Transform,
/// The node whose contents should be transformed.
pub child: LayoutNode,
}
#[class]
impl<T: TransformKind> TransformNode<T> {
impl<const T: TransformKind> TransformNode<T> {
/// The origin of the transformation.
pub const ORIGIN: Spec<Option<Align>> = Spec::default();
fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Template> {
let transform = match T {
MOVE => {
let tx = args.named("x")?.unwrap_or_default();
let ty = args.named("y")?.unwrap_or_default();
Transform::translation(tx, ty)
}
ROTATE => {
let angle = args.named_or_find("angle")?.unwrap_or_default();
Transform::rotation(angle)
}
SCALE | _ => {
let all = args.find()?;
let sx = args.named("x")?.or(all).unwrap_or(Relative::one());
let sy = args.named("y")?.or(all).unwrap_or(Relative::one());
Transform::scale(sx, sy)
}
};
Ok(Template::inline(Self {
kind: T::construct(args)?,
transform,
child: args.expect("body")?,
}))
}
}
impl<T: TransformKind> Layout for TransformNode<T> {
impl<const T: TransformKind> Layout for TransformNode<T> {
fn layout(
&self,
ctx: &mut LayoutContext,
@ -33,14 +51,12 @@ impl<T: TransformKind> Layout for TransformNode<T> {
styles: StyleChain,
) -> Vec<Constrained<Arc<Frame>>> {
let origin = styles.get(Self::ORIGIN).unwrap_or(Align::CENTER_HORIZON);
let matrix = self.kind.matrix();
let mut frames = self.child.layout(ctx, regions, styles);
for Constrained { item: frame, .. } in &mut frames {
let Spec { x, y } = origin.zip(frame.size).map(|(o, s)| o.resolve(s));
let transform = Transform::translation(x, y)
.pre_concat(matrix)
.pre_concat(self.transform)
.pre_concat(Transform::translation(-x, -y));
Arc::make_mut(frame).transform(transform);
@ -51,54 +67,13 @@ impl<T: TransformKind> Layout for TransformNode<T> {
}
/// Kinds of transformations.
pub trait TransformKind: Debug + Hash + Sized + Sync + Send + 'static {
fn construct(args: &mut Args) -> TypResult<Self>;
fn matrix(&self) -> Transform;
}
pub type TransformKind = usize;
/// A translation on the X and Y axes.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct Move(pub Length, pub Length);
impl TransformKind for Move {
fn construct(args: &mut Args) -> TypResult<Self> {
let tx = args.named("x")?.unwrap_or_default();
let ty = args.named("y")?.unwrap_or_default();
Ok(Self(tx, ty))
}
fn matrix(&self) -> Transform {
Transform::translation(self.0, self.1)
}
}
pub const MOVE: TransformKind = 0;
/// A rotational transformation.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct Rotate(pub Angle);
impl TransformKind for Rotate {
fn construct(args: &mut Args) -> TypResult<Self> {
Ok(Self(args.named_or_find("angle")?.unwrap_or_default()))
}
fn matrix(&self) -> Transform {
Transform::rotation(self.0)
}
}
pub const ROTATE: TransformKind = 1;
/// A scale transformation.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct Scale(pub Relative, pub Relative);
impl TransformKind for Scale {
fn construct(args: &mut Args) -> TypResult<Self> {
let all = args.find()?;
let sx = args.named("x")?.or(all).unwrap_or(Relative::one());
let sy = args.named("y")?.or(all).unwrap_or(Relative::one());
Ok(Self(sx, sy))
}
fn matrix(&self) -> Transform {
Transform::scale(self.0, self.1)
}
}
pub const SCALE: TransformKind = 2;