use std::collections::HashMap;
use std::sync::Arc;
use super::axis::PerAxis;
use super::cascade::{PerChannel, Sided};
use super::element::{AlignTo, Element, LineElement, RectElement, TextElement};
use super::geom::GeomTheme;
use super::legend::LegendTheme;
use super::length::{Length, Margin};
use super::palette::{Palette, ThemeColor};
use crate::scales::Locale;
pub const DEFAULT_LEGEND_SPACING_PT: f64 = 10.0;
pub const DEFAULT_LEGEND_GAP_PT: f64 = 10.0;
#[derive(Debug, Clone, PartialEq)]
pub struct Theme {
pub palette: Palette,
pub text: TextElement,
pub line: LineElement,
pub rect: RectElement,
pub plot_title: Element<TextElement>,
pub plot_subtitle: Element<TextElement>,
pub plot_caption: Element<TextElement>,
pub plot_text_align_to: AlignTo,
pub plot_background: Element<RectElement>,
pub plot_margin: Margin,
pub plot_padding: Margin,
pub panel_background: Element<RectElement>,
pub panel_border: Element<RectElement>,
pub panel_grid_major: PerChannel<LineElement>,
pub panel_grid_minor: PerChannel<LineElement>,
pub axis: PerAxis,
pub legend: LegendTheme,
pub legend_variants: HashMap<String, LegendTheme>,
pub legend_spacing: Length,
pub legend_gap: Length,
pub strip_background: Sided<RectElement>,
pub strip_text: Sided<TextElement>,
pub strip_padding: Margin,
pub geom: GeomTheme,
pub locale: Locale,
pub rich_text: std::sync::Arc<crate::text::rich::RichTextStyleSheet>,
}
impl Default for Theme {
fn default() -> Self {
use super::axis::axis_concrete_defaults;
use super::element::{
line_concrete_defaults, rect_concrete_defaults, text_concrete_defaults, HAlign,
Rotation, VAlign,
};
use super::font::{FontSpec, FontWeight};
let palette = Palette::default();
let text = text_concrete_defaults();
let line = line_concrete_defaults();
let rect = rect_concrete_defaults();
const HALF_LINE_PT: f64 = super::element::DEFAULT_TEXT_SIZE_PT / 2.0;
let grey92 = ThemeColor::mix(ThemeColor::Paper, ThemeColor::Ink, 0.08);
let grey85 = ThemeColor::mix(ThemeColor::Paper, ThemeColor::Ink, 0.15);
let grey10 = ThemeColor::mix(ThemeColor::Paper, ThemeColor::Ink, 0.9);
Theme {
palette,
text,
line,
rect,
plot_title: Element::Set(TextElement {
size_pt: Some(Length::Rel(1.2)),
font: FontSpec {
weight: Some(FontWeight::BOLD),
..FontSpec::default()
},
align: Some(HAlign::Start),
valign: Some(VAlign::Middle),
margin: Some(Margin::new(
Length::Abs(0.0),
Length::Abs(0.0),
Length::Abs(HALF_LINE_PT),
Length::Abs(0.0),
)),
..TextElement::default()
}),
plot_subtitle: Element::Set(TextElement {
align: Some(HAlign::Start),
valign: Some(VAlign::Middle),
margin: Some(Margin::new(
Length::Abs(0.0),
Length::Abs(0.0),
Length::Abs(HALF_LINE_PT),
Length::Abs(0.0),
)),
..TextElement::default()
}),
plot_caption: Element::Set(TextElement {
size_pt: Some(Length::Rel(0.8)),
align: Some(HAlign::End),
valign: Some(VAlign::Middle),
margin: Some(Margin::new(
Length::Abs(HALF_LINE_PT),
Length::Abs(0.0),
Length::Abs(0.0),
Length::Abs(0.0),
)),
..TextElement::default()
}),
plot_text_align_to: AlignTo::default(),
plot_background: Element::Set(RectElement {
fill: Some(ThemeColor::Paper),
color: None,
linewidth_pt: Some(Length::Abs(0.0)),
..RectElement::default()
}),
plot_margin: Margin::ZERO,
plot_padding: Margin::all(Length::Abs(HALF_LINE_PT)),
panel_background: Element::Set(RectElement {
fill: Some(grey92.clone()),
color: None,
linewidth_pt: Some(Length::Abs(0.0)),
..RectElement::default()
}),
panel_border: Element::Blank,
panel_grid_major: PerChannel::new(LineElement {
color: Some(ThemeColor::Paper),
linewidth_pt: Some(Length::Abs(0.5)),
..LineElement::default()
}),
panel_grid_minor: PerChannel::new(LineElement {
color: Some(ThemeColor::Paper),
linewidth_pt: Some(Length::Abs(0.25)),
..LineElement::default()
}),
axis: PerAxis::new(axis_concrete_defaults()),
legend: LegendTheme::default(),
legend_variants: HashMap::new(),
legend_spacing: Length::Abs(DEFAULT_LEGEND_SPACING_PT),
legend_gap: Length::Abs(DEFAULT_LEGEND_GAP_PT),
strip_background: Sided::new(RectElement {
fill: Some(grey85),
color: None,
linewidth_pt: Some(Length::Abs(0.0)),
..RectElement::default()
}),
strip_text: Sided::new(TextElement {
size_pt: Some(Length::Rel(0.8)),
color: Some(grey10),
angle: Some(Rotation::Along),
..TextElement::default()
}),
strip_padding: Margin::all(Length::Abs(HALF_LINE_PT)),
geom: GeomTheme::default(),
locale: Locale::default(),
rich_text: std::sync::Arc::new(crate::text::rich::RichTextStyleSheet::new()),
}
}
}
impl Theme {
#[inline]
pub fn new() -> Self {
Theme::default()
}
pub fn invert(mut self) -> Self {
std::mem::swap(&mut self.palette.paper, &mut self.palette.ink);
self
}
pub fn with_palette(mut self, palette: Palette) -> Self {
self.palette = palette;
self
}
pub fn with_locale(mut self, locale: Locale) -> Self {
self.locale = locale;
self
}
pub fn with_legend_variant(mut self, name: impl Into<String>, variant: LegendTheme) -> Self {
self.legend_variants.insert(name.into(), variant);
self
}
pub fn merge(&self, part: &ThemePart) -> Theme {
let mut out = self.clone();
part.apply(&mut out);
out
}
pub fn resolved_axis(&self, ch: u8, side: u8) -> crate::plot::theme::ResolvedAxis {
self.axis.resolve_with_root(ch, side, Some(&self.text))
}
pub fn legend_for(&self, variant: Option<&str>) -> &LegendTheme {
variant
.and_then(|name| self.legend_variants.get(name))
.unwrap_or(&self.legend)
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct ThemePart {
pub palette: Option<Palette>,
pub text: Option<TextElement>,
pub line: Option<LineElement>,
pub rect: Option<RectElement>,
pub plot_title: Option<Element<TextElement>>,
pub plot_subtitle: Option<Element<TextElement>>,
pub plot_caption: Option<Element<TextElement>>,
pub plot_text_align_to: Option<AlignTo>,
pub plot_background: Option<Element<RectElement>>,
pub plot_margin: Option<Margin>,
pub plot_padding: Option<Margin>,
pub panel_background: Option<Element<RectElement>>,
pub panel_border: Option<Element<RectElement>>,
pub panel_grid_major: Option<PerChannel<LineElement>>,
pub panel_grid_minor: Option<PerChannel<LineElement>>,
pub axis: Option<PerAxis>,
pub legend: Option<LegendTheme>,
pub legend_variants: HashMap<String, LegendTheme>,
pub legend_spacing: Option<Length>,
pub legend_gap: Option<Length>,
pub strip_background: Option<Sided<RectElement>>,
pub strip_text: Option<Sided<TextElement>>,
pub strip_padding: Option<Margin>,
pub geom: Option<GeomTheme>,
pub locale: Option<Locale>,
pub rich_text: Option<std::sync::Arc<crate::text::rich::RichTextStyleSheet>>,
}
impl ThemePart {
pub fn apply(&self, theme: &mut Theme) {
macro_rules! set_field {
($name:ident) => {
if let Some(ref v) = self.$name {
theme.$name = v.clone();
}
};
}
set_field!(palette);
set_field!(text);
set_field!(line);
set_field!(rect);
set_field!(plot_title);
set_field!(plot_subtitle);
set_field!(plot_caption);
set_field!(plot_text_align_to);
set_field!(plot_background);
set_field!(plot_margin);
set_field!(plot_padding);
set_field!(panel_background);
set_field!(panel_border);
set_field!(panel_grid_major);
set_field!(panel_grid_minor);
set_field!(axis);
set_field!(legend);
set_field!(legend_spacing);
set_field!(legend_gap);
set_field!(strip_background);
set_field!(strip_text);
set_field!(strip_padding);
set_field!(geom);
set_field!(locale);
set_field!(rich_text);
for (k, v) in &self.legend_variants {
theme.legend_variants.insert(k.clone(), v.clone());
}
}
}
pub type SharedTheme = Arc<Theme>;