use std::collections::HashMap;
pub use ribir_algo::{CowArc, Resource};
use smallvec::SmallVec;
use crate::{fill_svgs, prelude::*};
mod palette;
pub use palette::*;
mod icon_theme;
pub use icon_theme::*;
mod typography_theme;
pub use typography_theme::*;
mod transition_theme;
pub use transition_theme::*;
mod compose_decorators;
pub use compose_decorators::*;
mod custom_styles;
pub use custom_styles::*;
pub use ribir_painter::*;
pub use ribir_text::{FontFace, FontFamily, FontSize, FontWeight, Pixel};
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Brightness {
Dark,
Light,
}
pub struct Theme {
pub palette: Palette,
pub typography_theme: TypographyTheme,
pub icon_theme: IconTheme,
pub transitions_theme: TransitionTheme,
pub compose_decorators: ComposeDecorators,
pub custom_styles: CustomStyles,
pub font_bytes: Option<Vec<Vec<u8>>>,
pub font_files: Option<Vec<String>>,
}
impl Theme {
pub fn of(ctx: &impl ProviderCtx) -> QueryRef<Theme> {
Provider::of(ctx).unwrap()
}
pub fn write_of(ctx: &impl ProviderCtx) -> WriteRef<Theme> {
Provider::write_of(ctx).unwrap()
}
fn load_fonts(&mut self) {
let mut font_db = AppCtx::font_db().borrow_mut();
let Theme { font_bytes, font_files, .. } = self;
if let Some(font_bytes) = font_bytes {
font_bytes
.iter()
.for_each(|data| font_db.load_from_bytes(data.clone()));
}
if let Some(font_files) = font_files {
font_files.iter().for_each(|path| {
let _ = font_db.load_font_file(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::*;
this.silent().load_fonts();
let w = this.clone_watcher();
let theme = ThemeQuerier(this.clone_writer());
Provider::new(Box::new(theme))
.with_child(fn_widget! {
pipe!($w;).map(move |_| child.gen_widget())
})
.into_widget()
}
}
struct ThemeQuerier<T: StateWriter<Value = Theme>>(T);
impl<T: StateWriter<Value = Theme>> Query for ThemeQuerier<T> {
fn query_all<'q>(&'q self, type_id: TypeId, out: &mut SmallVec<[QueryHandle<'q>; 1]>) {
if let Some(h) = self.query(type_id) {
out.push(h)
}
}
fn query(&self, type_id: TypeId) -> Option<QueryHandle> {
ReadRef::filter_map(self.0.read(), |v: &mut Theme| {
let w: Option<&mut dyn Any> = if TypeId::of::<Theme>() == type_id {
Some(v)
} else if TypeId::of::<Palette>() == type_id {
Some(&mut v.palette)
} else if TypeId::of::<TypographyTheme>() == type_id {
Some(&mut v.typography_theme)
} else if TypeId::of::<IconTheme>() == type_id {
Some(&mut v.icon_theme)
} else if TypeId::of::<TransitionTheme>() == type_id {
Some(&mut v.transitions_theme)
} else if TypeId::of::<ComposeDecorators>() == type_id {
Some(&mut v.compose_decorators)
} else if TypeId::of::<CustomStyles>() == type_id {
Some(&mut v.custom_styles)
} else {
None
};
w.map(PartData::from_ref_mut)
})
.ok()
.map(QueryHandle::from_read_ref)
}
fn query_write(&self, type_id: TypeId) -> Option<QueryHandle> {
WriteRef::filter_map(self.0.write(), |v: &mut Theme| {
let w: Option<&mut dyn Any> = if TypeId::of::<Theme>() == type_id {
Some(v)
} else if TypeId::of::<Palette>() == type_id {
Some(&mut v.palette)
} else if TypeId::of::<TypographyTheme>() == type_id {
Some(&mut v.typography_theme)
} else if TypeId::of::<IconTheme>() == type_id {
Some(&mut v.icon_theme)
} else if TypeId::of::<TransitionTheme>() == type_id {
Some(&mut v.transitions_theme)
} else if TypeId::of::<ComposeDecorators>() == type_id {
Some(&mut v.compose_decorators)
} else if TypeId::of::<CustomStyles>() == type_id {
Some(&mut v.custom_styles)
} else {
None
};
w.map(PartData::from_ref_mut)
})
.ok()
.map(QueryHandle::from_write_ref)
}
fn queryable(&self) -> bool { true }
}
impl Default for Theme {
fn default() -> Self {
let icon_size = IconSize {
tiny: Size::new(18., 18.),
small: Size::new(24., 24.),
medium: Size::new(36., 36.),
large: Size::new(48., 48.),
huge: Size::new(64., 64.),
};
let regular_family = Box::new([FontFamily::Name(std::borrow::Cow::Borrowed("Lato"))]);
let medium_family = Box::new([FontFamily::Name(std::borrow::Cow::Borrowed("Lato"))]);
let typography_theme = typography_theme(
regular_family,
medium_family,
TextDecoration::NONE,
Color::BLACK.with_alpha(0.87).into(),
);
let mut icon_theme = IconTheme::new(icon_size);
fill_svgs! {
icon_theme,
svgs::ADD: "./icons/add_FILL0_wght400_GRAD0_opsz48.svg",
svgs::ARROW_BACK: "./icons/arrow_back_FILL0_wght400_GRAD0_opsz48.svg",
svgs::ARROW_DROP_DOWN: "./icons/arrow_drop_down_FILL0_wght400_GRAD0_opsz48.svg",
svgs::ARROW_FORWARD: "./icons/arrow_forward_FILL0_wght400_GRAD0_opsz48.svg",
svgs::CANCEL: "./icons/cancel_FILL0_wght400_GRAD0_opsz48.svg",
svgs::CHECK_BOX: "./icons/check_box_FILL0_wght400_GRAD0_opsz48.svg",
svgs::CHECK_BOX_OUTLINE_BLANK: "./icons/check_box_outline_blank_FILL0_wght400_GRAD0_opsz48.svg",
svgs::CHEVRON_RIGHT: "./icons/chevron_right_FILL0_wght400_GRAD0_opsz48.svg",
svgs::CLOSE: "./icons/close_FILL0_wght400_GRAD0_opsz48.svg",
svgs::DELETE: "./icons/delete_FILL0_wght400_GRAD0_opsz48.svg",
svgs::DONE: "./icons/done_FILL0_wght400_GRAD0_opsz48.svg",
svgs::EXPAND_MORE: "./icons/expand_more_FILL0_wght400_GRAD0_opsz48.svg",
svgs::FAVORITE: "./icons/favorite_FILL0_wght400_GRAD0_opsz48.svg",
svgs::HOME: "./icons/home_FILL0_wght400_GRAD0_opsz48.svg",
svgs::INDETERMINATE_CHECK_BOX: "./icons/indeterminate_check_box_FILL0_wght400_GRAD0_opsz48.svg",
svgs::LOGIN: "./icons/login_FILL0_wght400_GRAD0_opsz48.svg",
svgs::LOGOUT: "./icons/logout_FILL0_wght400_GRAD0_opsz48.svg",
svgs::MENU: "./icons/menu_FILL0_wght400_GRAD0_opsz48.svg",
svgs::MORE_HORIZ: "./icons/more_horiz_FILL0_wght400_GRAD0_opsz48.svg",
svgs::MORE_VERT: "./icons/more_vert_FILL0_wght400_GRAD0_opsz48.svg",
svgs::OPEN_IN_NEW: "./icons/open_in_new_FILL0_wght400_GRAD0_opsz48.svg",
svgs::SEARCH: "./icons/search_FILL0_wght400_GRAD0_opsz48.svg",
svgs::SETTINGS: "./icons/settings_FILL0_wght400_GRAD0_opsz48.svg",
svgs::STAR: "./icons/star_FILL0_wght400_GRAD0_opsz48.svg",
svgs::TEXT_CARET: "./icons/text_caret.svg"
};
Theme {
palette: Default::default(),
typography_theme,
icon_theme,
transitions_theme: Default::default(),
compose_decorators: Default::default(),
custom_styles: Default::default(),
font_bytes: None,
font_files: None,
}
}
}
fn typography_theme(
regular_family: Box<[FontFamily]>, medium_family: Box<[FontFamily]>, decoration: TextDecoration,
decoration_color: Brush,
) -> TypographyTheme {
let decoration = TextDecorationStyle { decoration, decoration_color };
let regular_face =
FontFace { families: regular_family.clone(), weight: FontWeight::NORMAL, ..<_>::default() };
let medium_face =
FontFace { families: medium_family, weight: FontWeight::MEDIUM, ..<_>::default() };
TypographyTheme {
display_large: TextTheme {
text: CowArc::owned(TextStyle {
line_height: Some(Em::from_pixel(64.0.into())),
font_size: FontSize::Pixel(57.0.into()),
letter_space: Some(0.0.into()),
font_face: regular_face.clone(),
}),
decoration: decoration.clone(),
},
display_medium: TextTheme {
text: CowArc::owned(TextStyle {
line_height: Some(Em::from_pixel(52.0.into())),
font_size: FontSize::Pixel(45.0.into()),
letter_space: Some(0.0.into()),
font_face: regular_face.clone(),
}),
decoration: decoration.clone(),
},
display_small: TextTheme {
text: CowArc::owned(TextStyle {
line_height: Some(Em::from_pixel(44.0.into())),
font_size: FontSize::Pixel(36.0.into()),
letter_space: Some(0.0.into()),
font_face: regular_face.clone(),
}),
decoration: decoration.clone(),
},
headline_large: TextTheme {
text: CowArc::owned(TextStyle {
line_height: Some(Em::from_pixel(40.0.into())),
font_size: FontSize::Pixel(32.0.into()),
letter_space: Some(0.0.into()),
font_face: regular_face.clone(),
}),
decoration: decoration.clone(),
},
headline_medium: TextTheme {
text: CowArc::owned(TextStyle {
line_height: Some(Em::from_pixel(36.0.into())),
font_size: FontSize::Pixel(28.0.into()),
letter_space: Some(0.0.into()),
font_face: regular_face.clone(),
}),
decoration: decoration.clone(),
},
headline_small: TextTheme {
text: CowArc::owned(TextStyle {
line_height: Some(Em::from_pixel(32.0.into())),
font_size: FontSize::Pixel(24.0.into()),
letter_space: Some(0.0.into()),
font_face: regular_face.clone(),
}),
decoration: decoration.clone(),
},
title_large: TextTheme {
text: CowArc::owned(TextStyle {
line_height: Some(Em::from_pixel(28.0.into())),
font_size: FontSize::Pixel(22.0.into()),
letter_space: Some(0.0.into()),
font_face: medium_face.clone(),
}),
decoration: decoration.clone(),
},
title_medium: TextTheme {
text: CowArc::owned(TextStyle {
line_height: Some(Em::from_pixel(24.0.into())),
font_size: FontSize::Pixel(16.0.into()),
letter_space: Some(0.15.into()),
font_face: medium_face.clone(),
}),
decoration: decoration.clone(),
},
title_small: TextTheme {
text: CowArc::owned(TextStyle {
line_height: Some(Em::from_pixel(20.0.into())),
font_size: FontSize::Pixel(14.0.into()),
letter_space: Some(0.1.into()),
font_face: medium_face.clone(),
}),
decoration: decoration.clone(),
},
label_large: TextTheme {
text: CowArc::owned(TextStyle {
line_height: Some(Em::from_pixel(20.0.into())),
font_size: FontSize::Pixel(14.0.into()),
letter_space: Some(0.1.into()),
font_face: medium_face.clone(),
}),
decoration: decoration.clone(),
},
label_medium: TextTheme {
text: CowArc::owned(TextStyle {
line_height: Some(Em::from_pixel(16.0.into())),
font_size: FontSize::Pixel(12.0.into()),
letter_space: Some(0.5.into()),
font_face: medium_face.clone(),
}),
decoration: decoration.clone(),
},
label_small: TextTheme {
text: CowArc::owned(TextStyle {
line_height: Some(Em::from_pixel(16.0.into())),
font_size: FontSize::Pixel(11.0.into()),
letter_space: Some(0.5.into()),
font_face: medium_face,
}),
decoration: decoration.clone(),
},
body_large: TextTheme {
text: CowArc::owned(TextStyle {
line_height: Some(Em::from_pixel(24.0.into())),
font_size: FontSize::Pixel(16.0.into()),
letter_space: Some(0.5.into()),
font_face: regular_face.clone(),
}),
decoration: decoration.clone(),
},
body_medium: TextTheme {
text: CowArc::owned(TextStyle {
line_height: Some(Em::from_pixel(20.0.into())),
font_size: FontSize::Pixel(14.0.into()),
letter_space: Some(0.25.into()),
font_face: regular_face.clone(),
}),
decoration: decoration.clone(),
},
body_small: TextTheme {
text: CowArc::owned(TextStyle {
line_height: Some(Em::from_pixel(16.0.into())),
font_size: FontSize::Pixel(12.0.into()),
letter_space: Some(0.4.into()),
font_face: regular_face,
}),
decoration,
},
}
}
#[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! {
$writer.write().push(Palette::of(ctx!()).brightness);
let writer = writer.clone_writer();
Palette { brightness: Brightness::Dark, ..Default::default() }
.with_child(fn_widget!{
$writer.write().push(Palette::of(ctx!()).brightness);
let writer = writer.clone_writer();
Palette { brightness: Brightness::Light, ..Default::default() }
.with_child(fn_widget!{
$writer.write().push(Palette::of(ctx!()).brightness);
Void
})
})
})
};
let mut wnd = TestWindow::new(w);
wnd.draw_frame();
assert_eq!(*watcher.read(), [Brightness::Light, Brightness::Dark, Brightness::Light]);
}
}