use std::rc::Rc;
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Edges<T> {
pub top: T,
pub right: T,
pub bottom: T,
pub left: T,
}
impl<T: Clone> Edges<T> {
pub fn all(v: T) -> Self {
Edges {
top: v.clone(),
right: v.clone(),
bottom: v.clone(),
left: v,
}
}
}
impl Edges<f32> {
pub fn any_positive(&self) -> bool {
self.top > 0.0 || self.right > 0.0 || self.bottom > 0.0 || self.left > 0.0
}
pub fn is_uniform(&self) -> bool {
self.top == self.right && self.right == self.bottom && self.bottom == self.left
}
}
#[derive(Clone, Debug, Default, PartialEq)]
pub enum Dimension {
#[default]
Auto,
Points(f32),
Percent(f32),
}
#[derive(Clone, Debug, Default, PartialEq)]
pub enum Align {
#[default]
Stretch,
Start,
End,
Center,
Baseline,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub enum Justify {
#[default]
Start,
End,
Center,
SpaceBetween,
SpaceAround,
SpaceEvenly,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub enum Overflow {
#[default]
Visible,
Hidden,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct CornerRadii {
pub top_left: f32,
pub top_right: f32,
pub bottom_right: f32,
pub bottom_left: f32,
}
impl CornerRadii {
pub fn all(r: f32) -> Self {
CornerRadii {
top_left: r,
top_right: r,
bottom_right: r,
bottom_left: r,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct StyleProps {
pub width: Option<Dimension>,
pub height: Option<Dimension>,
pub padding: Option<Edges<f32>>,
pub margin: Option<Edges<f32>>,
pub inset: Option<Edges<f32>>,
pub gap: Option<f32>,
pub column_gap: Option<f32>,
pub row_gap: Option<f32>,
pub opacity: f32,
pub flex_grow: Option<f32>,
pub flex_shrink: Option<f32>,
pub align_items: Option<Align>,
pub justify_content: Option<Justify>,
pub overflow: Overflow,
pub z_index: i32,
pub align_self: Option<Align>,
pub focusable: bool,
pub cursor: crate::element::events::Cursor,
pub position_absolute: bool,
}
impl Default for StyleProps {
fn default() -> Self {
Self {
width: None,
height: None,
padding: None,
margin: None,
inset: None,
gap: None,
column_gap: None,
row_gap: None,
opacity: 1.0,
flex_grow: None,
flex_shrink: None,
align_items: None,
justify_content: None,
overflow: Overflow::Visible,
z_index: 0,
align_self: None,
focusable: false,
cursor: crate::element::events::Cursor::default(),
position_absolute: false,
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Color {
pub r: f32,
pub g: f32,
pub b: f32,
pub a: f32,
}
impl Color {
pub const fn rgb8(r: u8, g: u8, b: u8) -> Self {
Color {
r: r as f32 / 255.0,
g: g as f32 / 255.0,
b: b as f32 / 255.0,
a: 1.0,
}
}
pub fn with_alpha(mut self, a: f32) -> Self {
self.a = a;
self
}
pub fn to_rgb8(self) -> (u8, u8, u8) {
(
(self.r.clamp(0.0, 1.0) * 255.0).round() as u8,
(self.g.clamp(0.0, 1.0) * 255.0).round() as u8,
(self.b.clamp(0.0, 1.0) * 255.0).round() as u8,
)
}
}
#[derive(Clone)]
pub enum ColorSource {
Static(Color),
Dynamic(Rc<dyn Fn() -> Color>),
}
impl ColorSource {
pub fn resolve(&self) -> Color {
match self {
Self::Static(c) => *c,
Self::Dynamic(f) => f(),
}
}
}
impl From<Color> for ColorSource {
fn from(c: Color) -> Self {
ColorSource::Static(c)
}
}
impl<F: Fn() -> Color + 'static> From<F> for ColorSource {
fn from(f: F) -> Self {
ColorSource::Dynamic(Rc::new(f))
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct LinearGradient {
pub start: (f32, f32),
pub end: (f32, f32),
pub start_color: Color,
pub end_color: Color,
}
impl LinearGradient {
pub fn new(start: (f32, f32), end: (f32, f32), start_color: Color, end_color: Color) -> Self {
Self {
start,
end,
start_color,
end_color,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct BoxShadow {
pub color: Color,
pub offset_x: f32,
pub offset_y: f32,
pub blur_radius: f32,
}
impl BoxShadow {
pub fn new(color: Color, offset_x: f32, offset_y: f32, blur_radius: f32) -> Self {
Self {
color,
offset_x,
offset_y,
blur_radius,
}
}
}
#[derive(Clone, Default)]
pub struct PaintProps {
pub background: Option<ColorSource>,
pub background_gradient: Option<LinearGradient>,
pub border_color: Edges<Option<ColorSource>>,
pub border_width: Edges<f32>,
pub radius: CornerRadii,
pub box_shadow: Option<BoxShadow>,
pub image: Option<crate::asset::ImageHandle>,
pub scroll_track_color: Option<ColorSource>,
pub scroll_thumb_color: Option<ColorSource>,
pub focus_ring_color: Option<ColorSource>,
}
impl std::fmt::Debug for PaintProps {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PaintProps")
.field("background", &self.background.as_ref().map(|c| c.resolve()))
.field("background_gradient", &self.background_gradient)
.field("border_color", &resolve_color_edges(&self.border_color))
.field("border_width", &self.border_width)
.field("radius", &self.radius)
.field("box_shadow", &self.box_shadow)
.field("image", &self.image)
.field(
"scroll_track_color",
&self.scroll_track_color.as_ref().map(|c| c.resolve()),
)
.field(
"scroll_thumb_color",
&self.scroll_thumb_color.as_ref().map(|c| c.resolve()),
)
.field(
"focus_ring_color",
&self.focus_ring_color.as_ref().map(|c| c.resolve()),
)
.finish()
}
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct PaintData {
pub background: Option<Color>,
pub background_gradient: Option<LinearGradient>,
pub border_color: Edges<Option<Color>>,
pub border_width: Edges<f32>,
pub radius: CornerRadii,
pub box_shadow: Option<BoxShadow>,
pub image: Option<crate::asset::ImageHandle>,
pub scroll_track_color: Option<Color>,
pub scroll_thumb_color: Option<Color>,
pub focus_ring_color: Option<Color>,
}
impl PaintProps {
pub fn resolve(&self) -> PaintData {
PaintData {
background: self.background.as_ref().map(|c| c.resolve()),
background_gradient: self.background_gradient,
border_color: resolve_color_edges(&self.border_color),
border_width: self.border_width,
radius: self.radius.clone(),
box_shadow: self.box_shadow,
image: self.image.clone(),
scroll_track_color: self.scroll_track_color.as_ref().map(|c| c.resolve()),
scroll_thumb_color: self.scroll_thumb_color.as_ref().map(|c| c.resolve()),
focus_ring_color: self.focus_ring_color.as_ref().map(|c| c.resolve()),
}
}
}
fn resolve_color_edges(edges: &Edges<Option<ColorSource>>) -> Edges<Option<Color>> {
Edges {
top: edges.top.as_ref().map(ColorSource::resolve),
right: edges.right.as_ref().map(ColorSource::resolve),
bottom: edges.bottom.as_ref().map(ColorSource::resolve),
left: edges.left.as_ref().map(ColorSource::resolve),
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct TextStyle {
pub font_size: f32,
pub font_weight: u16,
pub font_family: String,
pub line_height: f32,
pub letter_spacing: f32,
pub color: Option<Color>,
}
impl Default for TextStyle {
fn default() -> Self {
let typography = crate::theme::current_theme().typography;
TextStyle {
font_size: typography.font_size_md,
font_weight: 400,
font_family: typography.font_family,
line_height: typography.line_height,
letter_spacing: typography.letter_spacing,
color: None,
}
}
}
pub fn default_text_color() -> Color {
crate::theme::current_theme().colors.foreground
}
pub fn resolved_text_color(style: &TextStyle) -> Color {
style.color.unwrap_or_else(default_text_color)
}
#[cfg(test)]
mod tests {
use super::{resolved_text_color, Color, Overflow, StyleProps, TextStyle};
use crate::theme::{set_active_theme, Theme};
#[test]
fn resolved_text_color_prefers_explicit_style() {
let style = TextStyle {
color: Some(Color::rgb8(255, 128, 64)),
..TextStyle::default()
};
assert_eq!(resolved_text_color(&style), Color::rgb8(255, 128, 64));
}
#[test]
fn resolved_text_color_falls_back_to_theme_foreground() {
let original = crate::theme::current_theme();
let mut theme = Theme::default_dark();
theme.colors.foreground = Color::rgb8(200, 210, 220);
set_active_theme(theme);
let style = TextStyle::default();
assert_eq!(resolved_text_color(&style), Color::rgb8(200, 210, 220));
set_active_theme(original);
}
#[test]
fn style_props_default_overflow_is_visible() {
assert_eq!(StyleProps::default().overflow, Overflow::Visible);
}
#[test]
fn style_props_default_z_index_is_zero() {
assert_eq!(StyleProps::default().z_index, 0);
}
}