use crate::geometry::Color;
use crate::render::{Gradient, Paint};
use crate::spec::Align;
use crate::theme::Theme;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Role {
Bg,
Surface,
SurfaceAlt,
Border,
Divider,
Track,
Text,
TextMuted,
TextDisabled,
Placeholder,
Accent,
AccentHover,
AccentActive,
OnAccent,
Danger,
AccordionBorder,
AccordionHeaderBg,
}
impl Role {
pub fn resolve(self, t: &Theme) -> Color {
let p = &t.palette;
match self {
Role::Bg => p.bg,
Role::Surface => p.surface,
Role::SurfaceAlt => p.surface_alt,
Role::Border => p.border,
Role::Divider => p.divider,
Role::Track => p.track,
Role::Text => p.text,
Role::TextMuted => p.text_muted,
Role::TextDisabled => p.text_disabled,
Role::Placeholder => p.placeholder,
Role::Accent => p.accent,
Role::AccentHover => p.accent_hover,
Role::AccentActive => p.accent_active,
Role::OnAccent => p.on_accent,
Role::Danger => p.danger,
Role::AccordionBorder => t.accordion.border(p),
Role::AccordionHeaderBg => t.accordion.header_bg(p),
}
}
}
#[derive(Debug, Clone)]
pub enum Brush {
Solid(Color),
Gradient(Gradient),
Role(Role),
}
impl Brush {
pub fn resolve_paint(&self, t: &Theme) -> Paint {
match self {
Brush::Solid(c) => Paint::fill(*c),
Brush::Gradient(g) => Paint::gradient(g.clone()),
Brush::Role(r) => Paint::fill(r.resolve(t)),
}
}
pub fn solid_color(&self, t: &Theme) -> Color {
self.resolve_paint(t).color
}
}
#[derive(Debug, Clone, Copy)]
pub struct Shadow {
pub dx: f32,
pub dy: f32,
pub blur: f32,
pub spread: f32,
pub color: Color,
}
impl Shadow {
pub fn new(dx: f32, dy: f32, blur: f32, color: Color) -> Self {
Self {
dx,
dy,
blur,
spread: 0.0,
color,
}
}
}
#[derive(Debug, Clone)]
pub struct Style {
pub bg: Option<Brush>,
pub border: Option<(Brush, i32)>,
pub corner_radius: f32,
pub fg: Color,
pub fg_role: Option<Role>,
pub font_size: f32,
pub font_weight: u16,
pub font_family: Option<String>,
pub text_align: Align,
pub shadow: Option<Shadow>,
pub opacity: f32,
}
impl Default for Style {
fn default() -> Self {
Self {
bg: None,
border: None,
corner_radius: 0.0,
fg: Color::hex(0x1A1A1A),
fg_role: Some(Role::Text),
font_size: 14.0,
font_weight: crate::text::WEIGHT_NORMAL,
font_family: None,
text_align: Align::Start,
shadow: None,
opacity: 1.0,
}
}
}
impl Style {
pub fn resolved_fg(&self, t: &Theme) -> Color {
match self.fg_role {
Some(r) => r.resolve(t),
None => self.fg,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::geometry::Color;
use crate::theme::Theme;
#[test]
fn role_resolves_against_theme_palette() {
let light = Theme::default();
assert_eq!(Role::Divider.resolve(&light), light.palette.divider);
assert_eq!(Role::Accent.resolve(&light), light.palette.accent);
assert_eq!(Role::Text.resolve(&light), light.palette.text);
}
#[test]
fn brush_solid_resolves_to_fill_paint() {
let t = Theme::default();
let p = Brush::Solid(Color::hex(0x123456)).resolve_paint(&t);
assert_eq!(p.color, Color::hex(0x123456));
assert!(p.gradient.is_none());
}
#[test]
fn brush_role_tracks_theme() {
let t = Theme::default();
let p = Brush::Role(Role::Surface).resolve_paint(&t);
assert_eq!(p.color, t.palette.surface);
}
#[test]
fn resolved_fg_prefers_role() {
let t = Theme::default();
let s = Style {
fg: Color::hex(0x000000),
fg_role: Some(Role::TextMuted),
..Style::default()
};
assert_eq!(s.resolved_fg(&t), t.palette.text_muted);
let s2 = Style {
fg: Color::hex(0x010203),
fg_role: None,
..Style::default()
};
assert_eq!(s2.resolved_fg(&t), Color::hex(0x010203));
}
}