mirror of
https://github.com/danbulant/cushy
synced 2026-06-14 03:51:17 +00:00
parent
7a9ddaa926
commit
3762bc6dc1
12 changed files with 498 additions and 75 deletions
15
CHANGELOG.md
15
CHANGELOG.md
|
|
@ -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
6
Cargo.lock
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
56
examples/dynamic-fonts.rs
Normal 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()
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
129
src/fonts.rs
Normal 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,
|
||||
}
|
||||
195
src/graphics.rs
195
src/graphics.rs
|
|
@ -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> {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ mod names;
|
|||
pub mod styles;
|
||||
mod app;
|
||||
pub mod debug;
|
||||
pub mod fonts;
|
||||
mod tick;
|
||||
mod tree;
|
||||
pub mod value;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue