use crate::diag::{SourceResult, bail};
use crate::engine::Engine;
use crate::foundations::{
Args, AutoValue, Construct, Content, NativeElement, Packed, Smart, StyleChain, Value,
cast, elem,
};
use crate::introspection::Locator;
use crate::layout::{
Abs, Corners, Em, Fr, Fragment, Frame, Length, Region, Regions, Rel, Sides, Size,
Spacing,
};
use crate::visualize::{Paint, Stroke};
#[elem]
pub struct BoxElem {
pub width: Sizing,
pub height: Smart<Rel<Length>>,
pub baseline: Rel<Length>,
pub fill: Option<Paint>,
#[fold]
pub stroke: Sides<Option<Option<Stroke>>>,
#[fold]
pub radius: Corners<Option<Rel<Length>>>,
#[fold]
pub inset: Sides<Option<Rel<Length>>>,
#[fold]
pub outset: Sides<Option<Rel<Length>>>,
#[default(false)]
pub clip: bool,
#[positional]
pub body: Option<Content>,
}
#[elem(Construct)]
pub struct InlineElem {
#[required]
#[internal]
body: callbacks::InlineCallback,
}
impl Construct for InlineElem {
fn construct(_: &mut Engine, args: &mut Args) -> SourceResult<Content> {
bail!(args.span, "cannot be constructed manually");
}
}
impl InlineElem {
#[allow(clippy::type_complexity)]
pub fn layouter<T: NativeElement>(
captured: Packed<T>,
callback: fn(
content: &Packed<T>,
engine: &mut Engine,
locator: Locator,
styles: StyleChain,
region: Size,
) -> SourceResult<Vec<InlineItem>>,
) -> Self {
Self::new(callbacks::InlineCallback::new(captured, callback))
}
}
impl Packed<InlineElem> {
pub fn layout(
&self,
engine: &mut Engine,
locator: Locator,
styles: StyleChain,
region: Size,
) -> SourceResult<Vec<InlineItem>> {
self.body.call(engine, locator, styles, region)
}
}
#[derive(Debug, Clone)]
pub enum InlineItem {
Space(Abs, bool),
Frame(Frame),
}
#[elem]
pub struct BlockElem {
pub width: Smart<Rel<Length>>,
pub height: Sizing,
#[default(true)]
pub breakable: bool,
pub fill: Option<Paint>,
#[fold]
pub stroke: Sides<Option<Option<Stroke>>>,
#[fold]
pub radius: Corners<Option<Rel<Length>>>,
#[fold]
pub inset: Sides<Option<Rel<Length>>>,
#[fold]
pub outset: Sides<Option<Rel<Length>>>,
#[external]
#[default(Em::new(1.2).into())]
pub spacing: Spacing,
#[parse(
let spacing = args.named("spacing")?;
args.named("above")?.or(spacing)
)]
pub above: Smart<Spacing>,
#[parse(args.named("below")?.or(spacing))]
pub below: Smart<Spacing>,
#[default(false)]
pub clip: bool,
#[default(false)]
pub sticky: bool,
#[positional]
pub body: Option<BlockBody>,
}
impl BlockElem {
pub fn single_layouter<T: NativeElement>(
captured: Packed<T>,
f: fn(
content: &Packed<T>,
engine: &mut Engine,
locator: Locator,
styles: StyleChain,
region: Region,
) -> SourceResult<Frame>,
) -> Self {
Self::new()
.with_breakable(false)
.with_body(Some(BlockBody::SingleLayouter(
callbacks::BlockSingleCallback::new(captured, f),
)))
}
pub fn multi_layouter<T: NativeElement>(
captured: Packed<T>,
f: fn(
content: &Packed<T>,
engine: &mut Engine,
locator: Locator,
styles: StyleChain,
regions: Regions,
) -> SourceResult<Fragment>,
) -> Self {
Self::new().with_body(Some(BlockBody::MultiLayouter(
callbacks::BlockMultiCallback::new(captured, f),
)))
}
}
#[derive(Debug, Clone, PartialEq, Hash)]
pub enum BlockBody {
Content(Content),
SingleLayouter(callbacks::BlockSingleCallback),
MultiLayouter(callbacks::BlockMultiCallback),
}
impl Default for BlockBody {
fn default() -> Self {
Self::Content(Content::default())
}
}
cast! {
BlockBody,
self => match self {
Self::Content(content) => content.into_value(),
_ => Value::Auto,
},
v: Content => Self::Content(v),
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum Sizing {
Auto,
Rel(Rel),
Fr(Fr),
}
impl Sizing {
pub fn is_auto(self) -> bool {
matches!(self, Self::Auto)
}
pub fn is_fractional(self) -> bool {
matches!(self, Self::Fr(_))
}
}
impl Default for Sizing {
fn default() -> Self {
Self::Auto
}
}
impl From<Smart<Rel>> for Sizing {
fn from(smart: Smart<Rel>) -> Self {
match smart {
Smart::Auto => Self::Auto,
Smart::Custom(rel) => Self::Rel(rel),
}
}
}
impl<T: Into<Spacing>> From<T> for Sizing {
fn from(spacing: T) -> Self {
match spacing.into() {
Spacing::Rel(rel) => Self::Rel(rel),
Spacing::Fr(fr) => Self::Fr(fr),
}
}
}
cast! {
Sizing,
self => match self {
Self::Auto => Value::Auto,
Self::Rel(rel) => rel.into_value(),
Self::Fr(fr) => fr.into_value(),
},
_: AutoValue => Self::Auto,
v: Rel<Length> => Self::Rel(v),
v: Fr => Self::Fr(v),
}
mod callbacks {
use super::*;
macro_rules! callback {
($name:ident = ($($param:ident: $param_ty:ty),* $(,)?) -> $ret:ty) => {
#[derive(Debug, Clone, Hash)]
#[allow(clippy::derived_hash_with_manual_eq)]
pub struct $name {
captured: Content,
f: fn(&Content, $($param_ty),*) -> $ret,
}
impl $name {
pub fn new<T: NativeElement>(
captured: Packed<T>,
f: fn(&Packed<T>, $($param_ty),*) -> $ret,
) -> Self {
Self {
captured: captured.pack(),
#[allow(clippy::missing_transmute_annotations)]
f: unsafe { std::mem::transmute(f) },
}
}
pub fn call(&self, $($param: $param_ty),*) -> $ret {
(self.f)(&self.captured, $($param),*)
}
}
impl PartialEq for $name {
fn eq(&self, other: &Self) -> bool {
self.captured.eq(&other.captured)
}
}
};
}
callback! {
InlineCallback = (
engine: &mut Engine,
locator: Locator,
styles: StyleChain,
region: Size,
) -> SourceResult<Vec<InlineItem>>
}
callback! {
BlockSingleCallback = (
engine: &mut Engine,
locator: Locator,
styles: StyleChain,
region: Region,
) -> SourceResult<Frame>
}
callback! {
BlockMultiCallback = (
engine: &mut Engine,
locator: Locator,
styles: StyleChain,
regions: Regions,
) -> SourceResult<Fragment>
}
}