WIP on rebuilding with extracted UI framework

This commit is contained in:
Nathan Sobo
2021-03-18 13:13:31 -06:00
parent 356bc41752
commit 23308e17a9
33 changed files with 2673 additions and 657 deletions
+109 -111
View File
@@ -16,7 +16,6 @@ use std::{
fmt::{self, Debug},
hash::{Hash, Hasher},
marker::PhantomData,
mem,
rc::{self, Rc},
sync::{Arc, Weak},
};
@@ -66,9 +65,8 @@ pub trait UpdateView {
pub struct App(Rc<RefCell<MutableAppContext>>);
impl App {
#[cfg(test)]
pub fn run<T, F: Future<Output = T>>(f: impl FnOnce(App) -> F) -> T {
let foreground = Rc::new(executor::Foreground::new().unwrap());
pub fn test<T, F: Future<Output = T>>(f: impl FnOnce(App) -> F) -> T {
let foreground = Rc::new(executor::Foreground::test());
let app = Self(Rc::new(RefCell::new(
MutableAppContext::with_foreground_executor(foreground.clone()),
)));
@@ -297,7 +295,7 @@ pub struct MutableAppContext {
impl MutableAppContext {
pub fn new() -> Result<Self> {
Ok(Self::with_foreground_executor(Rc::new(
executor::Foreground::new()?,
executor::Foreground::platform(todo!())?,
)))
}
@@ -590,11 +588,11 @@ impl MutableAppContext {
self.ctx.windows.get_mut(&window_id).unwrap().root_view = Some(root_handle.clone().into());
self.focus(window_id, root_handle.id());
self.emit_ui_update(UiUpdate::OpenWindow {
window_id,
width: 1024.0,
height: 768.0,
});
// self.emit_ui_update(UiUpdate::OpenWindow {
// window_id,
// width: 1024.0,
// height: 768.0,
// });
(window_id, root_handle)
}
@@ -1175,8 +1173,8 @@ impl AppContext {
.map(|w| {
w.views
.iter()
.map(|(id, view)| view.render(self))
.collect::<HashMap<_, _>>()
.map(|(id, view)| (*id, view.render(self)))
.collect::<HashMap<_, Box<dyn Element>>>()
})
.ok_or(anyhow!("window not found"))
}
@@ -1287,7 +1285,7 @@ where
}
fn render<'a>(&self, app: &AppContext) -> Box<dyn Element> {
View::render(self, bump, app)
View::render(self, app)
}
fn on_focus(&mut self, app: &mut MutableAppContext, window_id: usize, view_id: usize) {
@@ -2392,7 +2390,7 @@ mod tests {
type Event = ();
}
App::run(|mut app| async move {
App::test(|mut app| async move {
let handle = app.add_model(|_| Model::default());
handle
.update(&mut app, |_, c| {
@@ -2425,7 +2423,7 @@ mod tests {
type Event = ();
}
App::run(|mut app| async move {
App::test(|mut app| async move {
let handle = app.add_model(|_| Model::default());
handle
.update(&mut app, |_, c| {
@@ -2454,7 +2452,7 @@ mod tests {
impl super::View for View {
fn render<'a>(&self, _: &AppContext) -> Box<dyn Element> {
Empty::new().finish(bump)
Empty::new().boxed()
}
fn ui_name() -> &'static str {
@@ -2525,8 +2523,8 @@ mod tests {
}
impl super::View for View {
fn render<'a>(&self, bump: &'a Bump, _: &AppContext) -> Box<dyn Element> {
Empty::new().finish(bump)
fn render<'a>(&self, _: &AppContext) -> Box<dyn Element> {
Empty::new().boxed()
}
fn ui_name() -> &'static str {
@@ -2581,8 +2579,8 @@ mod tests {
}
impl super::View for View {
fn render<'a>(&self, bump: &'a Bump, _: &AppContext) -> Box<dyn Element> {
Empty::new().finish(bump)
fn render<'a>(&self, _: &AppContext) -> Box<dyn Element> {
Empty::new().boxed()
}
fn ui_name() -> &'static str {
@@ -2634,8 +2632,8 @@ mod tests {
}
impl super::View for View {
fn render<'a>(&self, bump: &'a Bump, _: &AppContext) -> Box<dyn Element> {
Empty::new().finish(bump)
fn render<'a>(&self, _: &AppContext) -> Box<dyn Element> {
Empty::new().boxed()
}
fn ui_name() -> &'static str {
@@ -2679,8 +2677,8 @@ mod tests {
}
impl super::View for View {
fn render<'a>(&self, bump: &'a Bump, _: &AppContext) -> Box<dyn Element> {
Empty::new().finish(bump)
fn render<'a>(&self, _: &AppContext) -> Box<dyn Element> {
Empty::new().boxed()
}
fn ui_name() -> &'static str {
@@ -2729,8 +2727,8 @@ mod tests {
}
impl super::View for View {
fn render<'a>(&self, bump: &'a Bump, _: &AppContext) -> Box<dyn Element> {
Empty::new().finish(bump)
fn render<'a>(&self, _: &AppContext) -> Box<dyn Element> {
Empty::new().boxed()
}
fn ui_name() -> &'static str {
@@ -2790,8 +2788,8 @@ mod tests {
}
impl super::View for View {
fn render<'a>(&self, bump: &'a Bump, _: &AppContext) -> Box<dyn Element> {
Empty::new().finish(bump)
fn render<'a>(&self, _: &AppContext) -> Box<dyn Element> {
Empty::new().boxed()
}
fn ui_name() -> &'static str {
@@ -2799,7 +2797,7 @@ mod tests {
}
}
App::run(|mut app| async move {
App::test(|mut app| async move {
let (_, handle) = app.add_window(|_| View::default());
handle
.update(&mut app, |_, c| {
@@ -2832,8 +2830,8 @@ mod tests {
}
impl super::View for View {
fn render<'a>(&self, bump: &'a Bump, _: &AppContext) -> Box<dyn Element> {
Empty::new().finish(bump)
fn render<'a>(&self, _: &AppContext) -> Box<dyn Element> {
Empty::new().boxed()
}
fn ui_name() -> &'static str {
@@ -2841,11 +2839,11 @@ mod tests {
}
}
App::run(|mut app| async move {
App::test(|mut app| async move {
let (_, handle) = app.add_window(|_| View::default());
handle
.update(&mut app, |_, c| {
c.spawn_stream_local(stream::iter(vec![1, 2, 3]), |me, output, _| {
c.spawn_stream_local(smol::stream::iter(vec![1, 2, 3]), |me, output, _| {
me.events.push(output);
})
})
@@ -2868,8 +2866,8 @@ mod tests {
}
impl View for ViewA {
fn render<'a>(&self, bump: &'a Bump, _: &AppContext) -> Box<dyn Element> {
Empty::new().finish(bump)
fn render<'a>(&self, _: &AppContext) -> Box<dyn Element> {
Empty::new().boxed()
}
fn ui_name() -> &'static str {
@@ -2886,8 +2884,8 @@ mod tests {
}
impl View for ViewB {
fn render<'a>(&self, bump: &'a Bump, _: &AppContext) -> Box<dyn Element> {
Empty::new().finish(bump)
fn render<'a>(&self, _: &AppContext) -> Box<dyn Element> {
Empty::new().boxed()
}
fn ui_name() -> &'static str {
@@ -2990,8 +2988,8 @@ mod tests {
}
impl super::View for View {
fn render<'a>(&self, bump: &'a Bump, _: &AppContext) -> Box<dyn Element> {
Empty::new().finish(bump)
fn render<'a>(&self, _: &AppContext) -> Box<dyn Element> {
Empty::new().boxed()
}
fn ui_name() -> &'static str {
@@ -3049,86 +3047,86 @@ mod tests {
Ok(())
}
#[test]
fn test_ui_and_window_updates() {
struct View {
count: usize,
}
// #[test]
// fn test_ui_and_window_updates() {
// struct View {
// count: usize,
// }
impl Entity for View {
type Event = ();
}
// impl Entity for View {
// type Event = ();
// }
impl super::View for View {
fn render<'a>(&self, bump: &'a Bump, _: &AppContext) -> Box<dyn Element> {
Empty::new().finish(bump)
}
// impl super::View for View {
// fn render<'a>(&self, _: &AppContext) -> Box<dyn Element> {
// Empty::new().boxed()
// }
fn ui_name() -> &'static str {
"View"
}
}
// fn ui_name() -> &'static str {
// "View"
// }
// }
App::run(|mut app| async move {
let (window_id, _) = app.add_window(|_| View { count: 3 });
let view_1 = app.add_view(window_id, |_| View { count: 1 });
let view_2 = app.add_view(window_id, |_| View { count: 2 });
// App::test(|mut app| async move {
// let (window_id, _) = app.add_window(|_| View { count: 3 });
// let view_1 = app.add_view(window_id, |_| View { count: 1 });
// let view_2 = app.add_view(window_id, |_| View { count: 2 });
// Ensure that registering for UI updates after mutating the app still gives us all the
// updates.
let ui_updates = Rc::new(RefCell::new(Vec::new()));
let ui_updates_ = ui_updates.clone();
app.on_ui_update(move |update, _| ui_updates_.borrow_mut().push(update));
// // Ensure that registering for UI updates after mutating the app still gives us all the
// // updates.
// let ui_updates = Rc::new(RefCell::new(Vec::new()));
// let ui_updates_ = ui_updates.clone();
// app.on_ui_update(move |update, _| ui_updates_.borrow_mut().push(update));
assert_eq!(
ui_updates.borrow_mut().drain(..).collect::<Vec<_>>(),
vec![UiUpdate::OpenWindow {
window_id,
width: 1024.0,
height: 768.0,
}]
);
// assert_eq!(
// ui_updates.borrow_mut().drain(..).collect::<Vec<_>>(),
// vec![UiUpdate::OpenWindow {
// window_id,
// width: 1024.0,
// height: 768.0,
// }]
// );
let window_invalidations = Rc::new(RefCell::new(Vec::new()));
let window_invalidations_ = window_invalidations.clone();
app.on_window_invalidated(window_id, move |update, _| {
window_invalidations_.borrow_mut().push(update)
});
// let window_invalidations = Rc::new(RefCell::new(Vec::new()));
// let window_invalidations_ = window_invalidations.clone();
// app.on_window_invalidated(window_id, move |update, _| {
// window_invalidations_.borrow_mut().push(update)
// });
let view_2_id = view_2.id();
view_1.update(&mut app, |view, ctx| {
view.count = 7;
ctx.notify();
drop(view_2);
});
// let view_2_id = view_2.id();
// view_1.update(&mut app, |view, ctx| {
// view.count = 7;
// ctx.notify();
// drop(view_2);
// });
let invalidation = window_invalidations.borrow_mut().drain(..).next().unwrap();
assert_eq!(invalidation.updated.len(), 1);
assert!(invalidation.updated.contains(&view_1.id()));
assert_eq!(invalidation.removed, vec![view_2_id]);
// let invalidation = window_invalidations.borrow_mut().drain(..).next().unwrap();
// assert_eq!(invalidation.updated.len(), 1);
// assert!(invalidation.updated.contains(&view_1.id()));
// assert_eq!(invalidation.removed, vec![view_2_id]);
let view_3 = view_1.update(&mut app, |_, ctx| ctx.add_view(|_| View { count: 8 }));
// let view_3 = view_1.update(&mut app, |_, ctx| ctx.add_view(|_| View { count: 8 }));
let invalidation = window_invalidations.borrow_mut().drain(..).next().unwrap();
assert_eq!(invalidation.updated.len(), 1);
assert!(invalidation.updated.contains(&view_3.id()));
assert!(invalidation.removed.is_empty());
// let invalidation = window_invalidations.borrow_mut().drain(..).next().unwrap();
// assert_eq!(invalidation.updated.len(), 1);
// assert!(invalidation.updated.contains(&view_3.id()));
// assert!(invalidation.removed.is_empty());
view_3
.update(&mut app, |_, ctx| {
ctx.spawn_local(async { 9 }, |me, output, ctx| {
me.count = output;
ctx.notify();
})
})
.await;
// view_3
// .update(&mut app, |_, ctx| {
// ctx.spawn_local(async { 9 }, |me, output, ctx| {
// me.count = output;
// ctx.notify();
// })
// })
// .await;
let invalidation = window_invalidations.borrow_mut().drain(..).next().unwrap();
assert_eq!(invalidation.updated.len(), 1);
assert!(invalidation.updated.contains(&view_3.id()));
assert!(invalidation.removed.is_empty());
});
}
// let invalidation = window_invalidations.borrow_mut().drain(..).next().unwrap();
// assert_eq!(invalidation.updated.len(), 1);
// assert!(invalidation.updated.contains(&view_3.id()));
// assert!(invalidation.removed.is_empty());
// });
// }
#[test]
fn test_finish_pending_tasks() {
@@ -3139,8 +3137,8 @@ mod tests {
}
impl super::View for View {
fn render<'a>(&self, bump: &'a Bump, _: &AppContext) -> Box<dyn Element> {
Empty::new().finish(bump)
fn render<'a>(&self, _: &AppContext) -> Box<dyn Element> {
Empty::new().boxed()
}
fn ui_name() -> &'static str {
@@ -3154,20 +3152,20 @@ mod tests {
type Event = ();
}
App::run(|mut app| async move {
App::test(|mut app| async move {
let model = app.add_model(|_| Model);
let (_, view) = app.add_window(|_| View);
model.update(&mut app, |_, ctx| {
let _ = ctx.spawn(async {}, |_, _, _| {});
let _ = ctx.spawn_local(async {}, |_, _, _| {});
let _ = ctx.spawn_stream_local(futures::stream::iter(vec![1, 2, 3]), |_, _, _| {});
let _ = ctx.spawn_stream_local(smol::stream::iter(vec![1, 2, 3]), |_, _, _| {});
});
view.update(&mut app, |_, ctx| {
let _ = ctx.spawn(async {}, |_, _, _| {});
let _ = ctx.spawn_local(async {}, |_, _, _| {});
let _ = ctx.spawn_stream_local(futures::stream::iter(vec![1, 2, 3]), |_, _, _| {});
let _ = ctx.spawn_stream_local(smol::stream::iter(vec![1, 2, 3]), |_, _, _| {});
});
assert!(!app.0.borrow().task_callbacks.is_empty());
+27
View File
@@ -0,0 +1,27 @@
use anyhow::{anyhow, Result};
use std::borrow::Cow;
pub trait AssetSource: 'static {
fn load(&self, path: &str) -> Result<Cow<[u8]>>;
}
impl AssetSource for () {
fn load(&self, path: &str) -> Result<Cow<[u8]>> {
Err(anyhow!(
"get called on empty asset provider with \"{}\"",
path
))
}
}
pub struct AssetCache {
source: Box<dyn AssetSource>,
}
impl AssetCache {
pub fn new(source: impl AssetSource) -> Self {
Self {
source: Box::new(source),
}
}
}
+1
View File
@@ -2,6 +2,7 @@ use crate::{
color::ColorU,
fonts::{FamilyId, Properties},
geometry::vector::{vec2f, Vector2F},
text_layout::Line,
AfterLayoutContext, AppContext, Element, Event, EventContext, LayoutContext, MutableAppContext,
PaintContext, SizeConstraint,
};
+5 -2
View File
@@ -49,7 +49,10 @@ pub trait Element {
fn dispatch_event(&self, event: &Event, ctx: &mut EventContext, app: &AppContext) -> bool;
fn boxed(self) -> Box<dyn Element> {
fn boxed(self) -> Box<dyn Element>
where
Self: 'static + Sized,
{
Box::new(self)
}
}
@@ -60,7 +63,7 @@ pub trait ParentElement<'a>: Extend<Box<dyn Element>> + Sized {
}
fn add_child(&mut self, child: Box<dyn Element>) {
self.add_childen(Some(child));
self.add_children(Some(child));
}
fn with_children(mut self, children: impl IntoIterator<Item = Box<dyn Element>>) -> Self {
+6 -11
View File
@@ -1,12 +1,7 @@
use crate::{
geometry::{
rect::RectF,
vector::{vec2f, Vector2F},
},
AfterLayoutContext, AppContext, Element, Event, EventContext, LayoutContext, MutableAppContext,
PaintContext, SizeConstraint,
geometry::vector::Vector2F, AfterLayoutContext, AppContext, Element, Event, EventContext,
LayoutContext, MutableAppContext, PaintContext, SizeConstraint,
};
use std::rc::Rc;
pub struct Svg {
path: String,
@@ -65,10 +60,10 @@ impl Element for Svg {
fn after_layout(&mut self, _: &mut AfterLayoutContext, _: &mut MutableAppContext) {}
fn paint(&mut self, origin: Vector2F, ctx: &mut PaintContext, _: &AppContext) {
if let Some(tree) = self.tree.as_ref() {
ctx.canvas
.draw_svg(tree, RectF::new(origin, self.size.unwrap()));
}
// if let Some(tree) = self.tree.as_ref() {
// ctx.canvas
// .draw_svg(tree, RectF::new(origin, self.size.unwrap()));
// }
}
fn size(&self) -> Option<Vector2F> {
+1 -1
View File
@@ -136,7 +136,7 @@ where
SizeConstraint::new(vec2f(size.x(), 0.0), vec2f(size.x(), f32::INFINITY));
let first_item = (self.build_items)(0..1, app).next();
if let Some(first_item) = first_item {
if let Some(mut first_item) = first_item {
let mut item_size = first_item.layout(item_constraint, ctx, app);
item_size.set_x(size.x());
item_constraint.min = item_size;
-11
View File
@@ -285,14 +285,3 @@ fn push_font(state: &mut FontCacheState, font: Font) -> FontId {
state.fonts_by_name.insert(name, font_id);
font_id
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_render_emoji() {
let ctx = FontCache::new();
let _ = ctx.render_emoji(0, 16.0);
}
}
+11 -7
View File
@@ -1,17 +1,21 @@
mod app;
pub use app::*;
mod assets;
pub use assets::*;
pub mod elements;
pub mod executor;
mod fonts;
pub mod keymap;
pub mod platform;
pub mod fonts;
pub use fonts::FontCache;
mod presenter;
mod scene;
pub use scene::Scene;
pub mod text_layout;
pub use text_layout::TextLayoutCache;
mod util;
pub use app::*;
pub use elements::Element;
pub mod executor;
pub mod keymap;
pub mod platform;
pub use pathfinder_color as color;
pub use pathfinder_geometry as geometry;
pub use platform::Event;
pub use presenter::*;
use scene::Scene;
+10 -8
View File
@@ -1,8 +1,10 @@
use crate::{
app::{AppContext, MutableAppContext, WindowInvalidation},
elements::Element,
fonts::FontCache,
platform::Event,
Scene,
text_layout::TextLayoutCache,
AssetCache, Scene,
};
use pathfinder_geometry::vector::{vec2f, Vector2F};
use std::{any::Any, collections::HashMap, rc::Rc};
@@ -12,7 +14,7 @@ pub struct Presenter {
rendered_views: HashMap<usize, Box<dyn Element>>,
parents: HashMap<usize, usize>,
font_cache: Rc<FontCache>,
text_layout_cache: LayoutCache,
text_layout_cache: TextLayoutCache,
asset_cache: Rc<AssetCache>,
}
@@ -28,7 +30,7 @@ impl Presenter {
rendered_views: app.render_views(window_id).unwrap(),
parents: HashMap::new(),
font_cache,
text_layout_cache: LayoutCache::new(),
text_layout_cache: TextLayoutCache::new(),
asset_cache,
}
}
@@ -82,7 +84,7 @@ impl Presenter {
}
}
fn paint(&mut self, size: Vector2F, scale_factor: f32, app: &AppContext) -> Scene {
fn paint(&mut self, _size: Vector2F, _scale_factor: f32, _app: &AppContext) -> Scene {
// let mut canvas = Canvas::new(size * scale_factor).get_context_2d(self.font_context.clone());
// canvas.scale(scale_factor);
@@ -135,7 +137,7 @@ pub struct LayoutContext<'a> {
rendered_views: &'a mut HashMap<usize, Box<dyn Element>>,
parents: &'a mut HashMap<usize, usize>,
pub font_cache: &'a FontCache,
pub text_layout_cache: &'a LayoutCache,
pub text_layout_cache: &'a TextLayoutCache,
pub asset_cache: &'a AssetCache,
view_stack: Vec<usize>,
}
@@ -157,7 +159,7 @@ impl<'a> LayoutContext<'a> {
pub struct AfterLayoutContext<'a> {
rendered_views: &'a mut HashMap<usize, Box<dyn Element>>,
pub font_cache: &'a FontCache,
pub text_layout_cache: &'a LayoutCache,
pub text_layout_cache: &'a TextLayoutCache,
}
impl<'a> AfterLayoutContext<'a> {
@@ -173,7 +175,7 @@ pub struct PaintContext<'a> {
rendered_views: &'a mut HashMap<usize, Box<dyn Element>>,
// pub canvas: &'a mut CanvasRenderingContext2D,
pub font_cache: &'a FontCache,
pub text_layout_cache: &'a LayoutCache,
pub text_layout_cache: &'a TextLayoutCache,
}
impl<'a> PaintContext<'a> {
@@ -189,7 +191,7 @@ pub struct EventContext<'a> {
rendered_views: &'a HashMap<usize, Box<dyn Element>>,
actions: Vec<(usize, &'static str, Box<dyn Any>)>,
pub font_cache: &'a FontCache,
pub text_layout_cache: &'a LayoutCache,
pub text_layout_cache: &'a TextLayoutCache,
view_stack: Vec<usize>,
}
+407
View File
@@ -0,0 +1,407 @@
use crate::{
color::ColorU,
fonts::{FontCache, FontId, GlyphId},
geometry::rect::RectF,
scene::Scene,
};
use core_foundation::{
attributed_string::CFMutableAttributedString,
base::{CFRange, TCFType},
string::CFString,
};
use core_text::{font::CTFont, line::CTLine, string_attributes::kCTFontAttributeName};
use ordered_float::OrderedFloat;
use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
use pathfinder_geometry::vector::{vec2f, Vector2F};
use smallvec::SmallVec;
use std::{
borrow::Borrow,
char,
collections::HashMap,
convert::TryFrom,
hash::{Hash, Hasher},
ops::Range,
sync::Arc,
};
pub struct TextLayoutCache {
prev_frame: Mutex<HashMap<CacheKeyValue, Arc<Line>>>,
curr_frame: RwLock<HashMap<CacheKeyValue, Arc<Line>>>,
}
impl TextLayoutCache {
pub fn new() -> Self {
Self {
prev_frame: Mutex::new(HashMap::new()),
curr_frame: RwLock::new(HashMap::new()),
}
}
pub fn finish_frame(&self) {
let mut prev_frame = self.prev_frame.lock();
let mut curr_frame = self.curr_frame.write();
std::mem::swap(&mut *prev_frame, &mut *curr_frame);
curr_frame.clear();
}
pub fn layout_str<'a>(
&'a self,
text: &'a str,
font_size: f32,
runs: &'a [(Range<usize>, FontId)],
font_cache: &'a FontCache,
) -> Arc<Line> {
let key = &CacheKeyRef {
text,
font_size: OrderedFloat(font_size),
runs,
} as &dyn CacheKey;
let curr_frame = self.curr_frame.upgradable_read();
if let Some(line) = curr_frame.get(key) {
return line.clone();
}
let mut curr_frame = RwLockUpgradableReadGuard::upgrade(curr_frame);
if let Some((key, line)) = self.prev_frame.lock().remove_entry(key) {
curr_frame.insert(key, line.clone());
line.clone()
} else {
let line = Arc::new(layout_str(text, font_size, runs, font_cache));
let key = CacheKeyValue {
text: text.into(),
font_size: OrderedFloat(font_size),
runs: SmallVec::from(runs),
};
curr_frame.insert(key, line.clone());
line
}
}
}
trait CacheKey {
fn key<'a>(&'a self) -> CacheKeyRef<'a>;
}
impl<'a> PartialEq for (dyn CacheKey + 'a) {
fn eq(&self, other: &dyn CacheKey) -> bool {
self.key() == other.key()
}
}
impl<'a> Eq for (dyn CacheKey + 'a) {}
impl<'a> Hash for (dyn CacheKey + 'a) {
fn hash<H: Hasher>(&self, state: &mut H) {
self.key().hash(state)
}
}
#[derive(Eq, PartialEq)]
struct CacheKeyValue {
text: String,
font_size: OrderedFloat<f32>,
runs: SmallVec<[(Range<usize>, FontId); 1]>,
}
impl CacheKey for CacheKeyValue {
fn key<'a>(&'a self) -> CacheKeyRef<'a> {
CacheKeyRef {
text: &self.text.as_str(),
font_size: self.font_size,
runs: self.runs.as_slice(),
}
}
}
impl Hash for CacheKeyValue {
fn hash<H: Hasher>(&self, state: &mut H) {
self.key().hash(state);
}
}
impl<'a> Borrow<dyn CacheKey + 'a> for CacheKeyValue {
fn borrow(&self) -> &(dyn CacheKey + 'a) {
self as &dyn CacheKey
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
struct CacheKeyRef<'a> {
text: &'a str,
font_size: OrderedFloat<f32>,
runs: &'a [(Range<usize>, FontId)],
}
impl<'a> CacheKey for CacheKeyRef<'a> {
fn key<'b>(&'b self) -> CacheKeyRef<'b> {
*self
}
}
#[derive(Default)]
pub struct Line {
pub width: f32,
pub runs: Vec<Run>,
pub len: usize,
font_size: f32,
}
#[derive(Debug)]
pub struct Run {
pub font_id: FontId,
pub glyphs: Vec<Glyph>,
}
#[derive(Debug)]
pub struct Glyph {
pub id: GlyphId,
pub position: Vector2F,
pub index: usize,
}
impl Line {
pub fn x_for_index(&self, index: usize) -> f32 {
for run in &self.runs {
for glyph in &run.glyphs {
if glyph.index == index {
return glyph.position.x();
}
}
}
self.width
}
pub fn index_for_x(&self, x: f32) -> Option<usize> {
if x >= self.width {
None
} else {
for run in self.runs.iter().rev() {
for glyph in run.glyphs.iter().rev() {
if glyph.position.x() <= x {
return Some(glyph.index);
}
}
}
Some(0)
}
}
pub fn paint(
&self,
_origin: Vector2F,
_viewport_rect: RectF,
_colors: &[(Range<usize>, ColorU)],
_scene: Scene,
_font_cache: &FontCache,
) {
// canvas.set_font_size(self.font_size);
// let mut colors = colors.iter().peekable();
// for run in &self.runs {
// let bounding_box = font_cache.bounding_box(run.font_id, self.font_size);
// let ascent = font_cache.scale_metric(
// font_cache.metric(run.font_id, |m| m.ascent),
// run.font_id,
// self.font_size,
// );
// let descent = font_cache.scale_metric(
// font_cache.metric(run.font_id, |m| m.descent),
// run.font_id,
// self.font_size,
// );
// let max_glyph_width = bounding_box.x();
// let font = font_cache.font(run.font_id);
// let font_name = font_cache.font_name(run.font_id);
// let is_emoji = font_cache.is_emoji(run.font_id);
// for glyph in &run.glyphs {
// let glyph_origin = origin + glyph.position - vec2f(0.0, descent);
// if glyph_origin.x() + max_glyph_width < viewport_rect.origin().x() {
// continue;
// }
// if glyph_origin.x() > viewport_rect.upper_right().x() {
// break;
// }
// while let Some((range, color)) = colors.peek() {
// if glyph.index >= range.end {
// colors.next();
// } else {
// if glyph.index == range.start {
// canvas.set_fill_style(FillStyle::Color(*color));
// }
// break;
// }
// }
// if is_emoji {
// match font_cache.render_emoji(glyph.id, self.font_size) {
// Ok(image) => {
// canvas.draw_image(image, RectF::new(glyph_origin, bounding_box));
// }
// Err(error) => log::error!("rasterizing emoji: {}", error),
// }
// } else {
// canvas.fill_glyph(
// &font,
// &font_name,
// glyph.id,
// glyph_origin + vec2f(0.0, ascent),
// );
// }
// }
// }
}
}
pub fn layout_str(
text: &str,
font_size: f32,
runs: &[(Range<usize>, FontId)],
font_cache: &FontCache,
) -> Line {
let mut string = CFMutableAttributedString::new();
string.replace_str(&CFString::new(text), CFRange::init(0, 0));
let mut utf16_lens = text.chars().map(|c| c.len_utf16());
let mut prev_char_ix = 0;
let mut prev_utf16_ix = 0;
for (range, font_id) in runs {
let utf16_start = prev_utf16_ix
+ utf16_lens
.by_ref()
.take(range.start - prev_char_ix)
.sum::<usize>();
let utf16_end = utf16_start
+ utf16_lens
.by_ref()
.take(range.end - range.start)
.sum::<usize>();
prev_char_ix = range.end;
prev_utf16_ix = utf16_end;
let cf_range = CFRange::init(utf16_start as isize, (utf16_end - utf16_start) as isize);
let native_font = font_cache.native_font(*font_id, font_size);
unsafe {
string.set_attribute(cf_range, kCTFontAttributeName, &native_font);
}
}
let line = CTLine::new_with_attributed_string(string.as_concrete_TypeRef());
let width = line.get_typographic_bounds().width as f32;
let mut utf16_chars = text.encode_utf16();
let mut char_ix = 0;
let mut prev_utf16_ix = 0;
let mut runs = Vec::new();
for run in line.glyph_runs().into_iter() {
let font_id = font_cache.font_id_for_native_font(unsafe {
run.attributes()
.unwrap()
.get(kCTFontAttributeName)
.downcast::<CTFont>()
.unwrap()
});
let mut glyphs = Vec::new();
for ((glyph_id, position), utf16_ix) in run
.glyphs()
.iter()
.zip(run.positions().iter())
.zip(run.string_indices().iter())
{
let utf16_ix = usize::try_from(*utf16_ix).unwrap();
char_ix +=
char::decode_utf16(utf16_chars.by_ref().take(utf16_ix - prev_utf16_ix)).count();
prev_utf16_ix = utf16_ix;
glyphs.push(Glyph {
id: *glyph_id as GlyphId,
position: vec2f(position.x as f32, position.y as f32),
index: char_ix,
});
}
runs.push(Run { font_id, glyphs })
}
Line {
width,
runs,
font_size,
len: char_ix + 1,
}
}
#[cfg(test)]
mod tests {
use super::*;
use anyhow::Result;
use font_kit::properties::{
Properties as FontProperties, Style as FontStyle, Weight as FontWeight,
};
#[test]
fn test_layout_str() -> Result<()> {
let mut font_cache = FontCache::new();
let menlo = font_cache.load_family(&["Menlo"])?;
let menlo_regular = font_cache.select_font(menlo, &FontProperties::new())?;
let menlo_italic =
font_cache.select_font(menlo, &FontProperties::new().style(FontStyle::Italic))?;
let menlo_bold =
font_cache.select_font(menlo, &FontProperties::new().weight(FontWeight::BOLD))?;
let line = layout_str(
"hello world 😃",
16.0,
&[
(0..2, menlo_bold),
(2..6, menlo_italic),
(6..13, menlo_regular),
],
&mut font_cache,
);
assert!(font_cache.is_emoji(line.runs.last().unwrap().font_id));
Ok(())
}
#[test]
fn test_char_indices() -> Result<()> {
let mut font_cache = FontCache::new();
let zapfino = font_cache.load_family(&["Zapfino"])?;
let zapfino_regular = font_cache.select_font(zapfino, &FontProperties::new())?;
let menlo = font_cache.load_family(&["Menlo"])?;
let menlo_regular = font_cache.select_font(menlo, &FontProperties::new())?;
let text = "This is, m𐍈re 𐍈r less, Zapfino!𐍈";
let line = layout_str(
text,
16.0,
&[
(0..9, zapfino_regular),
(11..22, menlo_regular),
(22..text.encode_utf16().count(), zapfino_regular),
],
&mut font_cache,
);
assert_eq!(
line.runs
.iter()
.flat_map(|r| r.glyphs.iter())
.map(|g| g.index)
.collect::<Vec<_>>(),
vec![
0, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
31, 32
]
);
Ok(())
}
}
-72
View File
@@ -1,77 +1,5 @@
use rand::prelude::*;
use std::cmp::Ordering;
pub fn pre_inc(value: &mut usize) -> usize {
*value += 1;
*value
}
pub fn post_inc(value: &mut usize) -> usize {
let prev = *value;
*value += 1;
prev
}
pub fn find_insertion_index<'a, F, T, E>(slice: &'a [T], mut f: F) -> Result<usize, E>
where
F: FnMut(&'a T) -> Result<Ordering, E>,
{
use Ordering::*;
let s = slice;
let mut size = s.len();
if size == 0 {
return Ok(0);
}
let mut base = 0usize;
while size > 1 {
let half = size / 2;
let mid = base + half;
// mid is always in [0, size), that means mid is >= 0 and < size.
// mid >= 0: by definition
// mid < size: mid = size / 2 + size / 4 + size / 8 ...
let cmp = f(unsafe { s.get_unchecked(mid) })?;
base = if cmp == Greater { base } else { mid };
size -= half;
}
// base is always in [0, size) because base <= mid.
let cmp = f(unsafe { s.get_unchecked(base) })?;
if cmp == Equal {
Ok(base)
} else {
Ok(base + (cmp == Less) as usize)
}
}
pub struct RandomCharIter<T: Rng>(T);
impl<T: Rng> RandomCharIter<T> {
pub fn new(rng: T) -> Self {
Self(rng)
}
}
impl<T: Rng> Iterator for RandomCharIter<T> {
type Item = char;
fn next(&mut self) -> Option<Self::Item> {
if self.0.gen_bool(1.0 / 5.0) {
Some('\n')
} else {
Some(self.0.gen_range(b'a', b'z' + 1).into())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_find_insertion_index() {
assert_eq!(
find_insertion_index(&[0, 4, 8], |probe| Ok::<Ordering, ()>(probe.cmp(&2))),
Ok(1)
);
}
}