use crate::object::ObjectNode;
use crate::style::Style;
use crate::style_cascade::{Part, Selector, StylePatch};
use crate::widget::Color;
pub trait Theme {
fn apply(&self, style: &mut Style);
}
pub struct LightTheme;
impl Theme for LightTheme {
fn apply(&self, style: &mut Style) {
style.bg_color = Color(255, 255, 255, 255);
style.border_color = Color(0, 0, 0, 255);
}
}
pub struct DarkTheme;
impl Theme for DarkTheme {
fn apply(&self, style: &mut Style) {
style.bg_color = Color(0, 0, 0, 255);
style.border_color = Color(255, 255, 255, 255);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WidgetClass {
Screen,
Button,
Label,
Container,
Slider,
Switch,
Checkbox,
List,
}
pub trait LparTheme {
fn apply_to_node(&self, node: &mut ObjectNode, class: WidgetClass);
}
pub struct DefaultTheme;
const DEFAULT_PRIMARY: Color = Color(98, 0, 238, 255);
const DEFAULT_SURFACE: Color = Color(255, 255, 255, 255);
const TRANSPARENT: Color = Color(0, 0, 0, 0);
const DEFAULT_ON_SURFACE: Color = Color(33, 33, 33, 255);
const BUTTON_RADIUS: u8 = 6;
const BUTTON_BORDER_WIDTH: u8 = 1;
impl LparTheme for DefaultTheme {
fn apply_to_node(&self, node: &mut ObjectNode, class: WidgetClass) {
let sel = Selector::part(Part::MAIN);
node.clear_theme_styles();
match class {
WidgetClass::Button => {
node.add_theme_style(
StylePatch {
bg_color: Some(DEFAULT_PRIMARY),
border_color: Some(DEFAULT_PRIMARY),
border_width: Some(BUTTON_BORDER_WIDTH),
radius: Some(BUTTON_RADIUS),
..StylePatch::new()
},
sel,
);
}
WidgetClass::Label => {
node.add_theme_style(
StylePatch {
bg_color: Some(TRANSPARENT),
alpha: Some(255),
border_width: Some(0),
radius: Some(0),
..StylePatch::new()
},
sel,
);
}
WidgetClass::Container | WidgetClass::Screen => {
node.add_theme_style(
StylePatch {
bg_color: Some(DEFAULT_SURFACE),
border_width: Some(0),
radius: Some(0),
alpha: Some(255),
..StylePatch::new()
},
sel,
);
}
WidgetClass::Slider | WidgetClass::Switch | WidgetClass::Checkbox => {
node.add_theme_style(
StylePatch {
bg_color: Some(DEFAULT_PRIMARY),
alpha: Some(255),
border_width: Some(0),
radius: Some(4),
..StylePatch::new()
},
sel,
);
}
WidgetClass::List => {
node.add_theme_style(
StylePatch {
bg_color: Some(DEFAULT_SURFACE),
border_color: Some(DEFAULT_ON_SURFACE),
border_width: Some(1),
radius: Some(2),
alpha: Some(255),
..StylePatch::new()
},
sel,
);
}
}
}
}
#[cfg(test)]
mod tests {
use alloc::rc::Rc;
use core::cell::RefCell;
use super::*;
use crate::object::ObjectNode;
use crate::style_cascade::{InheritedContext, Part, resolve};
use crate::widget::{Rect, Widget};
struct Dummy;
impl Widget for Dummy {
fn bounds(&self) -> Rect {
Rect {
x: 0,
y: 0,
width: 10,
height: 10,
}
}
fn draw(&self, _r: &mut dyn crate::renderer::Renderer) {}
fn handle_event(&mut self, _e: &crate::event::Event) -> bool {
false
}
}
fn make_node() -> ObjectNode {
ObjectNode::new(Rc::new(RefCell::new(Dummy)))
}
#[test]
fn light_theme_sets_bg_white() {
let mut style = Style::default();
LightTheme.apply(&mut style);
assert_eq!(style.bg_color, Color(255, 255, 255, 255));
}
#[test]
fn dark_theme_sets_bg_black() {
let mut style = Style::default();
DarkTheme.apply(&mut style);
assert_eq!(style.bg_color, Color(0, 0, 0, 255));
}
#[test]
fn default_theme_applies_to_button() {
let mut node = make_node();
DefaultTheme.apply_to_node(&mut node, WidgetClass::Button);
let (style, _) = resolve(
node.style.as_deref(),
node.meta().states(),
Part::MAIN,
&InheritedContext::EMPTY,
);
assert_eq!(
style.bg_color, DEFAULT_PRIMARY,
"button bg should be primary color"
);
assert_eq!(style.radius, BUTTON_RADIUS, "button radius from theme");
assert_eq!(
style.border_width, BUTTON_BORDER_WIDTH,
"button border width from theme"
);
}
#[test]
fn local_override_wins_over_theme() {
let mut node = make_node();
DefaultTheme.apply_to_node(&mut node, WidgetClass::Button);
node.add_local_style(
StylePatch {
bg_color: Some(Color(255, 0, 0, 255)),
..StylePatch::new()
},
Selector::part(Part::MAIN),
);
let (style, _) = resolve(
node.style.as_deref(),
node.meta().states(),
Part::MAIN,
&InheritedContext::EMPTY,
);
assert_eq!(
style.bg_color,
Color(255, 0, 0, 255),
"local override must win over DefaultTheme"
);
}
#[test]
fn local_override_wins_regardless_of_apply_order() {
let mut node = make_node();
node.add_local_style(
StylePatch {
bg_color: Some(Color(255, 0, 0, 255)),
..StylePatch::new()
},
Selector::part(Part::MAIN),
);
DefaultTheme.apply_to_node(&mut node, WidgetClass::Button);
let (style, _) = resolve(
node.style.as_deref(),
node.meta().states(),
Part::MAIN,
&InheritedContext::EMPTY,
);
assert_eq!(
style.bg_color,
Color(255, 0, 0, 255),
"local override must win even when the theme is applied afterward"
);
}
#[test]
fn default_theme_container_sets_surface_bg() {
let mut node = make_node();
DefaultTheme.apply_to_node(&mut node, WidgetClass::Container);
let (style, _) = resolve(
node.style.as_deref(),
node.meta().states(),
Part::MAIN,
&InheritedContext::EMPTY,
);
assert_eq!(
style.bg_color, DEFAULT_SURFACE,
"container bg should be surface white"
);
}
}