use std::collections::HashMap;
use serde::{ Deserialize, Serialize };
use crate::types::Color;
use super::super::paint::{ ColorStop, GradientSpace, LinearGradient, Paint, RadialGradient };
use super::super::shadow::{ BlendMode, InsetShadow, Shadow, ShadowsRef };
use super::super::slots::{ Metadata, Slot };
use super::super::surface::Surface;
use super::super::text_style::
{
FontRef, FontStyle, LineHeight, TextDecoration, TextStyle, TextTransform,
};
use super::super::WallpaperFit;
use super::color_serde;
#[ derive( Debug, Clone, Copy, Serialize, Deserialize ) ]
#[ serde( rename_all = "kebab-case" ) ]
pub( super ) enum RawGradientSpace
{
Srgb,
LinearRgb,
Oklab,
}
impl Default for RawGradientSpace { fn default() -> Self { RawGradientSpace::LinearRgb } }
impl From<RawGradientSpace> for GradientSpace
{
fn from( r: RawGradientSpace ) -> Self
{
match r
{
RawGradientSpace::Srgb => GradientSpace::Srgb,
RawGradientSpace::LinearRgb => GradientSpace::LinearRgb,
RawGradientSpace::Oklab => GradientSpace::Oklab,
}
}
}
#[ derive( Debug, Clone, Copy, Serialize, Deserialize ) ]
#[ serde( rename_all = "kebab-case" ) ]
pub( super ) enum RawBlendMode
{
Normal,
PlusLighter,
Overlay,
Multiply,
Screen,
}
impl Default for RawBlendMode { fn default() -> Self { RawBlendMode::Normal } }
impl From<RawBlendMode> for BlendMode
{
fn from( r: RawBlendMode ) -> Self
{
match r
{
RawBlendMode::Normal => BlendMode::Normal,
RawBlendMode::PlusLighter => BlendMode::PlusLighter,
RawBlendMode::Overlay => BlendMode::Overlay,
RawBlendMode::Multiply => BlendMode::Multiply,
RawBlendMode::Screen => BlendMode::Screen,
}
}
}
#[ derive( Debug, Clone, Copy, Serialize, Deserialize ) ]
#[ serde( rename_all = "kebab-case" ) ]
pub( super ) enum RawFontStyle { Normal, Italic }
impl Default for RawFontStyle { fn default() -> Self { RawFontStyle::Normal } }
impl From<RawFontStyle> for FontStyle
{
fn from( r: RawFontStyle ) -> Self
{
match r
{
RawFontStyle::Normal => FontStyle::Normal,
RawFontStyle::Italic => FontStyle::Italic,
}
}
}
#[ derive( Debug, Clone, Copy, Serialize, Deserialize ) ]
#[ serde( rename_all = "kebab-case" ) ]
pub( super ) enum RawTextTransform { None, Uppercase, Lowercase, Capitalize }
impl Default for RawTextTransform { fn default() -> Self { RawTextTransform::None } }
impl From<RawTextTransform> for TextTransform
{
fn from( r: RawTextTransform ) -> Self
{
match r
{
RawTextTransform::None => TextTransform::None,
RawTextTransform::Uppercase => TextTransform::Uppercase,
RawTextTransform::Lowercase => TextTransform::Lowercase,
RawTextTransform::Capitalize => TextTransform::Capitalize,
}
}
}
#[ derive( Debug, Clone, Copy, Serialize, Deserialize ) ]
#[ serde( rename_all = "kebab-case" ) ]
pub( super ) enum RawTextDecoration { None, Underline, Strikethrough }
impl Default for RawTextDecoration { fn default() -> Self { RawTextDecoration::None } }
impl From<RawTextDecoration> for TextDecoration
{
fn from( r: RawTextDecoration ) -> Self
{
match r
{
RawTextDecoration::None => TextDecoration::None,
RawTextDecoration::Underline => TextDecoration::Underline,
RawTextDecoration::Strikethrough => TextDecoration::Strikethrough,
}
}
}
#[ derive( Debug, Clone, Default, Serialize, Deserialize ) ]
pub( super ) struct RawMetadata
{
#[ serde( default, skip_serializing_if = "Option::is_none" ) ]
pub( super ) semantic: Option<String>,
#[ serde( default, skip_serializing_if = "Option::is_none" ) ]
pub( super ) fluent: Option<String>,
#[ serde( default, skip_serializing_if = "Option::is_none" ) ]
pub( super ) usage: Option<String>,
#[ serde( default, skip_serializing_if = "Option::is_none" ) ]
pub( super ) note: Option<String>,
}
impl From<RawMetadata> for Metadata
{
fn from( r: RawMetadata ) -> Self
{
Metadata { semantic: r.semantic, fluent: r.fluent, usage: r.usage, note: r.note }
}
}
#[ derive( Debug, Clone, Serialize, Deserialize ) ]
#[ serde( deny_unknown_fields ) ]
pub( super ) struct RawColorStop
{
#[ serde( alias = "position" ) ]
pub( super ) pos: f32,
#[ serde( with = "color_serde" ) ]
pub( super ) color: Color,
}
impl From<RawColorStop> for ColorStop
{
fn from( r: RawColorStop ) -> Self { ColorStop { position: r.pos, color: r.color } }
}
#[ derive( Debug, Clone, Serialize, Deserialize ) ]
#[ serde( tag = "type", rename_all = "kebab-case", deny_unknown_fields ) ]
pub( super ) enum RawPaint
{
Solid
{
#[ serde( with = "color_serde" ) ]
color: Color,
},
Linear
{
angle_deg: f32,
#[ serde( default ) ]
space: RawGradientSpace,
stops: Vec<RawColorStop>,
},
Radial
{
center: [f32; 2],
radius: f32,
#[ serde( default ) ]
space: RawGradientSpace,
stops: Vec<RawColorStop>,
},
}
impl From<RawPaint> for Paint
{
fn from( r: RawPaint ) -> Self
{
match r
{
RawPaint::Solid { color } => Paint::Solid( color ),
RawPaint::Linear { angle_deg, space, stops } => Paint::Linear( LinearGradient
{
angle_deg,
space: space.into(),
stops: collect_capped_stops( stops ),
}),
RawPaint::Radial { center, radius, space, stops } => Paint::Radial( RadialGradient
{
center,
radius,
space: space.into(),
stops: collect_capped_stops( stops ),
}),
}
}
}
pub( super ) const MAX_GRADIENT_STOPS: usize = 64;
pub( super ) fn collect_capped_stops( raw: Vec<RawColorStop> ) -> Vec<ColorStop>
{
if raw.len() > MAX_GRADIENT_STOPS
{
eprintln!(
"[ltk theme] gradient declares {} stops, truncating to {}",
raw.len(), MAX_GRADIENT_STOPS,
);
raw.into_iter().take( MAX_GRADIENT_STOPS ).map( Into::into ).collect()
}
else
{
raw.into_iter().map( Into::into ).collect()
}
}
#[ derive( Debug, Clone, Serialize, Deserialize ) ]
#[ serde( deny_unknown_fields ) ]
pub( super ) struct RawShadow
{
pub( super ) offset: [f32; 2],
pub( super ) blur: f32,
#[ serde( default ) ]
pub( super ) spread: f32,
#[ serde( with = "color_serde" ) ]
pub( super ) color: Color,
#[ serde( default ) ]
pub( super ) blend: RawBlendMode,
}
impl From<RawShadow> for Shadow
{
fn from( r: RawShadow ) -> Self
{
Shadow { offset: r.offset, blur: r.blur, spread: r.spread, color: r.color, blend: r.blend.into() }
}
}
impl From<RawShadow> for InsetShadow
{
fn from( r: RawShadow ) -> Self
{
InsetShadow { offset: r.offset, blur: r.blur, spread: r.spread, color: r.color, blend: r.blend.into() }
}
}
#[ derive( Debug, Clone, Serialize, Deserialize ) ]
#[ serde( untagged ) ]
pub( super ) enum RawShadowsRef
{
Named( String ),
Inline( Vec<RawShadow> ),
}
impl From<RawShadowsRef> for ShadowsRef
{
fn from( r: RawShadowsRef ) -> Self
{
match r
{
RawShadowsRef::Named( id ) => ShadowsRef::Named( id ),
RawShadowsRef::Inline( items ) => ShadowsRef::Inline( items.into_iter().map( Into::into ).collect() ),
}
}
}
#[ derive( Debug, Clone, Serialize, Deserialize ) ]
#[ serde( deny_unknown_fields ) ]
pub( super ) struct RawSurfaceBody
{
pub( super ) fill: Box<RawPaint>,
#[ serde( default, skip_serializing_if = "Option::is_none" ) ]
pub( super ) shadows: Option<RawShadowsRef>,
#[ serde( default ) ]
pub( super ) inset_shadows: Vec<RawShadow>,
}
impl From<RawSurfaceBody> for Surface
{
fn from( r: RawSurfaceBody ) -> Self
{
Surface
{
fill: Paint::from( *r.fill ),
shadows: r.shadows.map( Into::into ),
inset_shadows: r.inset_shadows.into_iter().map( Into::into ).collect(),
}
}
}
#[ derive( Debug, Clone, Serialize, Deserialize ) ]
#[ serde( untagged ) ]
pub( super ) enum RawLineHeight
{
Px { px: f32 },
Multiplier { mul: f32 },
}
impl From<RawLineHeight> for LineHeight
{
fn from( r: RawLineHeight ) -> Self
{
match r
{
RawLineHeight::Px { px } => LineHeight::Px( px ),
RawLineHeight::Multiplier { mul } => LineHeight::Multiplier( mul ),
}
}
}
#[ derive( Debug, Clone, Serialize, Deserialize ) ]
#[ serde( deny_unknown_fields ) ]
pub( super ) struct RawFontRef
{
pub( super ) r#ref: String,
}
impl From<RawFontRef> for FontRef
{
fn from( r: RawFontRef ) -> Self { FontRef::Named( r.r#ref ) }
}
#[ derive( Debug, Clone, Serialize, Deserialize ) ]
#[ serde( deny_unknown_fields ) ]
pub( super ) struct RawTextStyleBody
{
pub( super ) family: RawFontRef,
pub( super ) weight: u16,
#[ serde( default ) ]
pub( super ) style: RawFontStyle,
pub( super ) size: f32,
pub( super ) line_height: RawLineHeight,
#[ serde( default ) ]
pub( super ) letter_spacing: f32,
#[ serde( default ) ]
pub( super ) transform: RawTextTransform,
#[ serde( default ) ]
pub( super ) decoration: RawTextDecoration,
#[ serde( default, skip_serializing_if = "Option::is_none" ) ]
pub( super ) color: Option<String>,
}
impl From<RawTextStyleBody> for TextStyle
{
fn from( r: RawTextStyleBody ) -> Self
{
TextStyle
{
family: r.family.into(),
weight: r.weight,
style: r.style.into(),
size: r.size,
line_height: r.line_height.into(),
letter_spacing: r.letter_spacing,
transform: r.transform.into(),
decoration: r.decoration.into(),
color: r.color,
}
}
}
#[ derive( Debug, Clone, Serialize, Deserialize ) ]
#[ serde( tag = "type", rename_all = "kebab-case" ) ]
pub( super ) enum RawSlot
{
Color
{
#[ serde( with = "color_serde" ) ]
value: Color,
#[ serde( default ) ]
meta: RawMetadata,
},
Linear
{
angle_deg: f32,
#[ serde( default ) ]
space: RawGradientSpace,
stops: Vec<RawColorStop>,
#[ serde( default ) ]
meta: RawMetadata,
},
Radial
{
center: [f32; 2],
radius: f32,
#[ serde( default ) ]
space: RawGradientSpace,
stops: Vec<RawColorStop>,
#[ serde( default ) ]
meta: RawMetadata,
},
Shadows
{
shadows: Vec<RawShadow>,
#[ serde( default ) ]
meta: RawMetadata,
},
Surface
{
#[ serde( flatten ) ]
body: RawSurfaceBody,
#[ serde( default ) ]
meta: RawMetadata,
},
Typography
{
#[ serde( flatten ) ]
body: RawTextStyleBody,
#[ serde( default ) ]
meta: RawMetadata,
},
}
impl From<RawSlot> for Slot
{
fn from( r: RawSlot ) -> Self
{
match r
{
RawSlot::Color { value, meta } => Slot::Color { value, meta: meta.into() },
RawSlot::Linear { angle_deg, space, stops, meta } => Slot::Paint
{
value: Paint::Linear( LinearGradient
{
angle_deg,
space: space.into(),
stops: collect_capped_stops( stops ),
}),
meta: meta.into(),
},
RawSlot::Radial { center, radius, space, stops, meta } => Slot::Paint
{
value: Paint::Radial( RadialGradient
{
center,
radius,
space: space.into(),
stops: collect_capped_stops( stops ),
}),
meta: meta.into(),
},
RawSlot::Shadows { shadows, meta } => Slot::Shadows
{
value: shadows.into_iter().map( Into::into ).collect(),
meta: meta.into(),
},
RawSlot::Surface { body, meta } => Slot::Surface
{
value: body.into(),
meta: meta.into(),
},
RawSlot::Typography { body, meta } => Slot::TextStyle
{
value: body.into(),
meta: meta.into(),
},
}
}
}
#[ derive( Debug, Clone, Serialize, Deserialize ) ]
#[ serde( deny_unknown_fields ) ]
pub( super ) struct RawFontSource
{
pub( super ) weight: u16,
#[ serde( default ) ]
pub( super ) style: RawFontStyle,
pub( super ) path: String,
}
#[ derive( Debug, Clone, Serialize, Deserialize ) ]
#[ serde( deny_unknown_fields ) ]
pub( super ) struct RawFontFamily
{
pub( super ) name: String,
#[ serde( default ) ]
pub( super ) fallbacks: Vec<String>,
#[ serde( default ) ]
pub( super ) sources: Vec<RawFontSource>,
}
#[ derive( Debug, Clone, Serialize, Deserialize ) ]
#[ serde( deny_unknown_fields ) ]
pub( super ) struct RawWallpaper
{
pub( super ) path: String,
#[ serde( default ) ]
pub( super ) fit: WallpaperFit,
}
#[ derive( Debug, Clone, Serialize, Deserialize ) ]
#[ serde( deny_unknown_fields ) ]
pub( super ) struct RawLauncher
{
pub( super ) background: String,
pub( super ) border_radius: f32,
}
#[ derive( Debug, Clone, Serialize, Deserialize, Default ) ]
#[ serde( deny_unknown_fields ) ]
pub( super ) struct RawWindowControls
{
#[ serde( default ) ] pub( super ) bar_bg: Option<String>,
#[ serde( default ) ] pub( super ) icon: Option<String>,
#[ serde( default ) ] pub( super ) hover_bg: Option<String>,
#[ serde( default ) ] pub( super ) pressed_bg: Option<String>,
#[ serde( default ) ] pub( super ) close_hover_bg: Option<String>,
#[ serde( default ) ] pub( super ) close_icon: Option<String>,
#[ serde( default ) ] pub( super ) focus_ring: Option<String>,
}
#[ derive( Debug, Clone, Serialize, Deserialize ) ]
#[ serde( deny_unknown_fields ) ]
pub( super ) struct RawMode
{
#[ serde( default, skip_serializing_if = "Option::is_none" ) ]
pub( super ) wallpaper: Option<RawWallpaper>,
#[ serde( default, skip_serializing_if = "Option::is_none" ) ]
pub( super ) lockscreen: Option<RawWallpaper>,
#[ serde( default, skip_serializing_if = "Option::is_none" ) ]
pub( super ) launcher: Option<RawLauncher>,
#[ serde( default, skip_serializing_if = "Option::is_none" ) ]
pub( super ) window_controls: Option<RawWindowControls>,
#[ serde( default ) ]
pub( super ) slots: HashMap<String, RawSlot>,
}
#[ derive( Debug, Clone, Serialize, Deserialize ) ]
#[ serde( deny_unknown_fields ) ]
pub( super ) struct RawModes
{
pub( super ) light: RawMode,
pub( super ) dark: RawMode,
}
#[ derive( Debug, Clone, Serialize, Deserialize ) ]
#[ serde( deny_unknown_fields ) ]
pub( super ) struct RawThemeMeta
{
pub( super ) id: String,
pub( super ) name: String,
}
#[ derive( Debug, Clone, Serialize, Deserialize ) ]
#[ serde( deny_unknown_fields ) ]
pub( super ) struct RawThemeDocument
{
pub( super ) theme: RawThemeMeta,
#[ serde( default ) ]
pub( super ) fonts: HashMap<String, RawFontFamily>,
pub( super ) modes: RawModes,
}