diff --git a/core/src/builtin_widgets/theme.rs b/core/src/builtin_widgets/theme.rs index 5eb26eaeb..eb01c968c 100644 --- a/core/src/builtin_widgets/theme.rs +++ b/core/src/builtin_widgets/theme.rs @@ -2,12 +2,13 @@ //! to app-wide or particular part of the application. use crate::{fill_svgs, prelude::*, widget::WidgetBuilder}; +use ahash::{HashMap, HashSet}; use ribir_algo::Sc; pub use ribir_algo::{CowArc, ShareResource}; use ribir_geom::Size; use ribir_macros::Declare; -use ribir_text::TextStyle; -use std::{collections::HashMap, rc::Rc}; +use ribir_text::{font_db::ID, TextStyle}; +use std::{rc::Rc, vec}; mod palette; pub use palette::*; @@ -54,7 +55,7 @@ pub struct InheritTheme { /// icon size standard pub icon_size: Option, /// a collection of icons. - pub icons: Option, ahash::RandomState>>, + pub icons: Option>>, pub transitions_theme: Option, pub compose_decorators: Option, pub custom_styles: Option, @@ -88,13 +89,7 @@ impl ComposeChild for ThemeWidget { let mut themes = ctx!().themes().clone(); themes.push(theme.clone()); - // Keep the empty node, because the subtree may be hold its id. - // - // And not try to swap child and the empty node, and remove the child - // node, because the subtree may be hold its id. - // - // A `Void` is cheap for a theme. - let p = Void.widget_build(ctx!()).attach_data(theme, ctx!()); + let p = ThemeRender{ theme: theme.clone() }.widget_build(ctx!()).attach_data(theme, ctx!()); // shadow the context with the theme. let ctx = BuildCtx::new_with_data(Some(p.id()), ctx!().tree, themes); let child = child.gen_widget(&ctx); @@ -105,6 +100,53 @@ impl ComposeChild for ThemeWidget { } } +// ThemeRender will install default font for the subtree in the +// perform_layout. +#[derive(Query, SingleChild)] +struct ThemeRender { + theme: Sc, +} + +impl Render for ThemeRender { + #[inline] + fn perform_layout(&self, clamp: BoxClamp, ctx: &mut LayoutCtx) -> Size { + let mut fonts = vec![]; + let font_db = AppCtx::font_db().clone(); + let font_families = match self.theme.deref() { + Theme::Full(f) => Some(f.typography_theme.default_font_family.clone()), + Theme::Inherit(i) => i + .typography_theme + .as_ref() + .map(|typography_theme| typography_theme.default_font_family.clone()), + }; + + if let Some(families) = font_families { + fonts = font_db + .borrow_mut() + .select_all_match(&FontFace { families, ..<_>::default() }); + } + if !fonts.is_empty() { + // install default font for the subtree. + let old_fonts = font_db.borrow().default_fonts().to_vec(); + let mut set: HashSet = HashSet::from_iter(fonts.iter().cloned()); + for font in old_fonts { + if set.insert(font) { + fonts.push(font); + } + } + font_db.borrow_mut().set_default_fonts(fonts); + } + if let Some(mut l) = ctx.single_child_layouter() { + l.perform_widget_layout(clamp) + } else { + Size::zero() + } + } + + #[inline] + fn paint(&self, _: &mut PaintingCtx) {} +} + impl Default for Theme { fn default() -> Self { Theme::Full(<_>::default()) } } @@ -339,3 +381,65 @@ impl From for Theme { #[inline] fn from(value: InheritTheme) -> Self { Theme::Inherit(value) } } + +#[cfg(test)] +mod tests { + use std::cell::RefCell; + + use super::*; + use crate::{reset_test_env, test_helper::*}; + + #[derive(Query, Declare)] + struct QueryFont { + ids: Rc>>, + } + + impl Render for QueryFont { + fn perform_layout(&self, _: BoxClamp, _: &mut LayoutCtx) -> Size { + *self.ids.borrow_mut() = AppCtx::font_db().borrow().default_fonts().to_vec(); + Size::zero() + } + fn paint(&self, _: &mut PaintingCtx) {} + } + #[test] + fn theme_font() { + reset_test_env!(); + + let font_db = AppCtx::font_db().clone(); + let font_ids = Rc::new(RefCell::new(vec![])); + let font_ids2 = font_ids.clone(); + let w = fn_widget! { + let mut typography_theme = TypographyTheme::of(ctx!()).clone(); + typography_theme.default_font_family = Box::new([FontFamily::Name("DejaVu Sans".into())]); + @ThemeWidget { + theme: Sc::new(Theme::Inherit(InheritTheme { + typography_theme: Some(typography_theme), + font_files: Some(vec![env!("CARGO_MANIFEST_DIR").to_owned() + "/../fonts/DejaVuSans.ttf"]), + ..<_>::default() + })), + @{ + Box::new(fn_widget!{ + @QueryFont { + ids: font_ids2.clone(), + } + }) + } + } + }; + + let old_path = font_db.borrow().default_fonts().to_vec(); + let mut wnd = TestWindow::new(w); + wnd.draw_frame(); + + let ids = font_db.borrow_mut().select_all_match(&FontFace { + families: Box::new([ + FontFamily::Name("DejaVu Sans".into()), + FontFamily::Name("Lato".into()), + ]), + ..<_>::default() + }); + + assert_eq!(*font_ids.borrow(), ids); + assert_eq!(&old_path, font_db.borrow().default_fonts()); + } +} diff --git a/core/src/context/build_ctx.rs b/core/src/context/build_ctx.rs index c3f482682..45e3f7c0d 100644 --- a/core/src/context/build_ctx.rs +++ b/core/src/context/build_ctx.rs @@ -66,7 +66,7 @@ impl<'a> BuildCtx<'a> { } pub(crate) fn find_cfg(&self, f: impl Fn(&Theme) -> Option<&T>) -> Option<&T> { - for t in self.themes().iter().rev() { + for t in self.themes().iter() { let v = f(t); if v.is_some() { return v; @@ -178,7 +178,6 @@ mod tests { theme: Sc::new(Theme::Inherit(InheritTheme { palette: Some(Rc::new(light_palette)), ..<_>::default() - })), @ { Box::new(fn_widget!{ diff --git a/core/src/widget_tree.rs b/core/src/widget_tree.rs index 02791d890..60a2a10ff 100644 --- a/core/src/widget_tree.rs +++ b/core/src/widget_tree.rs @@ -7,6 +7,8 @@ use std::{ }; pub mod widget_id; +use ribir_algo::Sc; +use ribir_text::font_db::ID; pub(crate) use widget_id::TreeArena; pub use widget_id::WidgetId; mod layout_info; @@ -56,6 +58,8 @@ impl WidgetTree { continue; } + let _guard = DefaultFontGuard::install_default_font(self, wid); + let clamp = self .store .layout_info(wid) @@ -203,6 +207,65 @@ impl WidgetTree { } } +struct DefaultFontGuard { + old_ids: Vec, +} +impl Drop for DefaultFontGuard { + fn drop(&mut self) { + let font_db = AppCtx::font_db().clone(); + font_db.borrow_mut().set_default_fonts(self.old_ids.clone()); + } +} + +impl DefaultFontGuard { + fn install_default_font(tree: &WidgetTree, wid: WidgetId) -> Self { + fn add_font( + fonts: &mut Vec, + set: &mut HashSet, + new_fonts: &[FontFamily], + ) { + for font in new_fonts { + if set.insert(font.clone()) { + fonts.push(font.clone()); + } + } + } + + let mut fonts = vec![]; + let mut set: HashSet = HashSet::default(); + let arena = &tree.arena; + wid.ancestors(arena).skip(1).for_each(|p| { + p.assert_get(arena) + .query_type_inside_first(|t: &Sc| { + match t.deref() { + Theme::Full(f) => add_font( + &mut fonts, + &mut set, + &f.typography_theme.default_font_family, + ), + Theme::Inherit(i) => { + if let Some(f) = &i.typography_theme { + add_font(&mut fonts, &mut set, &f.default_font_family); + } + } + } + false + }); + }); + + let font_db = AppCtx::font_db().clone(); + let old_ids = font_db.borrow().default_fonts().to_vec(); + let mut ids = font_db.borrow_mut().select_all_match(&FontFace { + families: fonts.into_boxed_slice(), + ..<_>::default() + }); + let set: HashSet = HashSet::from_iter(ids.iter().cloned()); + ids.extend(old_ids.iter().filter(|id| !set.contains(id))); + font_db.borrow_mut().set_default_fonts(ids); + Self { old_ids } + } +} + impl Default for WidgetTree { fn default() -> Self { let mut arena = TreeArena::new(); diff --git a/text/src/font_db.rs b/text/src/font_db.rs index 5c61b46a7..c0f775ad8 100644 --- a/text/src/font_db.rs +++ b/text/src/font_db.rs @@ -12,7 +12,7 @@ use crate::svg_glyph_cache::SvgGlyphCache; use crate::{FontFace, FontFamily}; /// A wrapper of fontdb and cache font data. pub struct FontDB { - default_font: ID, + default_fonts: Vec, data_base: fontdb::Database, cache: HashMap>, } @@ -31,9 +31,9 @@ pub struct Face { } impl FontDB { - pub fn set_default_font(&mut self, face_id: ID) { self.default_font = face_id; } + pub fn set_default_fonts(&mut self, ids: Vec) { self.default_fonts = ids; } - pub fn default_font(&self) -> ID { self.default_font } + pub fn default_fonts(&self) -> &[ID] { &self.default_fonts } pub fn try_get_face_data(&self, face_id: ID) -> Option<&Face> { self.cache.get(&face_id)?.as_ref() @@ -248,7 +248,7 @@ impl Default for FontDB { data_base.load_font_data(include_bytes!("../Lato-Regular.ttf").to_vec()); let default_font = data_base.faces().next().map(|f| f.id).unwrap(); let mut this = FontDB { - default_font, + default_fonts: vec![default_font], data_base, cache: <_>::default(), }; diff --git a/text/src/shaper.rs b/text/src/shaper.rs index 7994dc015..e0dc3b546 100644 --- a/text/src/shaper.rs +++ b/text/src/shaper.rs @@ -310,37 +310,54 @@ impl From for rustybuzz::Direction { #[derive(Clone)] struct FallBackFaceHelper<'a> { - ids: &'a [ID], + ids: Vec, font_db: &'a RefCell, face_idx: usize, } impl<'a> FallBackFaceHelper<'a> { - fn new(ids: &'a [ID], font_db: &'a RefCell) -> Self { Self { ids, font_db, face_idx: 0 } } + fn new(ids: &'a [ID], font_db: &'a RefCell) -> Self { + let mut ids = ids.to_vec(); + let set: ahash::HashSet = ahash::HashSet::from_iter(ids.iter().cloned()); + + { + let font_db = font_db.borrow(); + let default_ids = font_db.default_fonts(); + for id in default_ids.iter() { + if set.contains(id) { + continue; + } + ids.push(*id); + } + } + + Self { ids, font_db, face_idx: 0 } + } fn next_fallback_face(&mut self, text: &str) -> Option { let font_db = self.font_db.borrow(); loop { - if self.face_idx > self.ids.len() { + if self.face_idx >= self.ids.len() { return None; } - let face_idx = self.face_idx; - self.face_idx += 1; - if face_idx == self.ids.len() { - return font_db.try_get_face_data(font_db.default_font()).cloned(); - } let face = self .ids - .get(face_idx) + .get(self.face_idx) .and_then(|id| font_db.try_get_face_data(*id)) - .filter(|f| match text.is_empty() { - true => true, - false => text.chars().any(|c| f.has_char(c)), - }) .cloned(); - if face.is_some() { + + self.face_idx += 1; + if self.face_idx == self.ids.len() { return face; + } else { + let face = face.filter(|f| match text.is_empty() { + true => true, + false => text.chars().any(|c| f.has_char(c)), + }); + if face.is_some() { + return face; + } } } } diff --git a/text/src/svg_glyph_cache.rs b/text/src/svg_glyph_cache.rs index b22ff665d..337fafe73 100644 --- a/text/src/svg_glyph_cache.rs +++ b/text/src/svg_glyph_cache.rs @@ -269,7 +269,7 @@ mod tests { "##; let doc = super::SvgDocument::new(GlyphId(2428)..=GlyphId(2428), content.as_bytes()); let mut db = FontDB::default(); - let dummy_face = db.face_data_or_insert(db.default_font()).unwrap(); + let dummy_face = db.face_data_or_insert(db.default_fonts()[0]).unwrap(); assert_eq!(doc.elems.len(), 4); assert!( doc