use repose_core::prelude::{Color, Modifier, StateColors};
use repose_core::{AlignItems, Dp, JustifyContent, PaddingValues};
pub const FONT: &str = "Fredoka";
pub const MATERIAL_SYMBOLS: &str = "Material Symbols Outlined";
pub const ICON_BUTTON: f32 = 46.0;
pub const ICON_GLYPH: f32 = 26.0;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Tokens {
pub ink: Color,
pub cream: Color,
pub gold: Color,
pub gold_dark: Color,
pub coral: Color,
pub sky: Color,
pub sky_dark: Color,
pub cyan: Color,
pub pink: Color,
pub green: Color,
pub scrim: Color,
pub button_shadow: Color,
pub touch_bg: Color,
pub touch_ring: Color,
pub touch_shadow: Color,
pub touch_knob: Color,
pub touch_knob_dark: Color,
pub touch_center: Color,
}
impl Tokens {
pub const KICKBACK: Tokens = Tokens {
ink: Color(0x2B, 0x1A, 0x14, 0xFF),
cream: Color(0xFF, 0xF3, 0xDC, 0xFF),
gold: Color(0xFF, 0xE0, 0x66, 0xFF),
gold_dark: Color(0xD9, 0xA5, 0x1E, 0xFF),
coral: Color(0xFF, 0x5F, 0xA2, 0xFF),
sky: Color(0xF2, 0xA6, 0x5A, 0xFF),
sky_dark: Color(0xE8, 0x89, 0x4A, 0xFF),
cyan: Color(0x7F, 0xF0, 0xE4, 0xFF),
pink: Color(0xFF, 0x8A, 0xC8, 0xFF),
green: Color(0x8B, 0xD9, 0x6E, 0xFF),
scrim: Color(0x2B, 0x1A, 0x14, 0x77),
button_shadow: Color(0x2B, 0x1A, 0x14, 0x40),
touch_bg: Color(0xFF, 0xF3, 0xDC, 0x99),
touch_ring: Color(0x2B, 0x1A, 0x14, 0xFF),
touch_shadow: Color(0x2B, 0x1A, 0x14, 0x2E),
touch_knob: Color(0xFF, 0xFF, 0xFF, 0xFF),
touch_knob_dark: Color(0xE8, 0xC4, 0x9E, 0xFF),
touch_center: Color(0x2B, 0x1A, 0x14, 0xFF),
};
pub const TEMPLATE: Tokens = Tokens {
ink: Color(0x1E, 0x2A, 0x4A, 0xFF),
cream: Color(0xFF, 0xF8, 0xEA, 0xFF),
gold: Color(0xFF, 0xC9, 0x3C, 0xFF),
gold_dark: Color(0xE0, 0x9B, 0x1F, 0xFF),
coral: Color(0xF4, 0x71, 0x6A, 0xFF),
sky: Color(0x7F, 0xC9, 0xF2, 0xFF),
sky_dark: Color(0x5F, 0xAC, 0xDE, 0xFF),
cyan: Color(0x46, 0xC8, 0xF5, 0xFF),
pink: Color(0xFF, 0x9E, 0xB1, 0xFF),
green: Color(0x7B, 0xD8, 0x8F, 0xFF),
scrim: Color(0x1E, 0x2A, 0x4A, 0x66),
button_shadow: Color(0x1E, 0x2A, 0x4A, 0x33),
touch_bg: Color(0xFF, 0xF8, 0xEA, 0x99),
touch_ring: Color(0x1E, 0x2A, 0x4A, 0xFF),
touch_shadow: Color(0x1E, 0x2A, 0x4A, 0x22),
touch_knob: Color(0xFF, 0xFF, 0xFF, 0xFF),
touch_knob_dark: Color(0xC9, 0xD8, 0xEE, 0xFF),
touch_center: Color(0x1E, 0x2A, 0x4A, 0xFF),
};
}
impl Default for Tokens {
fn default() -> Self {
Self::KICKBACK
}
}
pub fn mix(a: Color, b: Color, t: f32) -> Color {
let t = t.clamp(0.0, 1.0);
let blend = |from: u8, to: u8| -> u8 {
(from as f32 + (to as f32 - from as f32) * t) as u8
};
Color(
blend(a.0, b.0),
blend(a.1, b.1),
blend(a.2, b.2),
blend(a.3, b.3),
)
}
pub fn lighten(color: Color, t: f32) -> Color {
mix(color, Color(0xFF, 0xFF, 0xFF, 0xFF), t)
}
pub fn darken(color: Color, t: f32) -> Color {
mix(color, Color(0x00, 0x00, 0x00, 0xFF), t)
}
pub fn state_colors(face: Color, ink: Color) -> StateColors {
StateColors {
default: face,
hovered: lighten(face, 0.08),
focused: lighten(face, 0.04),
pressed: mix(face, ink, 0.16),
disabled: mix(face, ink, 0.30),
dragged: mix(face, ink, 0.16),
}
}
pub fn hand_cut(radius: f32, seed: u32) -> [Dp; 4] {
let wobble = |k: u32| -> Dp {
let bits = seed.wrapping_mul(0x9E37_79B9 ^ k.wrapping_mul(0x85EB_CA6B));
let frac = ((bits >> 16) & 0xFF) as f32 / 255.0 - 0.5;
Dp(radius * (1.0 + frac * 0.34))
};
[wobble(1), wobble(2), wobble(3), wobble(4)]
}
pub fn chunky(
tokens: &Tokens,
face: Color,
pad_x: f32,
pad_y: f32,
radius: f32,
scale: f32,
) -> Modifier {
Modifier::new()
.state_colors(state_colors(face, tokens.ink))
.border(Dp(3.0 * scale), tokens.ink, Dp(radius * scale))
.padding_values(PaddingValues {
left: Dp(pad_x * scale),
right: Dp(pad_x * scale),
top: Dp(pad_y * scale),
bottom: Dp(pad_y * scale),
})
.shadow_with_color(Dp(0.0), Dp(5.0 * scale), tokens.button_shadow)
.align_items(AlignItems::CENTER)
.justify_content(JustifyContent::CENTER)
.focusable(true)
}
pub fn chip(tokens: &Tokens, face: Color, scale: f32) -> Modifier {
Modifier::new()
.background(face)
.border(Dp(2.5 * scale), tokens.ink, Dp(14.0 * scale))
.padding_values(PaddingValues {
left: Dp(14.0 * scale),
right: Dp(14.0 * scale),
top: Dp(6.0 * scale),
bottom: Dp(6.0 * scale),
})
}
pub fn outline_for(size: f32) -> f32 {
(size / 3.0).max(2.5)
}
#[cfg(test)]
mod tests {
use super::*;
const A: Color = Color(0x10, 0x20, 0x30, 0x40);
const B: Color = Color(0x80, 0x90, 0xA0, 0xFF);
#[test]
fn mix_endpoints_are_the_inputs() {
assert_eq!(mix(A, B, 0.0), A);
assert_eq!(mix(A, B, 1.0), B);
assert_eq!(mix(A, B, -3.0), A, "clamped below");
assert_eq!(mix(A, B, 9.0), B, "clamped above");
}
#[test]
fn mix_midpoint_is_the_average() {
assert_eq!(mix(A, B, 0.5), Color(0x48, 0x58, 0x68, 0x9F));
}
#[test]
fn lighten_and_darken_reach_white_and_black() {
assert_eq!(lighten(A, 0.0), A);
assert_eq!(lighten(A, 1.0), Color(0xFF, 0xFF, 0xFF, 0x40));
assert_eq!(darken(A, 1.0), Color(0x00, 0x00, 0x00, 0x40));
}
#[test]
fn state_colors_keep_the_face_as_default_and_shade_the_rest() {
let colors = state_colors(A, Color(0x00, 0x00, 0x00, 0xFF));
assert_eq!(colors.default, A);
assert_eq!(colors.hovered, lighten(A, 0.08));
assert_eq!(colors.focused, lighten(A, 0.04));
assert_eq!(colors.pressed, mix(A, Color(0x00, 0x00, 0x00, 0xFF), 0.16));
assert_eq!(colors.disabled, mix(A, Color(0x00, 0x00, 0x00, 0xFF), 0.30));
}
#[test]
fn hand_cut_stays_within_the_documented_band() {
for seed in [0u32, 1, 7, 0xDEAD_BEEF, 42] {
for corner in hand_cut(16.0, seed) {
assert!(
(16.0 * 0.66..=16.0 * 1.34).contains(&corner.0),
"seed {seed} produced {corner:?}"
);
}
}
}
#[test]
fn hand_cut_is_stable_for_a_seed() {
assert_eq!(hand_cut(12.0, 99), hand_cut(12.0, 99));
assert_ne!(hand_cut(12.0, 98), hand_cut(12.0, 99));
}
#[test]
fn the_default_tokens_are_the_palette_pair() {
assert_eq!(Tokens::default(), Tokens::KICKBACK);
assert_ne!(Tokens::KICKBACK.ink, Tokens::TEMPLATE.ink);
}
}