mod catppuccin;
pub(crate) mod defaults;
pub mod series;
pub mod theme;
use crate::style::series::Palette;
use crate::style::theme::Theme;
use crate::{Color, ColorU8, ResolveColor, render};
#[derive(Debug, Clone, PartialEq)]
pub struct Style {
theme: Theme,
palette: Palette,
}
impl Default for Style {
fn default() -> Self {
Style::light()
}
}
impl Style {
pub const fn new(theme: Theme, palette: Palette) -> Self {
Style { theme, palette }
}
pub const fn black_white() -> Self {
Style {
theme: Theme::Light,
palette: Palette::Black,
}
}
pub const fn light() -> Self {
Style {
theme: Theme::Light,
palette: Palette::Standard,
}
}
pub const fn dark() -> Self {
Style {
theme: Theme::Dark,
palette: Palette::Pastel,
}
}
pub const fn okabe_ito() -> Self {
Style {
theme: Theme::Light,
palette: Palette::OkabeIto,
}
}
pub const fn tol_bright() -> Self {
Style {
theme: Theme::Light,
palette: Palette::TolBright,
}
}
pub const fn catppuccin_mocha() -> Self {
Style {
theme: Theme::CatppuccinMocha,
palette: Palette::CatppuccinMocha,
}
}
pub const fn catppuccin_macchiato() -> Self {
Style {
theme: Theme::CatppuccinMacchiato,
palette: Palette::CatppuccinMacchiato,
}
}
pub const fn catppuccin_frappe() -> Self {
Style {
theme: Theme::CatppuccinFrappe,
palette: Palette::CatppuccinFrappe,
}
}
pub const fn catppuccin_latte() -> Self {
Style {
theme: Theme::CatppuccinLatte,
palette: Palette::CatppuccinLatte,
}
}
pub const fn theme(&self) -> &Theme {
&self.theme
}
pub const fn palette(&self) -> &Palette {
&self.palette
}
}
impl ResolveColor<theme::Color> for Style {
fn resolve_color(&self, col: &theme::Color) -> ColorU8 {
self.theme().resolve_color(col)
}
}
impl ResolveColor<series::IndexColor> for Style {
fn resolve_color(&self, col: &series::IndexColor) -> ColorU8 {
self.palette.get(*col)
}
}
impl ResolveColor<series::AutoColor> for (&Style, usize) {
fn resolve_color(&self, _col: &series::AutoColor) -> ColorU8 {
self.0.palette.get(series::IndexColor(self.1))
}
}
impl ResolveColor<series::Color> for (&Style, usize) {
fn resolve_color(&self, col: &series::Color) -> ColorU8 {
match col {
series::Color::Auto => self.0.palette.get(series::IndexColor(self.1)),
series::Color::Index(idx) => self.0.palette.get(*idx),
series::Color::Fixed(c) => *c,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Dash(pub Vec<f32>);
impl Default for Dash {
fn default() -> Self {
Dash(vec![5.0, 5.0])
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum LinePattern {
Solid,
Dash(Dash),
Dot,
}
impl Default for LinePattern {
fn default() -> Self {
LinePattern::Solid
}
}
impl From<Dash> for LinePattern {
fn from(dash: Dash) -> Self {
LinePattern::Dash(dash)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Stroke<C: Color> {
pub color: C,
pub width: f32,
pub pattern: LinePattern,
pub opacity: Option<f32>,
}
const DOT_DASH: &[f32] = &[1.0, 1.0];
impl<C: Color> Stroke<C> {
pub fn with_width(self, width: f32) -> Self {
Stroke { width, ..self }
}
pub fn with_opacity(self, opacity: f32) -> Self {
Stroke {
opacity: Some(opacity),
..self
}
}
pub fn with_pattern(self, pattern: LinePattern) -> Self {
Stroke { pattern, ..self }
}
pub fn as_stroke<'a, R>(&'a self, rc: &R) -> render::Stroke<'a>
where
R: ResolveColor<C>,
{
let color = if let Some(opacity) = self.opacity {
self.color.resolve(rc).with_opacity(opacity)
} else {
self.color.resolve(rc)
};
let pattern = match &self.pattern {
LinePattern::Solid => render::LinePattern::Solid,
LinePattern::Dash(Dash(a)) => render::LinePattern::Dash(a.as_slice()),
LinePattern::Dot => render::LinePattern::Dash(DOT_DASH),
};
render::Stroke {
color,
width: self.width,
pattern,
}
}
}
impl<C: Color> From<C> for Stroke<C> {
fn from(color: C) -> Self {
Stroke {
width: 1.0,
color,
pattern: LinePattern::default(),
opacity: None,
}
}
}
impl<C: Color> From<(C, f32)> for Stroke<C> {
fn from((color, width): (C, f32)) -> Self {
Stroke {
color,
width,
pattern: LinePattern::default(),
opacity: None,
}
}
}
impl<C: Color> From<(C, f32, LinePattern)> for Stroke<C> {
fn from((color, width, pattern): (C, f32, LinePattern)) -> Self {
Stroke {
color,
width,
pattern,
opacity: None,
}
}
}
impl<C: Color> From<(C, f32, Dash)> for Stroke<C> {
fn from((color, width, dash): (C, f32, Dash)) -> Self {
Stroke {
color,
width,
pattern: LinePattern::Dash(dash),
opacity: None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Fill<C: Color> {
Solid {
color: C,
opacity: Option<f32>,
},
}
impl<C> Default for Fill<C>
where
C: Color + Default,
{
fn default() -> Self {
Fill::Solid {
color: C::default(),
opacity: None,
}
}
}
impl<C: Color> Fill<C> {
pub fn with_opacity(self, opacity: f32) -> Self {
match self {
Fill::Solid { color, .. } => Fill::Solid {
color,
opacity: Some(opacity),
},
}
}
pub fn as_paint<R>(&self, rc: &R) -> render::Paint
where
R: ResolveColor<C>,
{
match self {
Fill::Solid {
color,
opacity: None,
} => render::Paint::Solid(color.resolve(rc)),
Fill::Solid {
color,
opacity: Some(opacity),
} => render::Paint::Solid(color.resolve(rc).with_opacity(*opacity)),
}
}
}
impl<C: Color> From<C> for Fill<C> {
fn from(color: C) -> Self {
Fill::Solid {
color,
opacity: None,
}
}
}
#[derive(Debug, Clone, Copy, Default)]
pub enum MarkerShape {
#[default]
Circle,
Square,
Diamond,
Cross,
Plus,
TriangleUp,
TriangleDown,
}
#[derive(Debug, Clone, Copy)]
pub struct MarkerSize(pub f32);
impl Default for MarkerSize {
fn default() -> Self {
MarkerSize(defaults::MARKER_SIZE)
}
}
impl From<f32> for MarkerSize {
fn from(size: f32) -> Self {
MarkerSize(size)
}
}
#[derive(Debug, Clone)]
pub struct Marker<C: Color> {
pub size: MarkerSize,
pub shape: MarkerShape,
pub fill: Option<Fill<C>>,
pub stroke: Option<Stroke<C>>,
}
impl<C> Default for Marker<C>
where
C: Color + Default,
{
fn default() -> Self {
Marker {
size: MarkerSize::default(),
shape: MarkerShape::default(),
fill: Some(Fill::default()),
stroke: None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ColorU8;
use crate::style::theme;
#[test]
fn test_color_resolve() {
let style = Style::light();
let theme_line: theme::Stroke = (theme::Color::Theme(theme::Col::LegendBorder), 2.0).into();
let stroke = theme_line.as_stroke(&style);
assert_eq!(stroke.color, ColorU8::from_html(b"#000000"));
let series_line: Stroke<series::IndexColor> = (series::IndexColor(2), 2.0).into();
let stroke = series_line.as_stroke(&style);
assert_eq!(stroke.color, ColorU8::from_html(b"#2ca02c"));
let series_line: Stroke<series::AutoColor> = (series::AutoColor, 2.0).into();
let stroke = series_line.as_stroke(&(&style, 2));
assert_eq!(stroke.color, ColorU8::from_html(b"#2ca02c"));
let fixed_color: Stroke<ColorU8> = (ColorU8::from_html(b"#123456"), 2.0).into();
let stroke = fixed_color.as_stroke(&());
assert_eq!(stroke.color, ColorU8::from_html(b"#123456"));
}
}