use std::sync::Arc;
use crate::scales::value::LinetypeStep;
use crate::stroke::{Cap, Join};
use super::font::FontSpec;
use super::length::{Length, Margin};
use super::palette::ThemeColor;
#[derive(Debug, Clone, PartialEq, Default)]
pub enum Element<T> {
#[default]
Inherit,
Blank,
Set(T),
}
impl<T> Element<T> {
#[inline]
pub fn is_blank(&self) -> bool {
matches!(self, Element::Blank)
}
#[inline]
pub fn is_inherit(&self) -> bool {
matches!(self, Element::Inherit)
}
#[inline]
pub fn as_set(&self) -> Option<&T> {
if let Element::Set(v) = self {
Some(v)
} else {
None
}
}
pub fn cascade<'a>(&'a self, parent: Option<&'a T>) -> Option<&'a T> {
match self {
Element::Set(v) => Some(v),
Element::Blank => None,
Element::Inherit => parent,
}
}
}
pub use crate::style_vocab::{HAlign, VAlign};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum AlignTo {
#[default]
Panel,
Plot,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Rotation {
Degrees(f32),
Along,
Across,
}
impl Default for Rotation {
fn default() -> Self {
Rotation::Degrees(0.0)
}
}
impl Rotation {
#[inline]
pub fn resolve(self, baseline_deg: f32) -> f32 {
match self {
Rotation::Degrees(d) => d,
Rotation::Along => baseline_deg,
Rotation::Across => baseline_deg + 90.0,
}
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct TextElement {
pub font: FontSpec,
pub color: Option<ThemeColor>,
pub size_pt: Option<Length>,
pub align: Option<HAlign>,
pub valign: Option<VAlign>,
pub angle: Option<Rotation>,
pub lineheight: Option<Length>,
pub letter_spacing: Option<Length>,
pub underline: Option<bool>,
pub strikethrough: Option<bool>,
pub margin: Option<Margin>,
pub text_stroke: Option<ThemeColor>,
pub text_linewidth_pt: Option<Length>,
pub markdown: Option<bool>,
}
impl TextElement {
pub fn cascade(&self, parent: &Self) -> Self {
Self {
font: parent.font.cascade(&self.font),
color: self.color.clone().or_else(|| parent.color.clone()),
size_pt: self.size_pt.or(parent.size_pt),
align: self.align.or(parent.align),
valign: self.valign.or(parent.valign),
angle: self.angle.or(parent.angle),
lineheight: self.lineheight.or(parent.lineheight),
letter_spacing: self.letter_spacing.or(parent.letter_spacing),
underline: self.underline.or(parent.underline),
strikethrough: self.strikethrough.or(parent.strikethrough),
margin: self.margin.or(parent.margin),
text_stroke: self
.text_stroke
.clone()
.or_else(|| parent.text_stroke.clone()),
text_linewidth_pt: self.text_linewidth_pt.or(parent.text_linewidth_pt),
markdown: self.markdown.or(parent.markdown),
}
}
}
pub const DEFAULT_TEXT_SIZE_PT: f64 = 11.0;
pub const DEFAULT_TEXT_LINEHEIGHT: f64 = 1.2;
fn build_text_concrete_defaults() -> TextElement {
TextElement {
font: FontSpec::default(),
color: Some(ThemeColor::Ink),
size_pt: Some(Length::Abs(DEFAULT_TEXT_SIZE_PT)),
align: Some(HAlign::Center),
valign: Some(VAlign::Middle),
angle: Some(Rotation::default()),
lineheight: Some(Length::Rel(DEFAULT_TEXT_LINEHEIGHT)),
letter_spacing: Some(Length::Abs(0.0)),
underline: Some(false),
strikethrough: Some(false),
margin: Some(Margin::ZERO),
text_stroke: None,
text_linewidth_pt: Some(Length::Abs(DEFAULT_LINEWIDTH_PT)),
markdown: Some(false),
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct LineElement {
pub color: Option<ThemeColor>,
pub linewidth_pt: Option<Length>,
pub linetype: Option<Arc<[LinetypeStep]>>,
pub cap: Option<Cap>,
pub join: Option<Join>,
}
impl LineElement {
pub fn cascade(&self, parent: &Self) -> Self {
Self {
color: self.color.clone().or_else(|| parent.color.clone()),
linewidth_pt: self.linewidth_pt.or(parent.linewidth_pt),
linetype: self.linetype.clone().or_else(|| parent.linetype.clone()),
cap: self.cap.or(parent.cap),
join: self.join.or(parent.join),
}
}
}
pub const DEFAULT_LINEWIDTH_PT: f64 = 1.0;
fn build_line_concrete_defaults() -> LineElement {
LineElement {
color: Some(ThemeColor::Ink),
linewidth_pt: Some(Length::Abs(DEFAULT_LINEWIDTH_PT)),
linetype: Some(Arc::from([])),
cap: Some(Cap::Butt),
join: Some(Join::Miter),
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct RectElement {
pub fill: Option<ThemeColor>,
pub color: Option<ThemeColor>,
pub linewidth_pt: Option<Length>,
pub linetype: Option<Arc<[LinetypeStep]>>,
pub corner_radius: Option<Length>,
}
impl RectElement {
pub fn cascade(&self, parent: &Self) -> Self {
Self {
fill: self.fill.clone().or_else(|| parent.fill.clone()),
color: self.color.clone().or_else(|| parent.color.clone()),
linewidth_pt: self.linewidth_pt.or(parent.linewidth_pt),
linetype: self.linetype.clone().or_else(|| parent.linetype.clone()),
corner_radius: self.corner_radius.or(parent.corner_radius),
}
}
}
fn build_rect_concrete_defaults() -> RectElement {
RectElement {
fill: Some(ThemeColor::Paper),
color: Some(ThemeColor::Ink),
linewidth_pt: Some(Length::Abs(1.0)),
linetype: Some(Arc::from([])),
corner_radius: Some(Length::Abs(0.0)),
}
}
static TEXT_CONCRETE_DEFAULTS: std::sync::LazyLock<TextElement> =
std::sync::LazyLock::new(build_text_concrete_defaults);
pub fn text_concrete_defaults() -> TextElement {
TEXT_CONCRETE_DEFAULTS.clone()
}
static LINE_CONCRETE_DEFAULTS: std::sync::LazyLock<LineElement> =
std::sync::LazyLock::new(build_line_concrete_defaults);
pub fn line_concrete_defaults() -> LineElement {
LINE_CONCRETE_DEFAULTS.clone()
}
static RECT_CONCRETE_DEFAULTS: std::sync::LazyLock<RectElement> =
std::sync::LazyLock::new(build_rect_concrete_defaults);
pub fn rect_concrete_defaults() -> RectElement {
RECT_CONCRETE_DEFAULTS.clone()
}