use lieui_geom::Color;
pub use lieui_text::{FontWeight, TextAlign, TextSpec};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ShadowSpec {
pub offset_x: f32,
pub offset_y: f32,
pub blur: f32,
pub spread: f32,
pub color: Color,
}
impl ShadowSpec {
pub const fn new(offset_x: f32, offset_y: f32, blur: f32, spread: f32, color: Color) -> Self {
Self {
offset_x,
offset_y,
blur,
spread,
color,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct PaintStyle {
pub background_color: Option<Color>,
pub hover_background: Option<Color>,
pub pressed_background: Option<Color>,
pub border_radius: f32,
pub border_color: Option<Color>,
pub border_width: f32,
pub clip_content: bool,
pub opacity: f32,
pub shadow: Option<ShadowSpec>,
}
impl Default for PaintStyle {
fn default() -> Self {
Self {
background_color: None,
hover_background: None,
pressed_background: None,
border_radius: 0.0,
border_color: None,
border_width: 0.0,
clip_content: false,
opacity: 1.0,
shadow: None,
}
}
}
impl PaintStyle {
pub fn new() -> Self {
Self::default()
}
pub fn background(mut self, c: Color) -> Self {
self.background_color = Some(c);
self
}
pub fn hover_background(mut self, c: Color) -> Self {
self.hover_background = Some(c);
self
}
pub fn pressed_background(mut self, c: Color) -> Self {
self.pressed_background = Some(c);
self
}
pub fn radius(mut self, r: f32) -> Self {
self.border_radius = r;
self
}
pub fn border(mut self, width: f32, color: Color) -> Self {
self.border_width = width;
self.border_color = Some(color);
self
}
pub fn opacity(mut self, o: f32) -> Self {
self.opacity = o.clamp(0.0, 1.0);
self
}
pub fn clip(mut self, v: bool) -> Self {
self.clip_content = v;
self
}
pub fn shadow(mut self, s: ShadowSpec) -> Self {
self.shadow = Some(s);
self
}
pub fn is_interactive(&self) -> bool {
self.hover_background.is_some() || self.pressed_background.is_some()
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct TextStyle {
pub spec: TextSpec,
pub color: Color,
pub hover_color: Option<Color>,
pub pressed_color: Option<Color>,
}
impl Default for TextStyle {
fn default() -> Self {
Self {
spec: TextSpec::default(),
color: Color::BLACK,
hover_color: None,
pressed_color: None,
}
}
}
impl TextStyle {
pub fn new() -> Self {
Self::default()
}
pub fn font_size(mut self, s: f64) -> Self {
self.spec.font_size = s;
self
}
pub fn font_family(mut self, f: impl Into<String>) -> Self {
self.spec.font_family = f.into();
self
}
pub fn font_weight(mut self, w: impl Into<FontWeight>) -> Self {
self.spec.font_weight = w.into();
self
}
pub fn line_height(mut self, h: f64) -> Self {
self.spec.line_height = Some(h);
self
}
pub fn max_width(mut self, w: f64) -> Self {
self.spec.max_width = Some(w);
self
}
pub fn text_align(mut self, a: TextAlign) -> Self {
self.spec.text_align = a;
self
}
pub fn wrap(mut self, v: bool) -> Self {
self.spec.wrap = v;
self
}
pub fn color(mut self, c: Color) -> Self {
self.color = c;
self
}
pub fn hover_color(mut self, c: Color) -> Self {
self.hover_color = Some(c);
self
}
pub fn pressed_color(mut self, c: Color) -> Self {
self.pressed_color = Some(c);
self
}
pub fn is_interactive(&self) -> bool {
self.hover_color.is_some() || self.pressed_color.is_some()
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub enum ImageFit {
#[default]
Fill,
Contain,
Cover,
None,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ImageStyle {
pub width: u32,
pub height: u32,
pub opacity: f32,
pub fit: ImageFit,
pub border_radius: f32,
}
impl Default for ImageStyle {
fn default() -> Self {
Self {
width: 0,
height: 0,
opacity: 1.0,
fit: ImageFit::Fill,
border_radius: 0.0,
}
}
}
impl ImageStyle {
pub fn new() -> Self {
Self::default()
}
pub fn size(mut self, w: u32, h: u32) -> Self {
self.width = w;
self.height = h;
self
}
pub fn opacity(mut self, o: f32) -> Self {
self.opacity = o.clamp(0.0, 1.0);
self
}
pub fn fit(mut self, f: ImageFit) -> Self {
self.fit = f;
self
}
pub fn radius(mut self, r: f32) -> Self {
self.border_radius = r;
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn paint_default_is_opaque_and_unstyled() {
let p = PaintStyle::default();
assert_eq!(p.opacity, 1.0);
assert!(p.background_color.is_none());
assert!(!p.clip_content);
assert!(!p.is_interactive());
}
#[test]
fn paint_builder_chains_and_clamps() {
let p = PaintStyle::new()
.background(Color::WHITE)
.hover_background(Color::RED)
.radius(6.0)
.border(1.0, Color::BLACK)
.opacity(3.0)
.clip(true);
assert_eq!(p.background_color, Some(Color::WHITE));
assert_eq!(p.border_radius, 6.0);
assert_eq!(p.opacity, 1.0); assert!(p.clip_content);
assert!(p.is_interactive()); }
#[test]
fn text_style_forwards_to_spec() {
let t = TextStyle::new()
.font_size(48.0)
.font_family("serif")
.text_align(TextAlign::Center)
.wrap(false)
.color(Color::RED);
assert_eq!(t.spec.font_size, 48.0);
assert_eq!(t.spec.font_family, "serif");
assert_eq!(t.spec.text_align, TextAlign::Center);
assert!(!t.spec.wrap);
assert_eq!(t.color, Color::RED);
assert_eq!(t.spec, TextSpec::new(48.0).font_family("serif").text_align(TextAlign::Center).wrap(false));
}
#[test]
fn text_style_default_matches_spec_default() {
let t = TextStyle::default();
assert_eq!(t.spec, TextSpec::default());
assert_eq!(t.color, Color::BLACK);
assert!(!t.is_interactive());
}
#[test]
fn image_style_defaults() {
let i = ImageStyle::default();
assert_eq!(i.opacity, 1.0);
assert_eq!(i.fit, ImageFit::Fill);
assert_eq!(ImageStyle::new().size(10, 20).fit(ImageFit::Cover).width, 10);
}
}