use crate::image::Image;
use crate::list::List;
use crate::style::{Align, Border, Color, Common, FontKey, Overflow, TextStyle};
use crate::table::Table;
use crate::theme::ThemeRole;
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(tag = "type", rename_all = "snake_case")
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Debug)]
pub enum Element {
Text(Text),
Row(Row),
Column(Column),
Spacer(Spacer),
Line(Line),
Rect(Rect),
Table(Table),
Image(Image),
List(List),
TableOfContents(TableOfContents),
PageBreak,
}
macro_rules! common_accessor {
($self:expr, $($ref:tt)*) => {
match $self {
Element::Text(t) => Some($($ref)* t.common),
Element::Row(r) => Some($($ref)* r.common),
Element::Column(c) => Some($($ref)* c.common),
Element::Line(l) => Some($($ref)* l.common),
Element::Rect(r) => Some($($ref)* r.common),
Element::Table(t) => Some($($ref)* t.common),
Element::Image(i) => Some($($ref)* i.common),
Element::List(l) => Some($($ref)* l.common),
Element::TableOfContents(t) => Some($($ref)* t.common),
Element::Spacer(_) | Element::PageBreak => None,
}
};
}
impl Element {
pub fn common(&self) -> Option<&Common> {
common_accessor!(self, &)
}
pub fn common_mut(&mut self) -> Option<&mut Common> {
common_accessor!(self, &mut)
}
}
macro_rules! common_builder_methods {
() => {
pub fn width(mut self, width: f32) -> Self {
self.common.width = Some(width);
self
}
pub fn height(mut self, height: f32) -> Self {
self.common.height = Some(height);
self
}
pub fn flex(mut self, factor: f32) -> Self {
self.common.flex = Some(factor);
self
}
pub fn padding(mut self, padding: f32) -> Self {
self.common.padding = padding;
self
}
pub fn corner_radius(mut self, radius: f32) -> Self {
self.common.corner_radius = radius;
self
}
pub fn overflow(mut self, overflow: Overflow) -> Self {
self.common.overflow = overflow;
self
}
pub fn background(mut self, color: Color) -> Self {
self.common.background = Some(color);
self
}
pub fn border(mut self, border: Border) -> Self {
self.common.border = Some(border);
self
}
pub fn keep_with_next(mut self) -> Self {
self.common.keep_with_next = true;
self
}
};
}
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(deny_unknown_fields, default)
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, Default)]
pub struct Text {
pub content: String,
#[cfg_attr(feature = "serde", serde(default))]
pub style: TextStyle,
pub url: Option<String>,
pub anchor: Option<String>,
pub link_to: Option<String>,
pub outline_level: Option<u8>,
pub role: Option<ThemeRole>,
pub spans: Option<Box<Vec<Span>>>,
pub hyphenate: Option<HyphenationLanguage>,
pub common: Common,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(rename_all = "snake_case"))]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum HyphenationLanguage {
EnglishUs,
German,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(deny_unknown_fields))]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Debug)]
pub struct Span {
pub text: String,
#[cfg_attr(feature = "serde", serde(default))]
pub style: TextStyle,
}
impl Span {
pub fn new(text: impl Into<String>, style: TextStyle) -> Self {
Span { text: text.into(), style }
}
}
impl Text {
pub fn new(content: impl Into<String>) -> Self {
Text {
content: content.into(),
style: TextStyle::default(),
url: None,
anchor: None,
link_to: None,
outline_level: None,
role: Some(ThemeRole::Body),
spans: None,
hyphenate: None,
common: Common::default(),
}
}
pub fn rich(spans: impl IntoIterator<Item = Span>) -> Self {
let spans: Vec<Span> = spans.into_iter().collect();
let content = spans.iter().map(|s| s.text.as_str()).collect::<Vec<_>>().concat();
let style = spans.first().map(|s| s.style).unwrap_or_default();
Text {
content,
style,
url: None,
anchor: None,
link_to: None,
outline_level: None,
role: None,
spans: Some(Box::new(spans)),
hyphenate: None,
common: Common::default(),
}
}
pub fn url(mut self, url: impl Into<String>) -> Self {
self.url = Some(url.into());
self
}
pub fn anchor(mut self, name: impl Into<String>) -> Self {
self.anchor = Some(name.into());
self
}
pub fn link_to(mut self, anchor: impl Into<String>) -> Self {
self.link_to = Some(anchor.into());
self
}
pub fn outline_level(mut self, level: u8) -> Self {
self.outline_level = Some(level);
self
}
pub fn hyphenate(mut self, lang: HyphenationLanguage) -> Self {
self.hyphenate = Some(lang);
self
}
pub fn role(mut self, role: ThemeRole) -> Self {
self.role = Some(role);
self
}
pub fn size(mut self, size: f32) -> Self {
self.style.size = size;
self.role = None;
self
}
pub fn bold(mut self) -> Self {
self.style.font = FontKey::SANS_BOLD;
self.role = None;
self
}
pub fn italic(mut self) -> Self {
self.style.font = FontKey::SANS_ITALIC;
self.role = None;
self
}
pub fn bold_italic(mut self) -> Self {
self.style.font = FontKey::SANS_BOLD_ITALIC;
self.role = None;
self
}
pub fn font(mut self, font: FontKey) -> Self {
self.style.font = font;
self.role = None;
self
}
pub fn color(mut self, color: Color) -> Self {
self.style.color = color;
self.role = None;
self
}
pub fn align(mut self, align: Align) -> Self {
self.style.align = align;
self
}
pub fn line_height(mut self, line_height: f32) -> Self {
self.style.line_height = line_height;
self.role = None;
self
}
pub fn heading1(self) -> Self {
self.size(24.0).bold().keep_with_next().outline_level(1).role(ThemeRole::Heading1)
}
pub fn heading2(self) -> Self {
self.size(18.0).bold().keep_with_next().outline_level(2).role(ThemeRole::Heading2)
}
pub fn heading3(self) -> Self {
self.size(14.0).bold().keep_with_next().outline_level(3).role(ThemeRole::Heading3)
}
pub fn caption(self) -> Self {
self.size(9.0).color(Color::rgb(0x66, 0x66, 0x66)).role(ThemeRole::Caption)
}
pub fn muted(self) -> Self {
self.color(Color::rgb(0x66, 0x66, 0x66)).role(ThemeRole::Muted)
}
pub fn table_header(self) -> Self {
self.bold().role(ThemeRole::TableHeader)
}
common_builder_methods!();
}
impl From<&str> for Text {
fn from(value: &str) -> Self {
Text::new(value)
}
}
impl From<String> for Text {
fn from(value: String) -> Self {
Text::new(value)
}
}
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(deny_unknown_fields, default)
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, Default)]
pub struct Row {
pub children: Vec<Element>,
pub gap: f32,
pub align: Align,
pub common: Common,
}
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(deny_unknown_fields, default)
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, Default)]
pub struct Column {
pub children: Vec<Element>,
pub gap: f32,
pub align: Align,
pub common: Common,
}
macro_rules! container_impl {
($ty:ident) => {
impl $ty {
pub fn new() -> Self {
Self::default()
}
pub fn child(mut self, child: impl Into<Element>) -> Self {
self.children.push(child.into());
self
}
pub fn children(mut self, children: impl IntoIterator<Item = impl Into<Element>>) -> Self {
self.children.extend(children.into_iter().map(Into::into));
self
}
pub fn gap(mut self, gap: f32) -> Self {
self.gap = gap;
self
}
pub fn align(mut self, align: Align) -> Self {
self.align = align;
self
}
common_builder_methods!();
}
};
}
container_impl!(Row);
container_impl!(Column);
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(deny_unknown_fields))]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Copy, Debug)]
pub struct Spacer {
pub size: f32,
}
impl Spacer {
pub fn new(size: f32) -> Self {
Spacer { size }
}
}
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(deny_unknown_fields, default)
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Debug)]
pub struct Line {
pub thickness: f32,
pub color: Color,
pub common: Common,
}
impl Default for Line {
fn default() -> Self {
Line {
thickness: 1.0,
color: Color::BLACK,
common: Common::default(),
}
}
}
impl Line {
pub fn new() -> Self {
Self::default()
}
pub fn thickness(mut self, thickness: f32) -> Self {
self.thickness = thickness;
self
}
pub fn color(mut self, color: Color) -> Self {
self.color = color;
self
}
common_builder_methods!();
}
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(deny_unknown_fields, default)
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, Default)]
pub struct Rect {
pub common: Common,
}
impl Rect {
pub fn new() -> Self {
Self::default()
}
common_builder_methods!();
}
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(deny_unknown_fields, default)
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Debug)]
pub struct TableOfContents {
pub max_depth: u8,
pub style: TextStyle,
pub leader: char,
#[cfg_attr(feature = "serde", serde(skip))]
pub skip: usize,
pub common: Common,
}
impl Default for TableOfContents {
fn default() -> Self {
TableOfContents {
max_depth: 3,
style: TextStyle::default(),
leader: '.',
skip: 0,
common: Common::default(),
}
}
}
impl TableOfContents {
pub fn new() -> Self {
Self::default()
}
pub fn max_depth(mut self, depth: u8) -> Self {
self.max_depth = depth;
self
}
pub fn leader(mut self, leader: char) -> Self {
self.leader = leader;
self
}
pub fn style(mut self, style: TextStyle) -> Self {
self.style = style;
self
}
common_builder_methods!();
}
macro_rules! element_from {
($ty:ident) => {
impl From<$ty> for Element {
fn from(value: $ty) -> Self {
Element::$ty(value)
}
}
};
}
element_from!(Text);
element_from!(Row);
element_from!(Column);
element_from!(Spacer);
element_from!(Line);
element_from!(Rect);
element_from!(Table);
element_from!(Image);
element_from!(List);
element_from!(TableOfContents);
impl From<&str> for Element {
fn from(value: &str) -> Self {
Element::Text(Text::new(value))
}
}
impl From<String> for Element {
fn from(value: String) -> Self {
Element::Text(Text::new(value))
}
}