diff options
author | 2023-02-04 11:12:15 +0100 | |
---|---|---|
committer | 2023-02-24 13:29:11 +0100 | |
commit | 238154af4ac8dda7f12dd90aa7be106e933bcb30 (patch) | |
tree | 4c8560b3464f0c900ddf31a3c063e925394923c6 | |
parent | b29de28d1f0f608f8029c93d154cfd1b0f8b8cbb (diff) | |
download | iced-238154af4ac8dda7f12dd90aa7be106e933bcb30.tar.gz iced-238154af4ac8dda7f12dd90aa7be106e933bcb30.tar.bz2 iced-238154af4ac8dda7f12dd90aa7be106e933bcb30.zip |
Implement `font::load` command in `iced_native`
-rw-r--r-- | examples/todos/src/main.rs | 15 | ||||
-rw-r--r-- | graphics/src/backend.rs | 5 | ||||
-rw-r--r-- | graphics/src/renderer.rs | 5 | ||||
-rw-r--r-- | native/src/command/action.rs | 16 | ||||
-rw-r--r-- | native/src/font.rs | 19 | ||||
-rw-r--r-- | native/src/lib.rs | 6 | ||||
-rw-r--r-- | native/src/program.rs | 3 | ||||
-rw-r--r-- | native/src/renderer/null.rs | 4 | ||||
-rw-r--r-- | native/src/text.rs | 7 | ||||
-rw-r--r-- | src/lib.rs | 6 | ||||
-rw-r--r-- | wgpu/Cargo.toml | 1 | ||||
-rw-r--r-- | wgpu/src/backend.rs | 6 | ||||
-rw-r--r-- | wgpu/src/text.rs | 470 | ||||
-rw-r--r-- | winit/src/application.rs | 10 |
14 files changed, 358 insertions, 215 deletions
diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs index 5509cf0e..5df4e968 100644 --- a/examples/todos/src/main.rs +++ b/examples/todos/src/main.rs @@ -1,5 +1,6 @@ use iced::alignment::{self, Alignment}; use iced::event::{self, Event}; +use iced::font::{self, Font}; use iced::keyboard; use iced::subscription; use iced::theme::{self, Theme}; @@ -9,7 +10,7 @@ use iced::widget::{ }; use iced::window; use iced::{Application, Element}; -use iced::{Color, Command, Font, Length, Settings, Subscription}; +use iced::{Color, Command, Length, Settings, Subscription}; use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; @@ -44,6 +45,7 @@ struct State { #[derive(Debug, Clone)] enum Message { Loaded(Result<SavedState, LoadError>), + FontLoaded(Result<(), font::Error>), Saved(Result<(), SaveError>), InputChanged(String), CreateTask, @@ -61,7 +63,11 @@ impl Application for Todos { fn new(_flags: ()) -> (Todos, Command<Message>) { ( Todos::Loading, - Command::perform(SavedState::load(), Message::Loaded), + Command::batch(vec![ + font::load(include_bytes!("../fonts/icons.ttf").as_slice()) + .map(Message::FontLoaded), + Command::perform(SavedState::load(), Message::Loaded), + ]), ) } @@ -384,7 +390,7 @@ fn view_controls(tasks: &[Task], current_filter: Filter) -> Element<Message> { let tasks_left = tasks.iter().filter(|task| !task.completed).count(); let filter_button = |label, filter, current_filter| { - let label = text(label).size(16); + let label = text(label); let button = button(label).style(if filter == current_filter { theme::Button::Primary @@ -401,8 +407,7 @@ fn view_controls(tasks: &[Task], current_filter: Filter) -> Element<Message> { tasks_left, if tasks_left == 1 { "task" } else { "tasks" } )) - .width(Length::Fill) - .size(16), + .width(Length::Fill), row![ filter_button("All", Filter::All, current_filter), filter_button("Active", Filter::Active, current_filter), diff --git a/graphics/src/backend.rs b/graphics/src/backend.rs index 7bbdee95..8658cffe 100644 --- a/graphics/src/backend.rs +++ b/graphics/src/backend.rs @@ -4,6 +4,8 @@ use iced_native::svg; use iced_native::text; use iced_native::{Font, Point, Size}; +use std::borrow::Cow; + /// The graphics backend of a [`Renderer`]. /// /// [`Renderer`]: crate::Renderer @@ -64,6 +66,9 @@ pub trait Text { point: Point, nearest_only: bool, ) -> Option<text::Hit>; + + /// Loads a [`Font`] from its bytes. + fn load_font(&mut self, font: Cow<'static, [u8]>); } /// A graphics backend that supports image rendering. diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index 7ad53dec..b052c094 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -10,6 +10,7 @@ use iced_native::{Background, Color, Element, Font, Point, Rectangle, Size}; pub use iced_native::renderer::Style; +use std::borrow::Cow; use std::marker::PhantomData; /// A backend-agnostic renderer that supports all the built-in widgets. @@ -167,6 +168,10 @@ where ) } + fn load_font(&mut self, bytes: Cow<'static, [u8]>) { + self.backend.load_font(bytes); + } + fn fill_text(&mut self, text: Text<'_, Self::Font>) { self.primitives.push(Primitive::Text { content: text.content.to_string(), diff --git a/native/src/command/action.rs b/native/src/command/action.rs index a51b8c21..d1589c05 100644 --- a/native/src/command/action.rs +++ b/native/src/command/action.rs @@ -1,10 +1,12 @@ use crate::clipboard; +use crate::font; use crate::system; use crate::widget; use crate::window; use iced_futures::MaybeSend; +use std::borrow::Cow; use std::fmt; /// An action that a [`Command`] can perform. @@ -27,6 +29,15 @@ pub enum Action<T> { /// Run a widget action. Widget(widget::Action<T>), + + /// Load a font from its bytes. + LoadFont { + /// The bytes of the font to load. + bytes: Cow<'static, [u8]>, + + /// The message to produce when the font has been loaded. + tagger: Box<dyn Fn(Result<(), font::Error>) -> T>, + }, } impl<T> Action<T> { @@ -49,6 +60,10 @@ impl<T> Action<T> { Self::Window(window) => Action::Window(window.map(f)), Self::System(system) => Action::System(system.map(f)), Self::Widget(widget) => Action::Widget(widget.map(f)), + Self::LoadFont { bytes, tagger } => Action::LoadFont { + bytes, + tagger: Box::new(move |result| f(tagger(result))), + }, } } } @@ -63,6 +78,7 @@ impl<T> fmt::Debug for Action<T> { Self::Window(action) => write!(f, "Action::Window({action:?})"), Self::System(action) => write!(f, "Action::System({action:?})"), Self::Widget(_action) => write!(f, "Action::Widget"), + Self::LoadFont { .. } => write!(f, "Action::LoadFont"), } } } diff --git a/native/src/font.rs b/native/src/font.rs new file mode 100644 index 00000000..6840a25f --- /dev/null +++ b/native/src/font.rs @@ -0,0 +1,19 @@ +//! Load and use fonts. +pub use iced_core::Font; + +use crate::command::{self, Command}; +use std::borrow::Cow; + +/// An error while loading a font. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Error {} + +/// Load a font from its bytes. +pub fn load( + bytes: impl Into<Cow<'static, [u8]>>, +) -> Command<Result<(), Error>> { + Command::single(command::Action::LoadFont { + bytes: bytes.into(), + tagger: Box::new(std::convert::identity), + }) +} diff --git a/native/src/lib.rs b/native/src/lib.rs index ebdc8490..27b6fc0d 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -47,6 +47,7 @@ pub mod clipboard; pub mod command; pub mod event; +pub mod font; pub mod image; pub mod keyboard; pub mod layout; @@ -80,8 +81,8 @@ mod debug; pub use iced_core::alignment; pub use iced_core::time; pub use iced_core::{ - color, Alignment, Background, Color, ContentFit, Font, Length, Padding, - Pixels, Point, Rectangle, Size, Vector, + color, Alignment, Background, Color, ContentFit, Length, Padding, Pixels, + Point, Rectangle, Size, Vector, }; pub use iced_futures::{executor, futures}; pub use iced_style::application; @@ -95,6 +96,7 @@ pub use command::Command; pub use debug::Debug; pub use element::Element; pub use event::Event; +pub use font::Font; pub use hasher::Hasher; pub use layout::Layout; pub use overlay::Overlay; diff --git a/native/src/program.rs b/native/src/program.rs index c71c237f..25cab332 100644 --- a/native/src/program.rs +++ b/native/src/program.rs @@ -1,4 +1,5 @@ //! Build interactive programs using The Elm Architecture. +use crate::text; use crate::{Command, Element, Renderer}; mod state; @@ -8,7 +9,7 @@ pub use state::State; /// The core of a user interface application following The Elm Architecture. pub trait Program: Sized { /// The graphics backend to use to draw the [`Program`]. - type Renderer: Renderer; + type Renderer: Renderer + text::Renderer; /// The type of __messages__ your [`Program`] will produce. type Message: std::fmt::Debug + Send; diff --git a/native/src/renderer/null.rs b/native/src/renderer/null.rs index 50d7d6d6..150ee786 100644 --- a/native/src/renderer/null.rs +++ b/native/src/renderer/null.rs @@ -2,6 +2,8 @@ use crate::renderer::{self, Renderer}; use crate::text::{self, Text}; use crate::{Background, Font, Point, Rectangle, Size, Theme, Vector}; +use std::borrow::Cow; + /// A renderer that does nothing. /// /// It can be useful if you are writing tests! @@ -52,6 +54,8 @@ impl text::Renderer for Null { 16.0 } + fn load_font(&mut self, _font: Cow<'static, [u8]>) {} + fn measure( &self, _content: &str, diff --git a/native/src/text.rs b/native/src/text.rs index b7915a55..1bbd36cc 100644 --- a/native/src/text.rs +++ b/native/src/text.rs @@ -2,6 +2,8 @@ use crate::alignment; use crate::{Color, Point, Rectangle, Size, Vector}; +use std::borrow::Cow; + /// A paragraph. #[derive(Debug, Clone, Copy)] pub struct Text<'a, Font> { @@ -72,7 +74,7 @@ pub trait Renderer: crate::Renderer { /// [`ICON_FONT`]: Self::ICON_FONT const ARROW_DOWN_ICON: char; - /// Returns the default [`Font`]. + /// Returns the default [`Self::Font`]. fn default_font(&self) -> Self::Font; /// Returns the default size of [`Text`]. @@ -112,6 +114,9 @@ pub trait Renderer: crate::Renderer { nearest_only: bool, ) -> Option<Hit>; + /// Loads a [`Self::Font`] from its bytes. + fn load_font(&mut self, font: Cow<'static, [u8]>); + /// Draws the given [`Text`]. fn fill_text(&mut self, text: Text<'_, Self::Font>); } @@ -196,6 +196,7 @@ use iced_glow as renderer; pub use iced_native::theme; pub use runtime::event; +pub use runtime::font; pub use runtime::subscription; pub use application::Application; @@ -203,6 +204,7 @@ pub use element::Element; pub use error::Error; pub use event::Event; pub use executor::Executor; +pub use font::Font; pub use renderer::Renderer; pub use result::Result; pub use sandbox::Sandbox; @@ -213,8 +215,8 @@ pub use theme::Theme; pub use runtime::alignment; pub use runtime::futures; pub use runtime::{ - color, Alignment, Background, Color, Command, ContentFit, Font, Length, - Padding, Point, Rectangle, Size, Vector, + color, Alignment, Background, Color, Command, ContentFit, Length, Padding, + Point, Rectangle, Size, Vector, }; #[cfg(feature = "system")] diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 1a94c6a3..dffbbab0 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -36,6 +36,7 @@ bitflags = "1.2" once_cell = "1.0" rustc-hash = "1.1" twox-hash = "1.6" +ouroboros = "0.15" [dependencies.bytemuck] version = "1.9" diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 874edb96..5a275c8a 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -14,6 +14,8 @@ use tracing::info_span; #[cfg(any(feature = "image", feature = "svg"))] use crate::image; +use std::borrow::Cow; + /// A [`wgpu`] graphics backend for [`iced`]. /// /// [`wgpu`]: https://github.com/gfx-rs/wgpu-rs @@ -234,6 +236,10 @@ impl backend::Text for Backend { nearest_only, ) } + + fn load_font(&mut self, font: Cow<'static, [u8]>) { + self.text_pipeline.load_font(font); + } } #[cfg(feature = "image")] diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 083d9d2b..cdfcd576 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -5,108 +5,37 @@ use iced_native::alignment; use iced_native::{Color, Font, Rectangle, Size}; use rustc_hash::{FxHashMap, FxHashSet}; +use std::borrow::Cow; use std::cell::RefCell; use std::hash::{BuildHasher, Hash, Hasher}; +use std::sync::Arc; use twox_hash::RandomXxHashBuilder64; #[allow(missing_debug_implementations)] pub struct Pipeline { + system: Option<System>, renderers: Vec<glyphon::TextRenderer>, atlas: glyphon::TextAtlas, - cache: glyphon::SwashCache<'static>, - measurement_cache: RefCell<Cache>, - render_cache: Cache, layer: usize, } -struct Cache { - entries: FxHashMap<KeyHash, glyphon::Buffer<'static>>, - recently_used: FxHashSet<KeyHash>, - hasher: RandomXxHashBuilder64, -} - -impl Cache { - fn new() -> Self { - Self { - entries: FxHashMap::default(), - recently_used: FxHashSet::default(), - hasher: RandomXxHashBuilder64::default(), - } - } - - fn get(&self, key: &KeyHash) -> Option<&glyphon::Buffer<'static>> { - self.entries.get(key) - } - - fn allocate( - &mut self, - key: Key<'_>, - ) -> (KeyHash, &mut glyphon::Buffer<'static>) { - let hash = { - let mut hasher = self.hasher.build_hasher(); - - key.content.hash(&mut hasher); - (key.size as i32).hash(&mut hasher); - key.font.hash(&mut hasher); - (key.bounds.width as i32).hash(&mut hasher); - (key.bounds.height as i32).hash(&mut hasher); - key.color.into_rgba8().hash(&mut hasher); - - hasher.finish() - }; +#[ouroboros::self_referencing] +struct System { + fonts: glyphon::FontSystem, - if !self.entries.contains_key(&hash) { - let metrics = - glyphon::Metrics::new(key.size as i32, (key.size * 1.2) as i32); + #[borrows(fonts)] + #[not_covariant] + cache: glyphon::SwashCache<'this>, - let mut buffer = glyphon::Buffer::new(&FONT_SYSTEM, metrics); + #[borrows(fonts)] + #[not_covariant] + measurement_cache: RefCell<Cache<'this>>, - buffer.set_size(key.bounds.width as i32, key.bounds.height as i32); - buffer.set_text( - key.content, - glyphon::Attrs::new().family(to_family(key.font)).color({ - let [r, g, b, a] = key.color.into_linear(); - - glyphon::Color::rgba( - (r * 255.0) as u8, - (g * 255.0) as u8, - (b * 255.0) as u8, - (a * 255.0) as u8, - ) - }), - ); - - let _ = self.entries.insert(hash, buffer); - } - - let _ = self.recently_used.insert(hash); - - (hash, self.entries.get_mut(&hash).unwrap()) - } - - fn trim(&mut self) { - self.entries - .retain(|key, _| self.recently_used.contains(key)); - - self.recently_used.clear(); - } + #[borrows(fonts)] + #[not_covariant] + render_cache: Cache<'this>, } -#[derive(Debug, Clone, Copy)] -struct Key<'a> { - content: &'a str, - size: f32, - font: Font, - bounds: Size, - color: Color, -} - -type KeyHash = u64; - -// TODO: Share with `iced_graphics` -static FONT_SYSTEM: once_cell::sync::Lazy<glyphon::FontSystem> = - once_cell::sync::Lazy::new(glyphon::FontSystem::new); - impl Pipeline { pub fn new( device: &wgpu::Device, @@ -114,15 +43,41 @@ impl Pipeline { format: wgpu::TextureFormat, ) -> Self { Pipeline { + system: Some( + SystemBuilder { + fonts: glyphon::FontSystem::new(), + cache_builder: |fonts| glyphon::SwashCache::new(fonts), + measurement_cache_builder: |_| RefCell::new(Cache::new()), + render_cache_builder: |_| Cache::new(), + } + .build(), + ), renderers: Vec::new(), atlas: glyphon::TextAtlas::new(device, queue, format), - cache: glyphon::SwashCache::new(&FONT_SYSTEM), - measurement_cache: RefCell::new(Cache::new()), - render_cache: Cache::new(), layer: 0, } } + pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) { + let heads = self.system.take().unwrap().into_heads(); + + let (locale, mut db) = heads.fonts.into_locale_and_db(); + + db.load_font_source(glyphon::fontdb::Source::Binary(Arc::new( + bytes.to_owned(), + ))); + + self.system = Some( + SystemBuilder { + fonts: glyphon::FontSystem::new_with_locale_and_db(locale, db), + cache_builder: |fonts| glyphon::SwashCache::new(fonts), + measurement_cache_builder: |_| RefCell::new(Cache::new()), + render_cache_builder: |_| Cache::new(), + } + .build(), + ); + } + pub fn prepare( &mut self, device: &wgpu::Device, @@ -132,93 +87,100 @@ impl Pipeline { scale_factor: f32, target_size: Size<u32>, ) { - if self.renderers.len() <= self.layer { - self.renderers - .push(glyphon::TextRenderer::new(device, queue)); - } - - let renderer = &mut self.renderers[self.layer]; - - let keys: Vec<_> = sections - .iter() - .map(|section| { - let (key, _) = self.render_cache.allocate(Key { - content: section.content, - size: section.size * scale_factor, - font: section.font, - bounds: Size { - width: section.bounds.width * scale_factor, - height: section.bounds.height * scale_factor, + self.system.as_mut().unwrap().with_mut(|fields| { + if self.renderers.len() <= self.layer { + self.renderers + .push(glyphon::TextRenderer::new(device, queue)); + } + + let renderer = &mut self.renderers[self.layer]; + + let keys: Vec<_> = sections + .iter() + .map(|section| { + let (key, _) = fields.render_cache.allocate( + fields.fonts, + Key { + content: section.content, + size: section.size * scale_factor, + font: section.font, + bounds: Size { + width: section.bounds.width * scale_factor, + height: section.bounds.height * scale_factor, + }, + color: section.color, + }, + ); + + key + }) + .collect(); + + let bounds = glyphon::TextBounds { + left: (bounds.x * scale_factor) as i32, + top: (bounds.y * scale_factor) as i32, + right: ((bounds.x + bounds.width) * scale_factor) as i32, + bottom: ((bounds.y + bounds.height) * scale_factor) as i32, + }; + + let text_areas: Vec<_> = sections + .iter() + .zip(keys.iter()) + .map(|(section, key)| { + let buffer = fields + .render_cache + .get(key) + .expect("Get cached buffer"); + + let x = section.bounds.x * scale_factor; + let y = section.bounds.y * scale_factor; + + let (total_lines, max_width) = buffer + .layout_runs() + .enumerate() + .fold((0, 0.0), |(_, max), (i, buffer)| { + (i + 1, buffer.line_w.max(max)) + }); + + let total_height = + total_lines as f32 * section.size * 1.2 * scale_factor; + + let left = match section.horizontal_alignment { + alignment::Horizontal::Left => x, + alignment::Horizontal::Center => x - max_width / 2.0, + alignment::Horizontal::Right => x - max_width, + }; + + let top = match section.vertical_alignment { + alignment::Vertical::Top => y, + alignment::Vertical::Center => y - total_height / 2.0, + alignment::Vertical::Bottom => y - total_height, + }; + + glyphon::TextArea { + buffer, + left: left as i32, + top: top as i32, + bounds, + } + }) + .collect(); + + renderer + .prepare( + device, + queue, + &mut self.atlas, + glyphon::Resolution { + width: target_size.width, + height: target_size.height, }, - color: section.color, - }); - - key - }) - .collect(); - - let bounds = glyphon::TextBounds { - left: (bounds.x * scale_factor) as i32, - top: (bounds.y * scale_factor) as i32, - right: ((bounds.x + bounds.width) * scale_factor) as i32, - bottom: ((bounds.y + bounds.height) * scale_factor) as i32, - }; - - let text_areas: Vec<_> = sections - .iter() - .zip(keys.iter()) - .map(|(section, key)| { - let buffer = - self.render_cache.get(key).expect("Get cached buffer"); - - let x = section.bounds.x * scale_factor; - let y = section.bounds.y * scale_factor; - - let (total_lines, max_width) = buffer - .layout_runs() - .enumerate() - .fold((0, 0.0), |(_, max), (i, buffer)| { - (i + 1, buffer.line_w.max(max)) - }); - - let total_height = - total_lines as f32 * section.size * 1.2 * scale_factor; - - let left = match section.horizontal_alignment { - alignment::Horizontal::Left => x, - alignment::Horizontal::Center => x - max_width / 2.0, - alignment::Horizontal::Right => x - max_width, - }; - - let top = match section.vertical_alignment { - alignment::Vertical::Top => y, - alignment::Vertical::Center => y - total_height / 2.0, - alignment::Vertical::Bottom => y - total_height, - }; - - glyphon::TextArea { - buffer, - left: left as i32, - top: top as i32, - bounds, - } - }) - .collect(); - - renderer - .prepare( - device, - queue, - &mut self.atlas, - glyphon::Resolution { - width: target_size.width, - height: target_size.height, - }, - &text_areas, - glyphon::Color::rgb(0, 0, 0), - &mut self.cache, - ) - .expect("Prepare text sections"); + &text_areas, + glyphon::Color::rgb(0, 0, 0), + fields.cache, + ) + .expect("Prepare text sections"); + }); } pub fn render( @@ -251,7 +213,10 @@ impl Pipeline { pub fn end_frame(&mut self) { self.renderers.truncate(self.layer); - self.render_cache.trim(); + self.system + .as_mut() + .unwrap() + .with_render_cache_mut(|cache| cache.trim()); self.layer = 0; } @@ -263,24 +228,29 @@ impl Pipeline { font: Font, bounds: Size, ) -> (f32, f32) { - let mut measurement_cache = self.measurement_cache.borrow_mut(); - - let (_, paragraph) = measurement_cache.allocate(Key { - content, - size: size, - font, - bounds, - color: Color::BLACK, - }); + self.system.as_ref().unwrap().with(|fields| { + let mut measurement_cache = fields.measurement_cache.borrow_mut(); + + let (_, paragraph) = measurement_cache.allocate( + fields.fonts, + Key { + content, + size: size, + font, + bounds, + color: Color::BLACK, + }, + ); - let (total_lines, max_width) = paragraph - .layout_runs() - .enumerate() - .fold((0, 0.0), |(_, max), (i, buffer)| { - (i + 1, buffer.line_w.max(max)) - }); + let (total_lines, max_width) = paragraph + .layout_runs() + .enumerate() + .fold((0, 0.0), |(_, max), (i, buffer)| { + (i + 1, buffer.line_w.max(max)) + }); - (max_width, size * 1.2 * total_lines as f32) + (max_width, size * 1.2 * total_lines as f32) + }) } pub fn hit_test( @@ -292,23 +262,31 @@ impl Pipeline { point: iced_native::Point, _nearest_only: bool, ) -> Option<Hit> { - let mut measurement_cache = self.measurement_cache.borrow_mut(); - - let (_, paragraph) = measurement_cache.allocate(Key { - content, - size: size, - font, - bounds, - color: Color::BLACK, - }); + self.system.as_ref().unwrap().with(|fields| { + let mut measurement_cache = fields.measurement_cache.borrow_mut(); + + let (_, paragraph) = measurement_cache.allocate( + fields.fonts, + Key { + content, + size: size, + font, + bounds, + color: Color::BLACK, + }, + ); - let cursor = paragraph.hit(point.x as i32, point.y as i32)?; + let cursor = paragraph.hit(point.x as i32, point.y as i32)?; - Some(Hit::CharOffset(cursor.index)) + Some(Hit::CharOffset(cursor.index)) + }) } pub fn trim_measurement_cache(&mut self) { - self.measurement_cache.borrow_mut().trim(); + self.system + .as_mut() + .unwrap() + .with_measurement_cache_mut(|cache| cache.borrow_mut().trim()); } } @@ -322,3 +300,87 @@ fn to_family(font: Font) -> glyphon::Family<'static> { Font::Monospace => glyphon::Family::Monospace, } } + +struct Cache<'a> { + entries: FxHashMap<KeyHash, glyphon::Buffer<'a>>, + recently_used: FxHashSet<KeyHash>, + hasher: RandomXxHashBuilder64, +} + +impl<'a> Cache<'a> { + fn new() -> Self { + Self { + entries: FxHashMap::default(), + recently_used: FxHashSet::default(), + hasher: RandomXxHashBuilder64::default(), + } + } + + fn get(&self, key: &KeyHash) -> Option<&glyphon::Buffer<'a>> { + self.entries.get(key) + } + + fn allocate( + &mut self, + fonts: &'a glyphon::FontSystem, + key: Key<'_>, + ) -> (KeyHash, &mut glyphon::Buffer<'a>) { + let hash = { + let mut hasher = self.hasher.build_hasher(); + + key.content.hash(&mut hasher); + (key.size as i32).hash(&mut hasher); + key.font.hash(&mut hasher); + (key.bounds.width as i32).hash(&mut hasher); + (key.bounds.height as i32).hash(&mut hasher); + key.color.into_rgba8().hash(&mut hasher); + + hasher.finish() + }; + + if !self.entries.contains_key(&hash) { + let metrics = + glyphon::Metrics::new(key.size as i32, (key.size * 1.2) as i32); + let mut buffer = glyphon::Buffer::new(&fonts, metrics); + + buffer.set_size(key.bounds.width as i32, key.bounds.height as i32); + buffer.set_text( + key.content, + glyphon::Attrs::new().family(to_family(key.font)).color({ + let [r, g, b, a] = key.color.into_linear(); + + glyphon::Color::rgba( + (r * 255.0) as u8, + (g * 255.0) as u8, + (b * 255.0) as u8, + (a * 255.0) as u8, + ) + }), + ); + + let _ = self.entries.insert(hash, buffer); + } + + let _ = self.recently_used.insert(hash); + + (hash, self.entries.get_mut(&hash).unwrap()) + } + + fn trim(&mut self) { + self.entries + .retain(|key, _| self.recently_used.contains(key)); + + self.recently_used.clear(); + } +} + +#[derive(Debug, Clone, Copy)] +struct Key<'a> { + content: &'a str, + size: f32, + font: Font, + bounds: Size, + color: Color, +} + +type KeyHash = u64; diff --git a/winit/src/application.rs b/winit/src/application.rs index 9781a453..889becad 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -851,6 +851,16 @@ pub fn run_command<A, E>( current_cache = user_interface.into_cache(); *cache = current_cache; } + command::Action::LoadFont { bytes, tagger } => { + use crate::text::Renderer; + + // TODO: Error handling (?) + renderer.load_font(bytes); + + proxy + .send_event(tagger(Ok(()))) + .expect("Send message to event loop"); + } } } } |