WIP on rebuilding with extracted UI framework
This commit is contained in:
+109
-111
@@ -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());
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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>,
|
||||
}
|
||||
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user