use core::fmt::Debug;
use nami::{Computed, Signal, impl_constant};
use waterui_core::{
Environment, Str,
resolve::{self, AnyResolvable, Resolvable},
};
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct Font(AnyResolvable<ResolvedFont>);
impl Default for Font {
fn default() -> Self {
Self::new(Body)
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct ResolvedFont {
pub size: f32,
pub weight: FontWeight,
pub line_height: Option<f32>,
pub letter_spacing: f32,
pub family: Option<Str>,
}
impl ResolvedFont {
#[must_use]
pub const fn new(size: f32, weight: FontWeight) -> Self {
Self {
size,
weight,
line_height: None,
letter_spacing: 0.0,
family: None,
}
}
#[must_use]
pub fn with_family(size: f32, weight: FontWeight, family: impl Into<Str>) -> Self {
Self {
size,
weight,
line_height: None,
letter_spacing: 0.0,
family: Some(family.into()),
}
}
#[must_use]
pub const fn with_static_family(size: f32, weight: FontWeight, family: &'static str) -> Self {
Self {
size,
weight,
line_height: None,
letter_spacing: 0.0,
family: Some(Str::from_static(family)),
}
}
#[must_use]
pub const fn with_typography_metrics(mut self, line_height: f32, letter_spacing: f32) -> Self {
self.line_height = Some(line_height);
self.letter_spacing = letter_spacing;
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum FontWeight {
Thin,
UltraLight,
Light,
#[default]
Normal,
Medium,
SemiBold,
Bold,
UltraBold,
Black,
}
impl_constant!(Font, ResolvedFont, FontWeight);
impl Font {
pub fn new(font: impl Resolvable<Resolved = ResolvedFont> + 'static) -> Self {
Self(AnyResolvable::new(font))
}
#[must_use]
pub fn weight(self, weight: FontWeight) -> Self {
Self::new(resolve::Map::new(self.0, move |font| ResolvedFont {
size: font.size,
weight,
line_height: font.line_height,
letter_spacing: font.letter_spacing,
family: font.family,
}))
}
#[must_use]
pub fn size(self, size: f32) -> Self {
Self::new(resolve::Map::new(self.0, move |font| ResolvedFont {
size,
weight: font.weight,
line_height: font.line_height,
letter_spacing: font.letter_spacing,
family: font.family,
}))
}
#[must_use]
pub fn family(self, family: impl Into<Str> + Clone + 'static) -> Self {
Self::new(resolve::Map::new(self.0, move |font| ResolvedFont {
size: font.size,
weight: font.weight,
line_height: font.line_height,
letter_spacing: font.letter_spacing,
family: Some(family.clone().into()),
}))
}
#[must_use]
pub fn line_height(self, line_height: f32) -> Self {
Self::new(resolve::Map::new(self.0, move |mut font| {
font.line_height = Some(line_height);
font
}))
}
#[must_use]
pub fn letter_spacing(self, letter_spacing: f32) -> Self {
Self::new(resolve::Map::new(self.0, move |mut font| {
font.letter_spacing = letter_spacing;
font
}))
}
#[must_use]
pub fn bold(self) -> Self {
self.weight(FontWeight::Bold)
}
#[must_use]
pub fn resolve(&self, env: &Environment) -> Computed<ResolvedFont> {
self.0.resolve(env)
}
}
pub trait FontSlot {
const DEFAULT: ResolvedFont;
}
macro_rules! impl_font {
($name:ident, $doc:expr, $size:expr, $weight:expr) => {
#[doc = $doc]
#[derive(Debug, Clone, Copy)]
pub struct $name;
impl FontSlot for $name {
const DEFAULT: ResolvedFont = ResolvedFont::new($size, $weight);
}
impl Resolvable for $name {
type Resolved = ResolvedFont;
fn resolve(&self, env: &Environment) -> impl Signal<Output = Self::Resolved> {
env.query::<Self, Computed<Self::Resolved>>()
.cloned()
.expect(concat!(
stringify!($name),
" font token is not installed in the environment"
))
}
}
impl From<$name> for Font {
fn from(value: $name) -> Self {
Self::new(value)
}
}
impl_constant!($name);
};
}
impl_font!(Body, "Body font style.", 16.0, FontWeight::Normal);
impl_font!(Title, "Title font style.", 22.0, FontWeight::Normal);
impl_font!(Headline, "Headline font style.", 24.0, FontWeight::Normal);
impl_font!(
Subheadline,
"Subheadline font style.",
16.0,
FontWeight::Medium
);
impl_font!(Caption, "Caption font style.", 12.0, FontWeight::Normal);
impl_font!(Footnote, "Footnote font style.", 11.0, FontWeight::Medium);