use std::sync::Arc;
use image::RgbaImage;
use include_dir::{include_dir, Dir, DirEntry};
use prism::Assets;
use std::fmt;
use std::fmt::Display;
use ptsd::{ColorResources, IconResources, FontResources};
pub use ptsd::Color;
#[derive(Debug, Clone)]
pub struct Theme(ptsd::Theme, BrandResources);
impl Theme {
pub fn from(assets: &Dir<'static>, color: Color) -> Self {
let (theme, is_dark) = ptsd::Theme::from(assets, color);
Theme::new(assets, theme, is_dark, color)
}
pub fn dark(assets: &Dir<'static>, color: Color) -> Self { Theme::new(assets, ptsd::Theme::dark(assets, color), true, color) }
pub fn light(assets: &Dir<'static>, color: Color) -> Self { Theme::new(assets, ptsd::Theme::light(assets, color), false, color) }
fn new(assets: &Dir<'static>, mut inner: ptsd::Theme, is_dark: bool, color: Color) -> Self {
Icons::map(&mut inner.icons);
Button::map(&mut inner.colors, is_dark, color);
Theme(inner, BrandResources::new(assets))
}
pub fn colors(&self) -> &ColorResources {&self.0.colors}
pub fn icons(&self) -> &IconResources {&self.0.icons}
pub fn fonts(&self) -> &FontResources {&self.0.fonts}
pub fn brand(&self) -> &BrandResources {&self.1}
pub fn colors_mut(&mut self) -> &mut ColorResources {&mut self.0.colors}
pub fn icons_mut(&mut self) -> &mut IconResources {&mut self.0.icons}
pub fn fonts_mut(&mut self) -> &mut FontResources {&mut self.0.fonts}
pub fn brand_mut(&mut self) -> &mut BrandResources {&mut self.1}
}
impl Default for Theme {
fn default() -> Theme {
let assets = include_dir!("resources");
let color = Color::from_hex("#00a2ff", 255);
let inner = ptsd::Theme::dark(&assets, color);
Theme::new(&assets, inner, true, color)
}
}
#[derive(Clone, Debug)]
pub struct BrandResources {
pub wordmark: Arc<RgbaImage>,
pub logo: Arc<RgbaImage>,
pub app_icon: Arc<RgbaImage>,
pub error: Arc<RgbaImage>,
pub qr_code: Arc<RgbaImage>,
}
impl Default for BrandResources {
fn default() -> Self {
let dir = include_dir!("resources/brand");
BrandResources {
logo: Arc::new(Assets::load_svg(&Assets::load_file(&dir, "logo.svg").unwrap())),
wordmark: Arc::new(Assets::load_svg(&Assets::load_file(&dir, "wordmark.svg").unwrap())),
app_icon: Arc::new(Assets::load_svg(&Assets::load_file(&dir, "app_icon.svg").unwrap())),
error: Arc::new(Assets::load_svg(&Assets::load_file(&dir, "error.svg").unwrap())),
qr_code: Arc::new(Assets::load_png(&dir, "qr_code.png").unwrap())
}
}
}
impl BrandResources {
fn new(dir: &Dir<'static>) -> Self {
let defaults = BrandResources::default();
let dir = dir.entries().iter().find_map(|entry| {
match entry {
DirEntry::Dir(d) if d.path().file_name().and_then(|n| n.to_str()) == Some("brand") => {
Some(d.clone())
}
_ => None,
}
}).unwrap_or(include_dir!("resources"));
BrandResources {
logo: Assets::load_file(&dir, "brand/logo.svg").map(|f| Arc::new(Assets::load_svg(&f))).unwrap_or(defaults.logo.clone()),
wordmark: Assets::load_file(&dir, "brand/wordmark.svg").map(|f| Arc::new(Assets::load_svg(&f))).unwrap_or(defaults.wordmark.clone()),
app_icon: Assets::load_file(&dir, "brand/app_icon.svg").map(|f| Arc::new(Assets::load_svg(&f))).unwrap_or(defaults.app_icon.clone()),
error: Assets::load_file(&dir, "brand/error.svg").map(|f| Arc::new(Assets::load_svg(&f))).unwrap_or(defaults.error.clone()),
qr_code: defaults.qr_code.clone()
}
}
}
#[derive(Copy, Clone, Debug)]
pub struct ButtonColors {
pub primary: ButtonColorSet,
pub secondary: ButtonColorSet,
pub ghost: ButtonColorSet,
}
#[derive(Copy, Clone, Debug)]
pub struct ButtonColorSet {
pub default: ButtonColorScheme,
pub disabled: ButtonColorScheme,
pub hover: ButtonColorScheme,
pub pressed: ButtonColorScheme,
}
#[derive(Copy, Clone, Debug)]
pub struct ButtonColorScheme {
pub background: Color,
pub label: Color,
pub outline: Color,
}
impl ButtonColorScheme {
fn from(resources: &ColorResources, variant: Variant, state: State) -> Self {
ButtonColorScheme {
background: resources.get(Button(variant, state, Slot::Background)),
label: resources.get(Button(variant, state, Slot::Label)),
outline: resources.get(Button(variant, state, Slot::Outline)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Variant { Primary, Secondary, Ghost }
impl Display for Variant {fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {write!(f, "Variant::{}", match self {
Variant::Primary => "Primary",
Variant::Secondary => "Secondary",
Variant::Ghost => "Ghost",
})}}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum State { Default, Hover, Pressed, Disabled }
impl Display for State {fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {write!(f, "State::{}", match self {
State::Default => "Default",
State::Hover => "Hover",
State::Pressed => "Pressed",
State::Disabled => "Disabled",
})}}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Slot { Background, Label, Outline }
impl Display for Slot {fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {write!(f, "Slot::{}", match self {
Slot::Background => "Background",
Slot::Label => "Label",
Slot::Outline => "Outline",
})}}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Button(pub Variant, pub State, pub Slot);
impl Display for Button {fn fmt(&self, f: &mut fmt::Formatter<'_>) ->
fmt::Result {write!(f, "Button({}, {}, {})", self.0, self.1, self.2)}}
impl Button {
pub fn get(resources: &ColorResources, variant: Variant) -> ButtonColorSet {
ButtonColorSet {
default: ButtonColorScheme::from(resources, variant, State::Default),
disabled: ButtonColorScheme::from(resources, variant, State::Disabled),
hover: ButtonColorScheme::from(resources, variant, State::Hover),
pressed: ButtonColorScheme::from(resources, variant, State::Pressed),
}
}
fn map(resources: &mut ColorResources, is_dark: bool, brand: Color) {
use Slot::*;
use State::*;
use Variant::*;
resources.insert(Button(Primary, Default, Background), brand);
resources.insert(Button(Primary, Default, Label), brand.contrasted());
resources.insert(Button(Primary, Default, Outline), Color::TRANSPARENT);
resources.insert(Button(Primary, Hover, Background), brand.darken(0.75));
resources.insert(Button(Primary, Hover, Label), brand.contrasted());
resources.insert(Button(Primary, Hover, Outline), Color::TRANSPARENT);
resources.insert(Button(Primary, Pressed, Background),brand.darken(0.7));
resources.insert(Button(Primary, Pressed, Label), brand.contrasted());
resources.insert(Button(Primary, Pressed, Outline), Color::TRANSPARENT);
resources.insert(Button(Primary, Disabled, Background), Color::from_hex("#443f3f", 255));
resources.insert(Button(Primary, Disabled, Label), Color::BLACK);
resources.insert(Button(Primary, Disabled, Outline), Color::TRANSPARENT);
match is_dark {
false => {
resources.insert(Button(Secondary, Default, Background), Color::TRANSPARENT);
resources.insert(Button(Secondary, Default, Label), Color::BLACK);
resources.insert(Button(Secondary, Default, Outline), Color::from_hex("#585250", 255));
resources.insert(Button(Secondary, Hover, Background), Color::from_hex("#DDDDDD", 255));
resources.insert(Button(Secondary, Hover, Label), Color::BLACK);
resources.insert(Button(Secondary, Hover, Outline), Color::from_hex("#585250", 255));
resources.insert(Button(Secondary, Pressed, Background), Color::from_hex("#DDDDDD", 255));
resources.insert(Button(Secondary, Pressed, Label), Color::BLACK);
resources.insert(Button(Secondary, Pressed, Outline), Color::BLACK);
resources.insert(Button(Secondary, Disabled, Background), Color::from_hex("#443f3f", 255));
resources.insert(Button(Secondary, Disabled, Label), Color::BLACK);
resources.insert(Button(Secondary, Disabled, Outline), Color::from_hex("#585250", 255));
resources.insert(Button(Ghost, Default, Background), Color::TRANSPARENT);
resources.insert(Button(Ghost, Default, Label), Color::BLACK);
resources.insert(Button(Ghost, Default, Outline), Color::TRANSPARENT);
resources.insert(Button(Ghost, Hover, Background), Color::from_hex("#DDDDDD", 255));
resources.insert(Button(Ghost, Hover, Label), Color::BLACK);
resources.insert(Button(Ghost, Hover, Outline), Color::TRANSPARENT);
resources.insert(Button(Ghost, Pressed, Background), Color::from_hex("#DDDDDD", 255));
resources.insert(Button(Ghost, Pressed, Label), Color::BLACK);
resources.insert(Button(Ghost, Pressed, Outline), Color::TRANSPARENT);
resources.insert(Button(Ghost, Disabled, Background), Color::from_hex("#443f3f", 255));
resources.insert(Button(Ghost, Disabled, Label), Color::BLACK);
resources.insert(Button(Ghost, Disabled, Outline), Color::TRANSPARENT);
},
true => {
resources.insert(Button(Secondary, Default, Background), Color::TRANSPARENT);
resources.insert(Button(Secondary, Default, Label), Color::WHITE);
resources.insert(Button(Secondary, Default, Outline), Color::from_hex("#585250", 255));
resources.insert(Button(Secondary, Hover, Background), Color::from_hex("#262322", 255));
resources.insert(Button(Secondary, Hover, Label), Color::WHITE);
resources.insert(Button(Secondary, Hover, Outline), Color::from_hex("#585250", 255));
resources.insert(Button(Secondary, Pressed, Background), Color::from_hex("#262322", 255));
resources.insert(Button(Secondary, Pressed, Label), Color::WHITE);
resources.insert(Button(Secondary, Pressed, Outline), Color::WHITE);
resources.insert(Button(Secondary, Disabled, Background), Color::from_hex("#443f3f", 255));
resources.insert(Button(Secondary, Disabled, Label), Color::BLACK);
resources.insert(Button(Secondary, Disabled, Outline), Color::from_hex("#585250", 255));
resources.insert(Button(Ghost, Default, Background), Color::TRANSPARENT);
resources.insert(Button(Ghost, Default, Label), Color::WHITE);
resources.insert(Button(Ghost, Default, Outline), Color::TRANSPARENT);
resources.insert(Button(Ghost, Hover, Background), Color::from_hex("#262322", 255));
resources.insert(Button(Ghost, Hover, Label), Color::WHITE);
resources.insert(Button(Ghost, Hover, Outline), Color::TRANSPARENT);
resources.insert(Button(Ghost, Pressed, Background), Color::from_hex("#262322", 255));
resources.insert(Button(Ghost, Pressed, Label), Color::WHITE);
resources.insert(Button(Ghost, Pressed, Outline), Color::TRANSPARENT);
resources.insert(Button(Ghost, Disabled, Background), Color::TRANSPARENT);
resources.insert(Button(Ghost, Disabled, Label), Color::from_hex("#585250", 255));
resources.insert(Button(Ghost, Disabled, Outline), Color::TRANSPARENT);
}
}
}
}
macro_rules! icons {
($($variant:ident => $name:expr),* $(,)?) => {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Icons {
$($variant),*
}
impl Icons {
pub fn map(icons: &mut IconResources) {
$(icons.insert(Icons::$variant, $name);)*
}
}
impl std::fmt::Display for Icons {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
$(Icons::$variant => write!(f, $name),)*
}
}
}
};
}
icons! {
Accounts => "accounts",
Add => "add",
AppStore => "app_store",
Baby => "baby",
Back => "back",
Backspace => "backspace",
Bitcoin => "bitcoin",
Block => "block",
Boot => "boot",
Camera => "camera",
Cancel => "cancel",
CapslockOn => "capslock_on",
Capslock => "capslock",
Check => "check",
Checkmark => "checkmark",
Close => "close",
Copy => "copy",
Credential => "credential",
Delete => "delete",
Discord => "discord",
Door => "door",
DownArrow => "down_arrow",
Down => "down",
Edit => "edit",
Emoji => "emoji",
Error => "error",
Explore => "explore",
Facebook => "facebook",
Forward => "forward",
Gif => "gif",
Group => "group",
Heart => "heart",
Home => "home",
Infinite => "infinite",
Info => "info",
Instagram => "instagram",
Left => "left",
Link => "link",
Megaphone => "megaphone",
Messages => "messages",
Microphone => "microphone",
Monitor => "monitor",
Notification => "notification",
Paste => "paste",
PelicanUI => "pelican_ui",
Photos => "photos",
PlayStore => "play_store",
Profile => "profile",
QrCode => "qr_code",
RadioFilled => "radio_filled",
Radio => "radio",
Right => "right",
Scan => "scan",
Search => "search",
Send => "send",
Senior => "senior",
Settings => "settings",
Unblock => "unblock",
Unboot => "unboot",
Unchecked => "unchecked",
Up => "up",
Wallet => "wallet",
Warning => "warning",
X => "x",
}