use crate::prelude::*;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(not(target_arch = "wasm32"))]
use std::fmt;
#[cfg(not(target_arch = "wasm32"))]
use std::path::PathBuf;
use std::{
borrow::Cow,
collections::hash_map::DefaultHasher,
hash::{Hash, Hasher},
};
pub(crate) type FontId = u64;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[must_use]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ThemeBuilder {
name: String,
#[cfg_attr(feature = "serde", serde(skip))]
fonts: Fonts,
size: u32,
styles: FontStyles,
colors: Colors,
spacing: Spacing,
}
impl Default for ThemeBuilder {
fn default() -> Self {
let theme = Theme::default();
Self {
name: theme.name,
fonts: theme.fonts,
size: theme.font_size,
styles: theme.styles,
colors: theme.colors,
spacing: theme.spacing,
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum FontType {
Body,
Heading,
Monospace,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ColorType {
Background,
Surface,
Primary,
PrimaryVariant,
Secondary,
SecondaryVariant,
Error,
OnBackground,
OnSurface,
OnPrimary,
OnSecondary,
OnError,
}
impl ThemeBuilder {
pub fn new<S: Into<String>>(name: S) -> Self {
Self {
name: name.into(),
..Self::default()
}
}
pub fn font_size(&mut self, size: u32) -> &mut Self {
self.size = size;
self
}
pub fn font(&mut self, font_type: FontType, font: Font, style: FontStyle) -> &mut Self {
match font_type {
FontType::Body => {
self.fonts.body = font;
self.styles.body = style;
}
FontType::Heading => {
self.fonts.heading = font;
self.styles.heading = style;
}
FontType::Monospace => {
self.fonts.monospace = font;
self.styles.monospace = style;
}
}
self
}
pub fn color<C: Into<Color>>(&mut self, color_type: ColorType, color: C) -> &mut Self {
let color = color.into();
let c = &mut self.colors;
match color_type {
ColorType::Background => c.background = color,
ColorType::Surface => c.surface = color,
ColorType::Primary => c.primary = color,
ColorType::PrimaryVariant => c.primary_variant = color,
ColorType::Secondary => c.secondary = color,
ColorType::SecondaryVariant => c.secondary_variant = color,
ColorType::Error => c.error = color,
ColorType::OnBackground => c.on_background = color,
ColorType::OnSurface => c.on_surface = color,
ColorType::OnPrimary => c.on_primary = color,
ColorType::OnSecondary => c.on_secondary = color,
ColorType::OnError => c.on_error = color,
}
self
}
pub fn spacing(&mut self, spacing: Spacing) -> &mut Self {
self.spacing = spacing;
self
}
pub fn build(&self) -> Theme {
Theme {
name: self.name.clone(),
fonts: self.fonts.clone(),
font_size: self.size,
styles: self.styles,
colors: self.colors,
spacing: self.spacing,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[must_use]
pub struct Font {
pub(crate) name: Cow<'static, str>,
#[cfg(not(target_arch = "wasm32"))]
pub(crate) source: FontSrc,
}
#[cfg(not(target_arch = "wasm32"))]
impl Default for Font {
fn default() -> Self {
Self::EMULOGIC
}
}
#[cfg(target_arch = "wasm32")]
impl Default for Font {
fn default() -> Self {
Self::named("Arial")
}
}
impl Font {
#[cfg(not(target_arch = "wasm32"))]
const NOTO_TTF: &'static [u8] = include_bytes!("../../assets/noto_sans_regular.ttf");
#[cfg(not(target_arch = "wasm32"))]
const EMULOGIC_TTF: &'static [u8] = include_bytes!("../../assets/emulogic.ttf");
#[cfg(not(target_arch = "wasm32"))]
const INCONSOLATA_TTF: &'static [u8] = include_bytes!("../../assets/inconsolata_bold.ttf");
#[cfg(not(target_arch = "wasm32"))]
pub const NOTO: Self = Self::from_bytes("Noto", Self::NOTO_TTF);
#[cfg(not(target_arch = "wasm32"))]
pub const EMULOGIC: Self = Self::from_bytes("Emulogic", Self::EMULOGIC_TTF);
#[cfg(not(target_arch = "wasm32"))]
pub const INCONSOLATA: Self = Self::from_bytes("Inconsolata", Self::INCONSOLATA_TTF);
#[inline]
pub const fn named(name: &'static str) -> Self {
Self {
name: Cow::Borrowed(name),
#[cfg(not(target_arch = "wasm32"))]
source: FontSrc::None,
}
}
#[cfg(not(target_arch = "wasm32"))]
#[inline]
pub const fn from_bytes(name: &'static str, bytes: &'static [u8]) -> Self {
Self {
name: Cow::Borrowed(name),
source: FontSrc::from_bytes(bytes),
}
}
#[cfg(not(target_arch = "wasm32"))]
#[inline]
pub fn from_file<S, P>(name: S, path: P) -> Self
where
S: Into<Cow<'static, str>>,
P: Into<PathBuf>,
{
Self {
name: name.into(),
source: FontSrc::from_file(path),
}
}
#[inline]
#[must_use]
pub fn name(&self) -> &str {
self.name.as_ref()
}
#[cfg(not(target_arch = "wasm32"))]
#[inline]
#[must_use]
pub(crate) const fn source(&self) -> &FontSrc {
&self.source
}
#[inline]
#[must_use]
pub fn id(&self) -> FontId {
let mut hasher = DefaultHasher::new();
self.name.hash(&mut hasher);
hasher.finish()
}
}
#[derive(Clone, PartialEq, Eq, Hash)]
#[cfg(not(target_arch = "wasm32"))]
pub(crate) enum FontSrc {
None,
Bytes(&'static [u8]),
Path(PathBuf),
}
#[cfg(not(target_arch = "wasm32"))]
impl fmt::Debug for FontSrc {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::None => write!(f, "None"),
Self::Bytes(bytes) => write!(f, "Bytes([u8; {}])", bytes.len()),
#[cfg(not(target_arch = "wasm32"))]
Self::Path(path) => write!(f, "Path({})", path.display()),
}
}
}
#[cfg(not(target_arch = "wasm32"))]
impl FontSrc {
pub(crate) const fn from_bytes(bytes: &'static [u8]) -> Self {
Self::Bytes(bytes)
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn from_file<P: Into<PathBuf>>(path: P) -> Self {
Self::Path(path.into())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct Fonts {
pub body: Font,
pub heading: Font,
pub monospace: Font,
}
impl Default for Fonts {
fn default() -> Self {
Self {
body: Font::default(),
heading: Font::default(),
#[cfg(not(target_arch = "wasm32"))]
monospace: Font::INCONSOLATA,
#[cfg(target_arch = "wasm32")]
monospace: Font::named("Courier"),
}
}
}
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
#[must_use]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct FontStyles {
pub body: FontStyle,
pub heading: FontStyle,
pub monospace: FontStyle,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
#[must_use]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Colors {
pub background: Color,
pub surface: Color,
pub primary: Color,
pub primary_variant: Color,
pub secondary: Color,
pub secondary_variant: Color,
pub error: Color,
pub on_background: Color,
pub on_surface: Color,
pub on_primary: Color,
pub on_secondary: Color,
pub on_error: Color,
}
impl Colors {
#[allow(clippy::unreadable_literal)]
pub const fn dark() -> Self {
Self {
background: Color::from_hex(0x121212),
surface: Color::from_hex(0x121212),
primary: Color::from_hex(0xbf360c),
primary_variant: Color::from_hex(0xff6f43),
secondary: Color::from_hex(0x0c95bf),
secondary_variant: Color::from_hex(0x43d3ff),
error: Color::from_hex(0xcf6679),
on_background: Color::WHITE,
on_surface: Color::WHITE,
on_primary: Color::BLACK,
on_secondary: Color::BLACK,
on_error: Color::BLACK,
}
}
#[allow(clippy::unreadable_literal)]
pub const fn light() -> Self {
Self {
background: Color::from_hex(0xffffff),
surface: Color::from_hex(0xffffff),
primary: Color::from_hex(0x00796b),
primary_variant: Color::from_hex(0x4db6ac),
secondary: Color::from_hex(0x79000e),
secondary_variant: Color::from_hex(0xb64d58),
error: Color::from_hex(0xb00020),
on_background: Color::BLACK,
on_surface: Color::BLACK,
on_primary: Color::WHITE,
on_secondary: Color::WHITE,
on_error: Color::WHITE,
}
}
#[inline]
pub fn on_background(&self) -> Color {
self.on_background.blended(self.background, 0.87)
}
#[inline]
pub fn on_surface(&self) -> Color {
self.on_surface.blended(self.surface, 0.87)
}
#[inline]
pub fn disabled(&self) -> Color {
self.on_background.blended(self.background, 0.38)
}
}
impl Default for Colors {
fn default() -> Self {
Self::dark()
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
#[must_use]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct SpacingBuilder {
frame_pad: Point<i32>,
item_pad: Point<i32>,
scroll_size: i32,
}
impl Default for SpacingBuilder {
fn default() -> Self {
let spacing = Spacing::default();
Self {
frame_pad: spacing.frame_pad,
item_pad: spacing.item_pad,
scroll_size: spacing.scroll_size,
}
}
}
impl SpacingBuilder {
pub fn frame_pad(&mut self, x: i32, y: i32) -> &mut Self {
self.frame_pad = point!(x, y);
self
}
pub fn item_pad(&mut self, x: i32, y: i32) -> &mut Self {
self.item_pad = point!(x, y);
self
}
pub fn scroll_size(&mut self, size: i32) -> &mut Self {
self.scroll_size = size;
self
}
pub const fn build(&self) -> Spacing {
Spacing {
frame_pad: self.frame_pad,
item_pad: self.item_pad,
scroll_size: self.scroll_size,
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[must_use]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Spacing {
pub frame_pad: Point<i32>,
pub item_pad: Point<i32>,
pub scroll_size: i32,
}
impl Default for Spacing {
fn default() -> Self {
Self {
frame_pad: point![8, 8],
item_pad: point![8, 6],
scroll_size: 12,
}
}
}
impl Spacing {
pub fn builder() -> SpacingBuilder {
SpacingBuilder::default()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
#[must_use]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Theme {
pub name: String,
#[cfg_attr(feature = "serde", serde(skip))]
pub fonts: Fonts,
pub font_size: u32,
pub styles: FontStyles,
pub colors: Colors,
pub spacing: Spacing,
}
impl Default for Theme {
fn default() -> Self {
Self::dark()
}
}
impl Theme {
#[inline]
pub fn builder() -> ThemeBuilder {
ThemeBuilder::default()
}
#[inline]
pub fn dark() -> Self {
Self {
name: "Dark".into(),
colors: Colors::dark(),
fonts: Fonts::default(),
font_size: 12,
styles: FontStyles::default(),
spacing: Spacing::default(),
}
}
#[inline]
pub fn light() -> Self {
Self {
name: "Light".into(),
colors: Colors::light(),
fonts: Fonts::default(),
font_size: 12,
styles: FontStyles::default(),
spacing: Spacing::default(),
}
}
}
impl PixState {
#[inline]
pub const fn theme(&self) -> &Theme {
&self.theme
}
#[inline]
pub fn theme_mut(&mut self) -> &mut Theme {
&mut self.theme
}
#[inline]
pub fn set_theme(&mut self, theme: Theme) {
self.theme = theme;
let colors = self.theme.colors;
self.background(colors.background);
self.fill(colors.on_background());
}
}