use material_color_utilities::{
dynamiccolor::{DynamicSchemeBuilder, MaterialDynamicColors, SpecVersion, Variant},
hct::Hct,
};
use tessera_ui::{Color, Dp, provide_context, tessera};
use crate::shape_def::Shape;
const DEFAULT_COLOR: Color = Color::from_rgb(0.4039, 0.3137, 0.6431);
#[derive(Clone, Copy, Debug)]
pub struct ContentColor {
pub current: Color,
}
impl Default for ContentColor {
fn default() -> Self {
ContentColor {
current: Color::BLACK,
}
}
}
pub struct MaterialAlpha;
impl MaterialAlpha {
pub const HOVER: f32 = 0.08;
pub const PRESSED: f32 = 0.1;
pub const FOCUSED: f32 = 0.1;
pub const DRAGGED: f32 = 0.16;
pub const DISABLED_CONTAINER: f32 = 0.12;
pub const DISABLED_CONTENT: f32 = 0.38;
}
pub fn content_color_for(background: Color, scheme: &MaterialColorScheme) -> Option<Color> {
scheme.content_color_for(background)
}
#[derive(Clone, Copy, Debug)]
pub struct TextStyle {
pub font_size: Dp,
pub line_height: Option<Dp>,
}
impl Default for TextStyle {
fn default() -> Self {
Self {
font_size: Dp(16.0),
line_height: Some(Dp(24.0)),
}
}
}
pub fn provide_text_style(style: TextStyle, child: impl FnOnce()) {
provide_context(|| style, child);
}
#[derive(Clone, Copy, Debug)]
pub struct MaterialTypography {
pub display_large: TextStyle,
pub display_medium: TextStyle,
pub display_small: TextStyle,
pub headline_large: TextStyle,
pub headline_medium: TextStyle,
pub headline_small: TextStyle,
pub title_large: TextStyle,
pub title_medium: TextStyle,
pub title_small: TextStyle,
pub body_large: TextStyle,
pub body_medium: TextStyle,
pub body_small: TextStyle,
pub label_large: TextStyle,
pub label_medium: TextStyle,
pub label_small: TextStyle,
}
impl Default for MaterialTypography {
fn default() -> Self {
Self {
display_large: TextStyle {
font_size: Dp(57.0),
line_height: Some(Dp(64.0)),
},
display_medium: TextStyle {
font_size: Dp(45.0),
line_height: Some(Dp(52.0)),
},
display_small: TextStyle {
font_size: Dp(36.0),
line_height: Some(Dp(44.0)),
},
headline_large: TextStyle {
font_size: Dp(32.0),
line_height: Some(Dp(40.0)),
},
headline_medium: TextStyle {
font_size: Dp(28.0),
line_height: Some(Dp(36.0)),
},
headline_small: TextStyle {
font_size: Dp(24.0),
line_height: Some(Dp(32.0)),
},
title_large: TextStyle {
font_size: Dp(22.0),
line_height: Some(Dp(28.0)),
},
title_medium: TextStyle {
font_size: Dp(16.0),
line_height: Some(Dp(24.0)),
},
title_small: TextStyle {
font_size: Dp(14.0),
line_height: Some(Dp(20.0)),
},
body_large: TextStyle {
font_size: Dp(16.0),
line_height: Some(Dp(24.0)),
},
body_medium: TextStyle {
font_size: Dp(14.0),
line_height: Some(Dp(20.0)),
},
body_small: TextStyle {
font_size: Dp(12.0),
line_height: Some(Dp(16.0)),
},
label_large: TextStyle {
font_size: Dp(14.0),
line_height: Some(Dp(20.0)),
},
label_medium: TextStyle {
font_size: Dp(12.0),
line_height: Some(Dp(16.0)),
},
label_small: TextStyle {
font_size: Dp(11.0),
line_height: Some(Dp(16.0)),
},
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct MaterialShapes {
pub extra_small: Shape,
pub small: Shape,
pub medium: Shape,
pub large: Shape,
pub extra_large: Shape,
}
impl Default for MaterialShapes {
fn default() -> Self {
Self {
extra_small: Shape::rounded_rectangle(Dp(4.0)),
small: Shape::rounded_rectangle(Dp(8.0)),
medium: Shape::rounded_rectangle(Dp(12.0)),
large: Shape::rounded_rectangle(Dp(16.0)),
extra_large: Shape::rounded_rectangle(Dp(28.0)),
}
}
}
#[derive(Clone, Debug, Default)]
pub struct MaterialTheme {
pub color_scheme: MaterialColorScheme,
pub typography: MaterialTypography,
pub shapes: MaterialShapes,
}
impl MaterialTheme {
pub fn from_color_scheme(color_scheme: MaterialColorScheme) -> Self {
Self {
color_scheme,
..Self::default()
}
}
pub fn from_seed(seed: Color, is_dark: bool) -> Self {
Self::from_color_scheme(scheme_from_seed(seed, is_dark))
}
}
#[tessera]
pub fn material_theme(theme: impl FnOnce() -> MaterialTheme, child: impl FnOnce()) {
provide_context(theme, child);
}
#[derive(Clone, Debug)]
pub struct MaterialColorScheme {
pub is_dark: bool,
pub primary: Color,
pub on_primary: Color,
pub primary_container: Color,
pub on_primary_container: Color,
pub secondary: Color,
pub on_secondary: Color,
pub secondary_container: Color,
pub on_secondary_container: Color,
pub tertiary: Color,
pub on_tertiary: Color,
pub tertiary_container: Color,
pub on_tertiary_container: Color,
pub error: Color,
pub on_error: Color,
pub error_container: Color,
pub on_error_container: Color,
pub background: Color,
pub on_background: Color,
pub surface: Color,
pub on_surface: Color,
pub surface_variant: Color,
pub on_surface_variant: Color,
pub outline: Color,
pub outline_variant: Color,
pub shadow: Color,
pub scrim: Color,
pub surface_tint: Color,
pub surface_bright: Color,
pub surface_dim: Color,
pub inverse_surface: Color,
pub inverse_on_surface: Color,
pub inverse_primary: Color,
pub surface_container: Color,
pub surface_container_high: Color,
pub surface_container_highest: Color,
pub surface_container_low: Color,
pub surface_container_lowest: Color,
pub primary_fixed: Color,
pub primary_fixed_dim: Color,
pub on_primary_fixed: Color,
pub on_primary_fixed_variant: Color,
pub secondary_fixed: Color,
pub secondary_fixed_dim: Color,
pub on_secondary_fixed: Color,
pub on_secondary_fixed_variant: Color,
pub tertiary_fixed: Color,
pub tertiary_fixed_dim: Color,
pub on_tertiary_fixed: Color,
pub on_tertiary_fixed_variant: Color,
}
impl MaterialColorScheme {
pub fn light_from_seed(seed: Color) -> Self {
scheme_from_seed(seed, false)
}
pub fn dark_from_seed(seed: Color) -> Self {
scheme_from_seed(seed, true)
}
pub fn content_color_for(&self, background: Color) -> Option<Color> {
if background == self.primary {
Some(self.on_primary)
} else if background == self.secondary {
Some(self.on_secondary)
} else if background == self.tertiary {
Some(self.on_tertiary)
} else if background == self.background {
Some(self.on_background)
} else if background == self.error {
Some(self.on_error)
} else if background == self.primary_container {
Some(self.on_primary_container)
} else if background == self.secondary_container {
Some(self.on_secondary_container)
} else if background == self.tertiary_container {
Some(self.on_tertiary_container)
} else if background == self.error_container {
Some(self.on_error_container)
} else if background == self.inverse_surface {
Some(self.inverse_on_surface)
} else if background == self.surface {
Some(self.on_surface)
} else if background == self.surface_variant {
Some(self.on_surface_variant)
} else if background == self.surface_bright
|| background == self.surface_container
|| background == self.surface_container_high
|| background == self.surface_container_highest
|| background == self.surface_container_low
|| background == self.surface_container_lowest
|| background == self.surface_dim
{
Some(self.on_surface)
} else if background == self.primary_fixed || background == self.primary_fixed_dim {
Some(self.on_primary_fixed)
} else if background == self.secondary_fixed || background == self.secondary_fixed_dim {
Some(self.on_secondary_fixed)
} else if background == self.tertiary_fixed || background == self.tertiary_fixed_dim {
Some(self.on_tertiary_fixed)
} else {
None
}
}
pub fn surface_color_at_elevation(&self, elevation: Dp) -> Color {
if elevation.0 == 0.0 {
return self.surface;
}
let alpha = ((4.5 * (elevation.0 + 1.0).ln()) + 2.0) / 100.0;
self.surface.blend_over(self.surface_tint, alpha as f32)
}
pub fn surface_color_at_elevation_for(&self, background: Color, elevation: Dp) -> Color {
if background == self.surface {
self.surface_color_at_elevation(elevation)
} else {
background
}
}
}
impl Default for MaterialColorScheme {
fn default() -> Self {
MaterialColorScheme::light_from_seed(DEFAULT_COLOR)
}
}
fn scheme_from_seed(seed: Color, is_dark: bool) -> MaterialColorScheme {
let scheme = DynamicSchemeBuilder::default()
.source_color_hct(Hct::from_int(color_to_argb(seed)))
.variant(Variant::TonalSpot)
.spec_version(SpecVersion::Spec2025)
.is_dark(is_dark)
.build();
let dynamic_colors = MaterialDynamicColors::new();
MaterialColorScheme {
is_dark,
primary: argb_to_color(dynamic_colors.primary().get_argb(&scheme)),
on_primary: argb_to_color(dynamic_colors.on_primary().get_argb(&scheme)),
primary_container: argb_to_color(dynamic_colors.primary_container().get_argb(&scheme)),
on_primary_container: argb_to_color(
dynamic_colors.on_primary_container().get_argb(&scheme),
),
secondary: argb_to_color(dynamic_colors.secondary().get_argb(&scheme)),
on_secondary: argb_to_color(dynamic_colors.on_secondary().get_argb(&scheme)),
secondary_container: argb_to_color(dynamic_colors.secondary_container().get_argb(&scheme)),
on_secondary_container: argb_to_color(
dynamic_colors.on_secondary_container().get_argb(&scheme),
),
tertiary: argb_to_color(dynamic_colors.tertiary().get_argb(&scheme)),
on_tertiary: argb_to_color(dynamic_colors.on_tertiary().get_argb(&scheme)),
tertiary_container: argb_to_color(dynamic_colors.tertiary_container().get_argb(&scheme)),
on_tertiary_container: argb_to_color(
dynamic_colors.on_tertiary_container().get_argb(&scheme),
),
error: argb_to_color(dynamic_colors.error().get_argb(&scheme)),
on_error: argb_to_color(dynamic_colors.on_error().get_argb(&scheme)),
error_container: argb_to_color(dynamic_colors.error_container().get_argb(&scheme)),
on_error_container: argb_to_color(dynamic_colors.on_error_container().get_argb(&scheme)),
background: argb_to_color(dynamic_colors.background().get_argb(&scheme)),
on_background: argb_to_color(dynamic_colors.on_background().get_argb(&scheme)),
surface: argb_to_color(dynamic_colors.surface().get_argb(&scheme)),
on_surface: argb_to_color(dynamic_colors.on_surface().get_argb(&scheme)),
surface_variant: argb_to_color(dynamic_colors.surface_variant().get_argb(&scheme)),
on_surface_variant: argb_to_color(dynamic_colors.on_surface_variant().get_argb(&scheme)),
outline: argb_to_color(dynamic_colors.outline().get_argb(&scheme)),
outline_variant: argb_to_color(dynamic_colors.outline_variant().get_argb(&scheme)),
shadow: argb_to_color(dynamic_colors.shadow().get_argb(&scheme)),
scrim: argb_to_color(dynamic_colors.scrim().get_argb(&scheme)),
surface_tint: argb_to_color(dynamic_colors.surface_tint().get_argb(&scheme)),
inverse_surface: argb_to_color(dynamic_colors.inverse_surface().get_argb(&scheme)),
inverse_on_surface: argb_to_color(dynamic_colors.inverse_on_surface().get_argb(&scheme)),
inverse_primary: argb_to_color(dynamic_colors.inverse_primary().get_argb(&scheme)),
surface_bright: argb_to_color(dynamic_colors.surface_bright().get_argb(&scheme)),
surface_dim: argb_to_color(dynamic_colors.surface_dim().get_argb(&scheme)),
surface_container: argb_to_color(dynamic_colors.surface_container().get_argb(&scheme)),
surface_container_high: argb_to_color(
dynamic_colors.surface_container_high().get_argb(&scheme),
),
surface_container_highest: argb_to_color(
dynamic_colors.surface_container_highest().get_argb(&scheme),
),
surface_container_low: argb_to_color(
dynamic_colors.surface_container_low().get_argb(&scheme),
),
surface_container_lowest: argb_to_color(
dynamic_colors.surface_container_lowest().get_argb(&scheme),
),
primary_fixed: argb_to_color(dynamic_colors.primary_fixed().get_argb(&scheme)),
primary_fixed_dim: argb_to_color(dynamic_colors.primary_fixed_dim().get_argb(&scheme)),
on_primary_fixed: argb_to_color(dynamic_colors.on_primary_fixed().get_argb(&scheme)),
on_primary_fixed_variant: argb_to_color(
dynamic_colors.on_primary_fixed_variant().get_argb(&scheme),
),
secondary_fixed: argb_to_color(dynamic_colors.secondary_fixed().get_argb(&scheme)),
secondary_fixed_dim: argb_to_color(dynamic_colors.secondary_fixed_dim().get_argb(&scheme)),
on_secondary_fixed: argb_to_color(dynamic_colors.on_secondary_fixed().get_argb(&scheme)),
on_secondary_fixed_variant: argb_to_color(
dynamic_colors
.on_secondary_fixed_variant()
.get_argb(&scheme),
),
tertiary_fixed: argb_to_color(dynamic_colors.tertiary_fixed().get_argb(&scheme)),
tertiary_fixed_dim: argb_to_color(dynamic_colors.tertiary_fixed_dim().get_argb(&scheme)),
on_tertiary_fixed: argb_to_color(dynamic_colors.on_tertiary_fixed().get_argb(&scheme)),
on_tertiary_fixed_variant: argb_to_color(
dynamic_colors.on_tertiary_fixed_variant().get_argb(&scheme),
),
}
}
fn linear_to_srgb_channel(v: f32) -> f32 {
let v = v.clamp(0.0, 1.0);
if v <= 0.003_130_8 {
v * 12.92
} else {
1.055 * v.powf(1.0 / 2.4) - 0.055
}
}
fn srgb_to_linear_channel(v: f32) -> f32 {
let v = v.clamp(0.0, 1.0);
if v <= 0.04045 {
v / 12.92
} else {
((v + 0.055) / 1.055).powf(2.4)
}
}
fn color_to_argb(color: Color) -> u32 {
let r = (linear_to_srgb_channel(color.r) * 255.0 + 0.5) as u32;
let g = (linear_to_srgb_channel(color.g) * 255.0 + 0.5) as u32;
let b = (linear_to_srgb_channel(color.b) * 255.0 + 0.5) as u32;
let a = (color.a.clamp(0.0, 1.0) * 255.0 + 0.5) as u32;
(a << 24) | (r << 16) | (g << 8) | b
}
fn argb_to_color(argb: u32) -> Color {
let a = ((argb >> 24) & 0xFF) as f32 / 255.0;
let r_srgb = ((argb >> 16) & 0xFF) as f32 / 255.0;
let g_srgb = ((argb >> 8) & 0xFF) as f32 / 255.0;
let b_srgb = (argb & 0xFF) as f32 / 255.0;
Color::new(
srgb_to_linear_channel(r_srgb),
srgb_to_linear_channel(g_srgb),
srgb_to_linear_channel(b_srgb),
a,
)
}