Dynamic font loading

Closes #145
This commit is contained in:
Jonathan Johnson 2024-03-06 16:53:36 -08:00
parent 7a9ddaa926
commit 3762bc6dc1
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
12 changed files with 498 additions and 75 deletions

View file

@ -53,6 +53,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `ColorExt::into_source_and_lightness` has been renamed to
`ColorExt::into_hsl`, and its return type is now `Hsl` instead of the
individual components.
- `Window::font_data_to_load` has been renamed to `fonts`, and now has the
`FontCollection` type.
- Several font-related functions have been moved from `GraphicsContext` to
`WidgetContext`:
- `GraphicsContext::set_font_family()`
- `GraphicsContext::find_available_font_family()`
- `GraphicsContext::set_available_font_family()`
### Fixed
@ -218,6 +226,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `List` is a new widget that creates lists similar to HTML's `ol` and `ul`
tags.
- `Dynamic::try_lock()` is a panic-free version of `Dynamic::lock()`.
- `FontCollection` is a new type that can be used to load fonts at app/window
startup or at runtime.
- `Cushy::fonts()`returns a `FontCollection` that is loaded into all windows.
- `WidgetContext::loaded_font_faces()` returns a list of fonts that were loaded
for a given `LoadedFont`.
- `Graphics::font_system()` returns a reference to the underlying Cosmic Text
`FontSystem`.
[plotters]: https://github.com/plotters-rs/plotters

6
Cargo.lock generated
View file

@ -321,9 +321,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.0.89"
version = "1.0.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0ba8f7aaa012f30d5b2861462f6708eccd49c3c39863fe083a308035f63d723"
checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5"
dependencies = [
"jobserver",
"libc",
@ -1123,7 +1123,7 @@ checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
[[package]]
name = "kludgine"
version = "0.7.0"
source = "git+https://github.com/khonsulabs/kludgine#eff686f12918a52321a63c3cb2abbbd68be45068"
source = "git+https://github.com/khonsulabs/kludgine#f2bc0d6ea9ebe2709ab208723fc7a072cf87164f"
dependencies = [
"ahash",
"alot",

View file

@ -48,7 +48,7 @@ nominals = "0.3.0"
# [patch.crates-io]
# kludgine = { path = "../kludgine" }
# cosmic-text = { path = "../cosmic-text" }
# appit = { path = "../appit" }
# figures = { path = "../figures" }
# alot = { git = "https://github.com/khonsulabs/alot" }

56
examples/dynamic-fonts.rs Normal file
View file

@ -0,0 +1,56 @@
use cushy::fonts::FontCollection;
use cushy::styles::components::{FontFamily, FontWeight, LineHeight, TextSize};
use cushy::styles::{Component, DynamicComponent, FamilyOwned, FontFamilyList};
use cushy::value::{Dynamic, Source};
use cushy::widget::MakeWidget;
use cushy::widgets::input::InputValue;
use cushy::Run;
use figures::units::Px;
fn main() -> cushy::Result<()> {
let file_path = Dynamic::<String>::default();
let fonts = FontCollection::default();
let font_data = file_path.map_each(|path| std::fs::read(path).map_err(|err| err.to_string()));
let loaded_font = font_data.map_each({
let fonts = fonts.clone();
move |result| {
result
.as_ref()
.ok()
.map(|data| fonts.push_unloadable(data.to_vec()))
}
});
let primary_family_name = DynamicComponent::new({
let loaded_font = loaded_font.clone();
move |context| {
let font = loaded_font.get_tracking_invalidate(context)?;
let face = context.loaded_font_faces(&font).first()?;
Some(Component::custom(FontFamilyList::from(vec![
FamilyOwned::Name(face.families[0].0.clone()),
])))
}
});
let family_weight = DynamicComponent::new(move |context| {
let font = loaded_font.get_tracking_invalidate(context)?;
let face = context.loaded_font_faces(&font).first()?;
Some(Component::FontWeight(face.weight))
});
let mut window = file_path
.into_input()
.validation(font_data.clone())
.and(
"The quick brown fox jumps over the lazy dog."
.with(&TextSize, Px::new(36))
.with(&LineHeight, Px::new(36))
.with_dynamic(&FontFamily, primary_family_name)
.with_dynamic(&FontWeight, family_weight),
)
.into_rows()
.into_window();
window.fonts = fonts;
window.run()
}

View file

@ -3,6 +3,7 @@ use std::sync::{Arc, Mutex, MutexGuard};
use arboard::Clipboard;
use kludgine::app::{AppEvent, AsApplication};
use crate::fonts::FontCollection;
use crate::utils::IgnorePoison;
use crate::window::sealed::WindowCommand;
use crate::window::WindowHandle;
@ -46,6 +47,7 @@ impl AsApplication<AppEvent<WindowCommand>> for PendingApp {
#[derive(Clone)]
pub struct Cushy {
pub(crate) clipboard: Option<Arc<Mutex<Clipboard>>>,
pub(crate) fonts: FontCollection,
}
impl Cushy {
@ -54,6 +56,7 @@ impl Cushy {
clipboard: Clipboard::new()
.ok()
.map(|clipboard| Arc::new(Mutex::new(clipboard))),
fonts: FontCollection::default(),
}
}
@ -65,6 +68,12 @@ impl Cushy {
.as_ref()
.map(|mutex| mutex.lock().ignore_poison())
}
/// Returns the font collection that will be loaded in all Cushy windows.
#[must_use]
pub fn fonts(&self) -> &FontCollection {
&self.fonts
}
}
/// A type that is a Cushy application.

View file

@ -6,16 +6,18 @@ use figures::units::{Lp, Px, UPx};
use figures::{IntoSigned, Point, Px2D, Rect, Round, ScreenScale, Size, Zero};
use kludgine::app::winit::event::{Ime, MouseButton, MouseScrollDelta, TouchPhase};
use kludgine::app::winit::window::CursorIcon;
use kludgine::cosmic_text::FamilyOwned;
use kludgine::shapes::{Shape, StrokeOptions};
use kludgine::{Color, Kludgine, KludgineId};
use crate::animation::ZeroToOne;
use crate::graphics::Graphics;
use crate::fonts::{LoadedFont, LoadedFontFace};
use crate::graphics::{FontState, Graphics};
use crate::styles::components::{
CornerRadius, FontFamily, FontStyle, FontWeight, HighlightColor, LayoutOrder, LineHeight,
Opacity, TextSize, WidgetBackground,
};
use crate::styles::{ComponentDefinition, Styles, Theme, ThemePair};
use crate::styles::{ComponentDefinition, FontFamilyList, Styles, Theme, ThemePair};
use crate::tree::Tree;
use crate::value::{IntoValue, Source, Value};
use crate::widget::{EventHandling, MountedWidget, RootBehavior, WidgetId, WidgetInstance};
@ -563,6 +565,36 @@ impl<'context, 'clip, 'gfx, 'pass> GraphicsContext<'context, 'clip, 'gfx, 'pass>
})
}
/// Sets the current font family.
pub fn set_font_family(&mut self, family: FamilyOwned) {
self.font_state.current_font_family = None;
self.gfx.set_font_family(family);
}
/// Returns the currently set font family list.
pub fn current_family_list(&mut self) -> FontFamilyList {
self.font_state
.current_font_family
.clone()
.unwrap_or_else(|| FontFamilyList::from(vec![FamilyOwned::new(self.gfx.font_family())]))
}
/// Returns the first font family in `list` that is currently in the font
/// system, or None if no font families match.
pub fn find_available_font_family(&mut self, list: &FontFamilyList) -> Option<FamilyOwned> {
self.font_state.find_available_font_family(list)
}
/// Sets the font family to the first family in `list`.
pub fn set_available_font_family(&mut self, list: &FontFamilyList) {
if self.font_state.current_font_family.as_ref() != Some(list) {
if let Some(family) = self.find_available_font_family(list) {
self.set_font_family(family);
}
self.font_state.current_font_family = Some(list.clone());
}
}
/// Updates `self` to have `opacity`.
///
/// This setting will be mixed with the current opacity value.
@ -636,8 +668,7 @@ impl<'context, 'clip, 'gfx, 'pass> GraphicsContext<'context, 'clip, 'gfx, 'pass>
/// Applies the current style settings for font family, text size, font
/// style, and font weight.
pub fn apply_current_font_settings(&mut self) {
self.gfx
.set_available_font_family(&self.widget.get(&FontFamily));
self.set_available_font_family(&self.widget.get(&FontFamily));
self.gfx.set_font_size(self.widget.get(&TextSize));
self.gfx.set_line_height(self.widget.get(&LineHeight));
self.gfx.set_font_style(self.widget.get(&FontStyle));
@ -856,6 +887,7 @@ pub struct WidgetContext<'context> {
theme: Cow<'context, ThemePair>,
cursor: &'context mut CursorState,
pending_state: PendingState<'context>,
font_state: &'context mut FontState,
effective_styles: Styles,
cache: WidgetCacheKey,
}
@ -865,6 +897,7 @@ impl<'context> WidgetContext<'context> {
current_node: MountedWidget,
theme: &'context ThemePair,
window: &'context mut dyn PlatformWindow,
font_state: &'context mut FontState,
theme_mode: ThemeMode,
cursor: &'context mut CursorState,
) -> Self {
@ -891,6 +924,7 @@ impl<'context> WidgetContext<'context> {
},
cursor,
current_node,
font_state,
theme: Cow::Borrowed(theme),
window,
}
@ -902,6 +936,7 @@ impl<'context> WidgetContext<'context> {
tree: self.tree.clone(),
current_node: self.current_node.clone(),
window: &mut *self.window,
font_state: &mut *self.font_state,
theme: Cow::Borrowed(self.theme.as_ref()),
pending_state: self.pending_state.borrowed(),
cache: self.cache,
@ -940,6 +975,7 @@ impl<'context> WidgetContext<'context> {
},
current_node,
tree: self.tree.clone(),
font_state: &mut *self.font_state,
window: &mut *self.window,
theme,
pending_state: self.pending_state.borrowed(),
@ -1197,6 +1233,16 @@ impl<'context> WidgetContext<'context> {
pub fn cache_key(&self) -> WidgetCacheKey {
self.cache
}
/// Returns a list of faces that were loaded from `font`, or an empty slice
/// if no faces were loaded.
#[must_use]
pub fn loaded_font_faces(&self, font: &LoadedFont) -> &[LoadedFontFace] {
self.font_state
.loaded_fonts
.get(&font.id())
.map_or(&[], |ids| &ids.faces)
}
}
impl Drop for EventContext<'_> {

129
src/fonts.rs Normal file
View file

@ -0,0 +1,129 @@
//! Types for loading fonts to use in Cushy.
use std::sync::Arc;
use alot::{LotId, Lots};
use kludgine::cosmic_text::fontdb::{self, Language};
use kludgine::cosmic_text::{Stretch, Style, Weight};
use crate::value::Dynamic;
/// A collection of fonts that can be loaded into Cushy.
#[derive(Clone, Default, PartialEq)]
pub struct FontCollection(pub(crate) Dynamic<FontCollectionData>);
impl FontCollection {
/// Pushes a font that will be unloaded when the last clone of the
/// [`LoadedFont`] is dropped.
#[must_use]
pub fn push_unloadable(&self, font_data: Vec<u8>) -> LoadedFont {
LoadedFont(Arc::new(LoadedFontHandle {
collection: self.clone(),
id: self.push_inner(font_data),
}))
}
/// Adds `font_data` to this collection and returns self.
#[must_use]
pub fn with(self, font_data: Vec<u8>) -> Self {
self.push(font_data);
self
}
/// Pushes `font_data` into this collection.
pub fn push(&self, font_data: Vec<u8>) {
self.push_inner(font_data);
}
fn push_inner(&self, font_data: Vec<u8>) -> LotId {
self.0.lock().fonts.push(Arc::new(font_data))
}
}
pub(crate) struct FontIter<'a> {
collection: *const (),
iter: alot::unordered::EntryIter<'a, Arc<Vec<u8>>>,
}
impl<'a> Iterator for FontIter<'a> {
type Item = (LoadedFontId, &'a Arc<Vec<u8>>);
fn next(&mut self) -> Option<Self::Item> {
self.iter.next().map(|(id, data)| {
(
LoadedFontId {
collection: self.collection,
id,
},
data,
)
})
}
}
#[derive(Default)]
pub(crate) struct FontCollectionData {
fonts: Lots<Arc<Vec<u8>>>,
}
impl FontCollectionData {
pub(crate) fn fonts(&self, collection: &FontCollection) -> FontIter<'_> {
FontIter {
collection: collection.0.as_ptr(),
iter: self.fonts.entries(),
}
}
}
#[derive(PartialEq)]
struct LoadedFontHandle {
collection: FontCollection,
id: LotId,
}
impl Drop for LoadedFontHandle {
fn drop(&mut self) {
let mut collection = self.collection.0.lock();
collection.fonts.remove(self.id);
}
}
/// A handle to font data that has been loaded.
///
/// This type can be cloned and uses reference counting to track when to release
/// the underlying font data.
///
/// Font data is not parsed until it is loaded into a running Cushy window. To
/// find information about this handle, use
/// [`WidgetContext::loaded_font_faces()`](crate::context::WidgetContext::loaded_font_faces).
#[derive(PartialEq, Clone)]
pub struct LoadedFont(Arc<LoadedFontHandle>);
impl LoadedFont {
pub(crate) fn id(&self) -> LoadedFontId {
LoadedFontId {
collection: self.0.collection.0.as_ptr(),
id: self.0.id,
}
}
}
#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Copy)]
pub(crate) struct LoadedFontId {
pub(crate) collection: *const (),
pub(crate) id: LotId,
}
/// Information about a [`LoadedFont`].
#[derive(Debug)]
pub struct LoadedFontFace {
/// The font database ID for this face.
pub id: fontdb::ID,
/// The names of the families contained in this face, and the corresponding
/// language of the name.
pub families: Vec<(String, Language)>,
/// The weight of the font face.
pub weight: Weight,
/// The style of the font face.
pub style: Style,
/// The stretch of the font face.
pub stretch: Stretch,
}

View file

@ -1,11 +1,11 @@
use std::ops::{Deref, DerefMut};
use ahash::AHashSet;
use figures::units::{Px, UPx};
use figures::{
self, Fraction, IntoSigned, IntoUnsigned, Point, Rect, ScreenScale, ScreenUnit, Size, Zero,
};
use kludgine::cosmic_text::FamilyOwned;
use kempt::{map, Map};
use kludgine::cosmic_text::{fontdb, FamilyOwned, FontSystem};
use kludgine::drawing::Renderer;
use kludgine::shapes::Shape;
use kludgine::text::{MeasuredText, Text, TextOrigin};
@ -14,13 +14,14 @@ use kludgine::{
};
use crate::animation::ZeroToOne;
use crate::fonts::{FontCollection, LoadedFontFace, LoadedFontId};
use crate::styles::FontFamilyList;
use crate::value::{DynamicRead, Generation, Source};
/// A 2d graphics context
pub struct Graphics<'clip, 'gfx, 'pass> {
renderer: RenderContext<'clip, 'gfx, 'pass>,
region: Rect<Px>,
font_state: &'clip mut FontState,
pub(crate) opacity: ZeroToOne,
}
@ -32,11 +33,10 @@ enum RenderContext<'clip, 'gfx, 'pass> {
impl<'clip, 'gfx, 'pass> Graphics<'clip, 'gfx, 'pass> {
/// Returns a new graphics context for the given [`Renderer`].
#[must_use]
pub fn new(renderer: Renderer<'gfx, 'pass>, font_state: &'clip mut FontState) -> Self {
pub fn new(renderer: Renderer<'gfx, 'pass>) -> Self {
Self {
region: renderer.clip_rect().into_signed(),
renderer: RenderContext::Renderer(renderer),
font_state,
opacity: ZeroToOne::ONE,
}
}
@ -66,28 +66,6 @@ impl<'clip, 'gfx, 'pass> Graphics<'clip, 'gfx, 'pass> {
)
}
/// Sets the current font family.
pub fn set_font_family(&mut self, family: cosmic_text::FamilyOwned) {
self.font_state.current_font_family = None;
self.renderer.set_font_family(family);
}
/// Returns the first font family in `list` that is currently in the font
/// system, or None if no font families match.
pub fn find_available_font_family(&mut self, list: &FontFamilyList) -> Option<FamilyOwned> {
self.font_state.find_available_font_family(list)
}
/// Sets the font family to the first family in `list`.
pub fn set_available_font_family(&mut self, list: &FontFamilyList) {
if self.font_state.current_font_family.as_ref() != Some(list) {
if let Some(family) = self.find_available_font_family(list) {
self.set_font_family(family);
}
self.font_state.current_font_family = Some(list.clone());
}
}
/// Returns the underlying renderer.
///
/// Note: Kludgine graphics contexts only support clipping. This type adds
@ -120,7 +98,6 @@ impl<'clip, 'gfx, 'pass> Graphics<'clip, 'gfx, 'pass> {
Graphics {
renderer: RenderContext::Clipped(self.renderer.clipped_to(new_clip)),
region,
font_state: &mut *self.font_state,
opacity: self.opacity,
}
}
@ -310,6 +287,11 @@ impl<'clip, 'gfx, 'pass> Graphics<'clip, 'gfx, 'pass> {
self.renderer.draw_measured_text(text, origin);
}
/// Returns a reference to the font system used to render.
pub fn font_system(&mut self) -> &mut FontSystem {
self.renderer.font_system()
}
/// Returns this renderer as a
/// [`DrawingArea`](plotters::drawing::DrawingArea) compatible with the
/// [plotters](https://github.com/plotters-rs/plotters) crate.
@ -358,26 +340,163 @@ impl<'gfx, 'pass> DerefMut for RenderContext<'_, 'gfx, 'pass> {
}
}
pub(crate) struct LoadedFontIds {
generation: usize,
pub(crate) faces: Vec<LoadedFontFace>,
}
pub struct FontState {
fonts: AHashSet<String>,
current_font_family: Option<FontFamilyList>,
app_fonts: FontCollection,
app_font_generation: Generation,
window_fonts: FontCollection,
window_font_generation: Generation,
pub(crate) loaded_fonts: Map<LoadedFontId, LoadedFontIds>,
font_generation: usize,
fonts: Map<String, usize>,
pub(crate) current_font_family: Option<FontFamilyList>,
}
impl FontState {
pub fn new(db: &cosmic_text::fontdb::Database) -> Self {
let faces = db.faces();
let mut fonts = AHashSet::with_capacity(faces.size_hint().0);
for (family, _) in faces.filter_map(|f| f.families.first()) {
fonts.insert(family.clone());
}
Self {
pub fn new(
db: &mut cosmic_text::fontdb::Database,
window_fonts: FontCollection,
app_fonts: FontCollection,
) -> Self {
let mut fonts = Map::new();
Self::gather_available_family_names(&mut fonts, 0, db);
let mut state = Self {
fonts,
current_font_family: None,
window_font_generation: window_fonts.0.generation(),
window_fonts,
app_font_generation: app_fonts.0.generation(),
app_fonts,
font_generation: 0,
loaded_fonts: Map::new(),
};
state.update_fonts(db);
state
}
fn gather_available_family_names(
families: &mut Map<String, usize>,
generation: usize,
db: &cosmic_text::fontdb::Database,
) {
for (family, _) in db.faces().filter_map(|f| f.families.first()) {
families
.entry(family)
.and_modify(|gen| *gen = generation)
.or_insert(generation);
}
let mut i = 0;
while i < families.len() {
if families.field(i).expect("length checked").value == generation {
i += 1;
} else {
families.remove_by_index(i);
}
}
}
pub fn next_frame(&mut self) {
pub fn update_fonts(&mut self, db: &mut cosmic_text::fontdb::Database) -> bool {
let new_app_generation = self.app_fonts.0.generation();
let app_fonts_changed = if self.app_font_generation == new_app_generation {
false
} else {
self.app_font_generation = new_app_generation;
true
};
let new_window_generation = self.window_fonts.0.generation();
let window_fonts_changed = if self.window_font_generation == new_window_generation {
false
} else {
self.window_font_generation = new_window_generation;
true
};
let changed = app_fonts_changed || window_fonts_changed;
if changed {
self.font_generation += 1;
if app_fonts_changed {
Self::synchronize_font_list(
&mut self.loaded_fonts,
self.font_generation,
&self.app_fonts,
db,
);
}
if window_fonts_changed {
Self::synchronize_font_list(
&mut self.loaded_fonts,
self.font_generation,
&self.window_fonts,
db,
);
}
// Remove all fonts that didn't have their generation touched.
let mut i = 0;
while i < self.loaded_fonts.len() {
let field = self.loaded_fonts.field(i).expect("length checked");
let check_if_changed = (app_fonts_changed
&& self.app_fonts.0.as_ptr() == field.key().collection)
|| (window_fonts_changed
&& self.window_fonts.0.as_ptr() == field.key().collection);
if !check_if_changed || field.value.generation == self.font_generation {
i += 1;
} else {
for face in self.loaded_fonts.remove_by_index(i).value.faces {
db.remove_face(face.id);
}
}
}
Self::gather_available_family_names(&mut self.fonts, self.font_generation, db);
}
changed
}
fn synchronize_font_list(
loaded_fonts: &mut Map<LoadedFontId, LoadedFontIds>,
generation: usize,
collection: &FontCollection,
db: &mut cosmic_text::fontdb::Database,
) {
for (font_id, data) in collection.0.read().fonts(collection) {
match loaded_fonts.entry(font_id) {
map::Entry::Occupied(mut entry) => {
entry.generation = generation;
}
map::Entry::Vacant(entry) => {
let faces = db
.load_font_source(fontdb::Source::Binary(data.clone()))
.into_iter()
.filter_map(|id| {
db.face(id).map(|face| LoadedFontFace {
id,
families: face.families.clone(),
weight: face.weight,
style: face.style,
stretch: face.stretch,
})
})
.collect();
entry.insert(LoadedFontIds { generation, faces });
}
}
}
}
#[must_use]
pub fn next_frame(&mut self, db: &mut cosmic_text::fontdb::Database) -> bool {
self.current_font_family = None;
self.update_fonts(db)
}
pub fn find_available_font_family(&self, list: &FontFamilyList) -> Option<FamilyOwned> {

View file

@ -16,6 +16,7 @@ mod names;
pub mod styles;
mod app;
pub mod debug;
pub mod fonts;
mod tick;
mod tree;
pub mod value;

View file

@ -892,6 +892,10 @@ impl<T> Dynamic<T> {
}))
}
pub(crate) fn as_ptr(&self) -> *const () {
Arc::as_ptr(&self.0).cast()
}
/// Returns a weak reference to this dynamic.
///
/// This is powered by [`Arc`]/[`Weak`] and follows the same semantics for

View file

@ -10,6 +10,7 @@ use kludgine::{CanRenderTo, Color, DrawableExt};
use super::input::CowString;
use crate::context::{GraphicsContext, LayoutContext, Trackable, WidgetContext};
use crate::styles::components::TextColor;
use crate::styles::FontFamilyList;
use crate::value::{Dynamic, Generation, IntoReadOnly, ReadOnly, Value};
use crate::widget::{Widget, WidgetInstance};
use crate::window::WindowLocal;
@ -21,7 +22,7 @@ pub struct Label<T> {
/// The contents of the label.
pub display: ReadOnly<T>,
displayed: String,
prepared_text: WindowLocal<(MeasuredText<Px>, Option<Generation>, Px, Color)>,
prepared_text: WindowLocal<LabelCacheKey>,
}
impl<T> Label<T>
@ -44,14 +45,16 @@ where
width: Px,
) -> &MeasuredText<Px> {
let check_generation = self.display.generation();
context.apply_current_font_settings();
let current_families = context.current_family_list();
match self.prepared_text.get(context) {
Some((prepared, prepared_generation, prepared_width, prepared_color))
if prepared.can_render_to(&context.gfx)
&& *prepared_generation == check_generation
&& *prepared_color == color
&& *prepared_width == width => {}
Some(cache)
if cache.text.can_render_to(&context.gfx)
&& cache.generation == check_generation
&& cache.color == color
&& cache.width == width
&& cache.families == current_families => {}
_ => {
context.apply_current_font_settings();
let measured = self.display.map(|text| {
self.displayed.clear();
if let Err(err) = write!(&mut self.displayed, "{}", text.as_display(context)) {
@ -61,14 +64,22 @@ where
.gfx
.measure_text(Text::new(&self.displayed, color).wrap_at(width))
});
self.prepared_text
.set(context, (measured, check_generation, width, color));
self.prepared_text.set(
context,
LabelCacheKey {
text: measured,
generation: check_generation,
width,
color,
families: current_families,
},
);
}
}
self.prepared_text
.get(context)
.map(|(prepared, _, _, _)| prepared)
.map(|cache| &cache.text)
.expect("always initialized")
}
}
@ -133,6 +144,15 @@ impl_make_widget!(
ReadOnly<String> => String
);
#[derive(Debug)]
struct LabelCacheKey {
text: MeasuredText<Px>,
generation: Option<Generation>,
width: Px,
color: Color,
families: FontFamilyList,
}
/// A context-aware [`Display`] implementation.
///
/// This trait is automatically implemented for all types that implement

View file

@ -49,6 +49,7 @@ use crate::context::{
AsEventContext, EventContext, Exclusive, GraphicsContext, LayoutContext, Trackable,
WidgetContext,
};
use crate::fonts::FontCollection;
use crate::graphics::{FontState, Graphics};
use crate::styles::{Edges, FontFamilyList, ThemePair};
use crate::tree::Tree;
@ -472,9 +473,8 @@ where
/// The list of font families to try to find when a [`FamilyOwned::Cursive`]
/// font is requested.
pub cursive_font_family: FontFamilyList,
/// A list of data buffers that contain font data to ensure are loaded
/// during drawing operations.
pub font_data_to_load: Vec<Vec<u8>>,
/// A collection of fonts that this window will load.
pub fonts: FontCollection,
on_closed: Option<OnceCallback>,
inner_size: Option<Dynamic<Size<UPx>>>,
@ -572,8 +572,8 @@ impl Window<WidgetInstance> {
/// rendering.
///
/// All font families contained in `font_data` will be loaded.
pub fn loading_font(mut self, font_data: Vec<u8>) -> Self {
self.font_data_to_load.push(font_data);
pub fn loading_font(self, font_data: Vec<u8>) -> Self {
self.fonts.push(font_data);
self
}
@ -636,10 +636,12 @@ where
fantasy_font_family: FontFamilyList::default(),
monospace_font_family: FontFamilyList::default(),
cursive_font_family: FontFamilyList::default(),
font_data_to_load: vec![
fonts: {
let fonts = FontCollection::default();
#[cfg(feature = "roboto-flex")]
include_bytes!("../assets/RobotoFlex.ttf").to_vec(),
],
fonts.push(include_bytes!("../assets/RobotoFlex.ttf").to_vec());
fonts
},
}
}
}
@ -681,7 +683,7 @@ where
inner_size: self.inner_size.unwrap_or_default(),
theme: Some(self.theme),
theme_mode: self.theme_mode,
font_data_to_load: self.font_data_to_load,
font_data_to_load: self.fonts,
serif_font_family: self.serif_font_family,
sans_serif_font_family: self.sans_serif_font_family,
fantasy_font_family: self.fantasy_font_family,
@ -802,6 +804,7 @@ where
previously_active,
&self.current_theme,
window,
&mut self.fonts,
self.theme_mode.get(),
&mut self.cursor,
),
@ -814,6 +817,7 @@ where
default.clone(),
&self.current_theme,
window,
&mut self.fonts,
self.theme_mode.get(),
&mut self.cursor,
),
@ -832,6 +836,7 @@ where
keyboard_activated,
&self.current_theme,
window,
&mut self.fonts,
self.theme_mode.get(),
&mut self.cursor,
),
@ -864,6 +869,7 @@ where
managed,
&self.current_theme,
window,
&mut self.fonts,
self.theme_mode.get(),
&mut self.cursor,
),
@ -938,13 +944,10 @@ where
fn load_fonts(
settings: &mut sealed::WindowSettings,
app_fonts: FontCollection,
fontdb: &mut fontdb::Database,
) -> FontState {
for font_to_load in settings.font_data_to_load.drain(..) {
fontdb.load_font_data(font_to_load);
}
let fonts = FontState::new(fontdb);
let fonts = FontState::new(fontdb, settings.font_data_to_load.clone(), app_fonts);
fonts.apply_font_family_list(
&settings.serif_font_family,
|| default_family(Family::Serif),
@ -1017,6 +1020,7 @@ where
target,
&self.current_theme,
window,
&mut self.fonts,
self.theme_mode.get(),
&mut self.cursor,
),
@ -1049,6 +1053,7 @@ where
target,
&self.current_theme,
window,
&mut self.fonts,
self.theme_mode.get(),
&mut self.cursor,
),
@ -1122,7 +1127,11 @@ where
inner_size.set(window.inner_size());
let fonts = Self::load_fonts(&mut settings, graphics.font_system().db_mut());
let fonts = Self::load_fonts(
&mut settings,
cushy.fonts.clone(),
graphics.font_system().db_mut(),
);
let theme_mode = match settings.theme_mode.take() {
Some(Value::Dynamic(dynamic)) => {
@ -1201,17 +1210,22 @@ where
);
let root_mode = self.constrain_window_resizing(resizable, &mut window, graphics);
self.fonts.next_frame();
let fonts_changed = self.fonts.next_frame(graphics.font_system().db_mut());
if fonts_changed {
println!("Rebuilding font system");
graphics.rebuild_font_system();
}
let graphics = self.contents.new_frame(graphics);
let mut context = GraphicsContext {
widget: WidgetContext::new(
self.root.clone(),
&self.current_theme,
&mut window,
&mut self.fonts,
self.theme_mode.get(),
&mut self.cursor,
),
gfx: Exclusive::Owned(Graphics::new(graphics, &mut self.fonts)),
gfx: Exclusive::Owned(Graphics::new(graphics)),
};
if self.initial_frame {
self.root
@ -1342,6 +1356,7 @@ where
target,
&self.current_theme,
&mut window,
&mut self.fonts,
self.theme_mode.get(),
&mut self.cursor,
),
@ -1391,6 +1406,7 @@ where
widget,
&self.current_theme,
&mut window,
&mut self.fonts,
self.theme_mode.get(),
&mut self.cursor,
),
@ -1430,6 +1446,7 @@ where
widget,
&self.current_theme,
&mut window,
&mut self.fonts,
self.theme_mode.get(),
&mut self.cursor,
),
@ -1470,6 +1487,7 @@ where
self.root.clone(),
&self.current_theme,
&mut window,
&mut self.fonts,
self.theme_mode.get(),
&mut self.cursor,
),
@ -1488,6 +1506,7 @@ where
handler.clone(),
&self.current_theme,
&mut window,
&mut self.fonts,
self.theme_mode.get(),
&mut self.cursor,
),
@ -1521,6 +1540,7 @@ where
self.root.clone(),
&self.current_theme,
&mut window,
&mut self.fonts,
self.theme_mode.get(),
&mut self.cursor,
),
@ -1557,6 +1577,7 @@ where
self.root.clone(),
&self.current_theme,
&mut window,
&mut self.fonts,
self.theme_mode.get(),
&mut self.cursor,
),
@ -1575,6 +1596,7 @@ where
hovered.clone(),
&self.current_theme,
&mut window,
&mut self.fonts,
self.theme_mode.get(),
&mut self.cursor,
),
@ -1616,6 +1638,7 @@ where
handler,
&self.current_theme,
&mut window,
&mut self.fonts,
self.theme_mode.get(),
&mut self.cursor,
),
@ -1934,6 +1957,7 @@ pub(crate) mod sealed {
use crate::app::Cushy;
use crate::context::sealed::InvalidationStatus;
use crate::fonts::FontCollection;
use crate::styles::{FontFamilyList, ThemePair};
use crate::value::{Dynamic, Value};
use crate::widget::OnceCallback;
@ -1960,7 +1984,7 @@ pub(crate) mod sealed {
pub fantasy_font_family: FontFamilyList,
pub monospace_font_family: FontFamilyList,
pub cursive_font_family: FontFamilyList,
pub font_data_to_load: Vec<Vec<u8>>,
pub font_data_to_load: FontCollection,
pub on_closed: Option<OnceCallback>,
}
@ -2488,7 +2512,7 @@ impl CushyWindowBuilder {
fantasy_font_family: FontFamilyList::default(),
monospace_font_family: FontFamilyList::default(),
cursive_font_family: FontFamilyList::default(),
font_data_to_load: Vec::default(),
font_data_to_load: FontCollection::default(),
on_closed: None,
},
);