pub use ribir_algo::{CowArc, Resource};
use smallvec::{SmallVec, smallvec};
use crate::{prelude::*, text::LineHeight};
mod palette;
pub use palette::*;
mod typography_theme;
pub use ribir_painter::*;
pub use typography_theme::*;
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Brightness {
Dark,
Light,
}
pub struct Theme {
pub palette: Palette,
pub typography_theme: TypographyTheme,
pub classes: Classes,
pub font_bytes: Vec<Vec<u8>>,
pub font_files: Vec<String>,
pub icon_font: IconFont,
}
#[derive(Clone, Debug, Default)]
pub struct IconFont(pub FontFace);
#[derive(Clone)]
#[repr(transparent)]
pub struct ContainerColor(pub Color);
impl ContainerColor {
pub fn provider(color: Color) -> Provider { Provider::new(ContainerColor(color)) }
}
impl Theme {
pub fn of(ctx: &impl AsRef<ProviderCtx>) -> QueryRef<'_, Self> { Provider::of(ctx).unwrap() }
pub fn write_of(ctx: &impl AsRef<ProviderCtx>) -> WriteRef<'_, Self> {
Provider::write_of(ctx).unwrap()
}
pub(crate) fn preprocess_before_compose(
this: impl StateWriter<Value = Self>, child: GenWidget,
) -> (SmallVec<[Provider; 1]>, Widget<'static>) {
fn load_fonts(theme: &impl StateWriter<Value = Theme>) {
let mut t = theme.write();
t.load_fonts();
t.forget_modifies();
}
load_fonts(&this);
let container_color =
this.part_reader(|t| PartRef::from_value(ContainerColor(t.palette.secondary_container())));
let default_text_style = this.part_reader(|theme| {
let mut text = theme.typography_theme.body_medium.text.clone();
text.line_height = LineHeight::default();
PartRef::from_value(text)
});
let providers = smallvec![
Provider::writer(this.clone_writer(), None),
Provider::reader(part_reader!(&this.palette.primary)),
Provider::reader(container_color),
Provider::reader(default_text_style),
Provider::reader(part_reader!(&this.palette)),
Provider::reader(part_reader!(&this.typography_theme)),
Classes::reader_into_provider(part_reader!(&this.classes)),
Provider::reader(part_reader!(&this.icon_font))
];
let child = pipe!($read(this);)
.map(move |_| {
load_fonts(&this);
child.clone()
})
.into_widget();
(providers, child)
}
fn load_fonts(&mut self) {
let text_services = AppCtx::text_services();
let Theme { font_bytes, font_files, .. } = self;
font_bytes.drain(..).for_each(|data| {
let _ = text_services.register_font_bytes(data);
});
font_files.drain(..).for_each(|path| {
let _ = text_services.register_font_file(std::path::Path::new(&path));
});
}
}
impl ComposeChild<'static> for Theme {
type Child = GenWidget;
fn compose_child(this: impl StateWriter<Value = Self>, child: Self::Child) -> Widget<'static> {
use crate::prelude::*;
let (providers, child) = Theme::preprocess_before_compose(this, child);
Providers::new(providers).with_child(child)
}
}
impl Default for Theme {
fn default() -> Self {
Theme {
palette: Palette::default(),
typography_theme: typography_theme(),
classes: <_>::default(),
font_bytes: vec![],
font_files: vec![],
icon_font: Default::default(),
}
}
}
fn typography_theme() -> TypographyTheme {
fn text_theme(line_height: f32, font_size: f32, letter_space: f32) -> TextTheme {
let mut families = vec![FontFamily::Name(std::borrow::Cow::Borrowed("Lato"))];
families.extend(
fallback_font_families()
.iter()
.copied()
.map(|family| FontFamily::Name(std::borrow::Cow::Borrowed(family))),
);
families.push(FontFamily::SansSerif);
families.push(FontFamily::Serif);
let font_face = FontFace {
families: families.into_boxed_slice(),
weight: FontWeight::NORMAL,
..<_>::default()
};
let overflow = TextOverflow::Overflow;
TextTheme {
text: TextStyle {
line_height: line_height.into(),
font_size,
letter_space,
font_face,
overflow,
},
decoration: TextDecorationStyle {
decoration: TextDecoration::NONE,
decoration_color: Some(Color::BLACK.with_alpha(0.87)),
},
}
}
TypographyTheme {
display_large: text_theme(64., 57., 0.),
display_medium: text_theme(52., 45., 0.),
display_small: text_theme(44., 36., 0.),
headline_large: text_theme(40., 32., 0.),
headline_medium: text_theme(36., 28., 0.),
headline_small: text_theme(32., 24., 0.),
title_large: text_theme(28., 22., 0.),
title_medium: text_theme(24., 16., 0.15),
title_small: text_theme(20., 14., 0.1),
label_large: text_theme(20., 14., 0.1),
label_medium: text_theme(16., 12., 0.5),
label_small: text_theme(16., 11., 0.5),
body_large: text_theme(24., 16., 0.5),
body_medium: text_theme(20., 14., 0.25),
body_small: text_theme(16., 12., 0.4),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{reset_test_env, test_helper::*};
#[test]
fn themes() {
reset_test_env!();
let (watcher, writer) = split_value(vec![]);
let w = fn_widget! {
let writer = writer.clone_writer();
let mut theme = Theme::default();
theme.palette.brightness = Brightness::Light;
theme.with_child(fn_widget! {
$write(writer).push(Palette::of(BuildCtx::get()).brightness);
let writer = writer.clone_writer();
let palette = Palette { brightness: Brightness::Dark, ..Default::default() };
@Providers {
providers: [Provider::new(palette)],
@ {
$write(writer).push(Palette::of(BuildCtx::get()).brightness);
let writer = writer.clone_writer();
let palette = Palette { brightness: Brightness::Light, ..Default::default() };
@Providers {
providers: [Provider::new(palette)],
@ {
$write(writer).push(Palette::of(BuildCtx::get()).brightness);
Void::default()
}
}
}
}
})
};
let wnd = TestWindow::from_widget(w);
wnd.draw_frame();
assert_eq!(*watcher.read(), [Brightness::Light, Brightness::Dark, Brightness::Light]);
}
}