app: remove old TEXT_CTX and replace with parley new pattern

This commit is contained in:
jkds
2026-01-06 09:56:34 +01:00
committed by darkfi
parent c4ec4de713
commit 5c2bd621df
8 changed files with 125 additions and 171 deletions

View File

@@ -115,7 +115,6 @@ impl God {
// Abort the application on panic right away
std::panic::set_hook(Box::new(panic_hook));
text::init_txt_ctx();
let file_logging_guard = logger::setup_logging();
info!(target: "main", "Creating the app");

View File

@@ -24,7 +24,7 @@ use crate::{
gfx::Point,
mesh::Color,
prop::{PropertyAtomicGuard, PropertyColor, PropertyFloat32, PropertyStr},
text::{TextContext, TEXT_CTX},
text,
};
use std::cmp::{max, min};
@@ -111,8 +111,7 @@ impl Editor {
underlines.push(compose_start..compose_end);
}
let mut txt_ctx = TEXT_CTX.get().await;
self.layout = txt_ctx.make_layout(
self.layout = text::make_layout(
&self.state.text,
text_color,
font_size,
@@ -175,7 +174,7 @@ impl Editor {
pub fn driver<'a>(
&'a mut self,
_txt_ctx: &'a mut TextContext,
_layout_ctx: &'a mut parley::LayoutContext<Color>,
) -> Option<parley::PlainEditorDriver<'a, Color>> {
None
}

View File

@@ -20,7 +20,7 @@ use crate::{
gfx::Point,
mesh::Color,
prop::{PropertyAtomicGuard, PropertyColor, PropertyFloat32, PropertyStr},
text::{TextContext, FONT_STACK, TEXT_CTX},
text::{self, FONT_STACK},
};
pub struct Editor {
@@ -83,9 +83,11 @@ impl Editor {
styles.insert(parley::StyleProperty::OverflowWrap(parley::OverflowWrap::Anywhere));
*self.editor.edit_styles() = styles;
let mut txt_ctx = TEXT_CTX.get().await;
let (font_ctx, layout_ctx) = txt_ctx.borrow();
self.editor.refresh_layout(font_ctx, layout_ctx);
let mut font_ctx = text::GLOBAL_FONT_CTX.clone();
text::THREAD_LAYOUT_CTX.with(|layout_ctx| {
let mut layout_ctx = layout_ctx.borrow_mut();
self.editor.refresh_layout(&mut font_ctx, &mut layout_ctx);
});
}
pub fn layout(&self) -> &parley::Layout<Color> {
@@ -106,19 +108,20 @@ impl Editor {
}
pub async fn insert(&mut self, txt: &str, atom: &mut PropertyAtomicGuard) {
let mut txt_ctx = TEXT_CTX.get().await;
let (font_ctx, layout_ctx) = txt_ctx.borrow();
let mut drv = self.editor.driver(font_ctx, layout_ctx);
drv.insert_or_replace_selection(&txt);
self.on_buffer_changed(atom).await;
text::THREAD_LAYOUT_CTX.with(|layout_ctx| {
let mut layout_ctx = layout_ctx.borrow_mut();
let mut drv = self.driver(&mut layout_ctx);
drv.insert_or_replace_selection(&txt);
self.on_buffer_changed(atom).await;
});
}
pub fn driver<'a>(
&'a mut self,
txt_ctx: &'a mut TextContext,
layout_ctx: &'a mut parley::LayoutContext<Color>,
) -> Option<parley::PlainEditorDriver<'a, Color>> {
let (font_ctx, layout_ctx) = txt_ctx.borrow();
Some(self.editor.driver(font_ctx, layout_ctx))
let mut font_ctx = text::GLOBAL_FONT_CTX.clone();
Some(self.editor.driver(&mut font_ctx, layout_ctx))
}
pub fn set_width(&mut self, w: f32) {
@@ -141,10 +144,11 @@ impl Editor {
/// Android uses byte indexes whereas parley has its own things. So this API is a compromise
/// between them both.
pub async fn set_selection(&mut self, select_start: usize, select_end: usize) {
let mut txt_ctx = TEXT_CTX.get().await;
let (font_ctx, layout_ctx) = txt_ctx.borrow();
let mut drv = self.editor.driver(font_ctx, layout_ctx);
drv.select_byte_range(select_start, select_end);
text::THREAD_LAYOUT_CTX.with(|layout_ctx| {
let mut layout_ctx = layout_ctx.borrow_mut();
let mut drv = self.driver(&mut layout_ctx);
drv.select_byte_range(select_start, select_end);
});
}
#[allow(dead_code)]

View File

@@ -16,12 +16,15 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use async_lock::Mutex as AsyncMutex;
use std::{
cell::RefCell,
ops::Range,
sync::{Arc, OnceLock},
sync::Arc,
};
use fontique::{Collection, CollectionOptions, SourceCache, SourceCacheOptions};
use once_cell::sync::Lazy;
use crate::{mesh::Color, util::spawn_thread};
pub mod atlas;
@@ -30,107 +33,84 @@ pub use editor::Editor;
mod render;
pub use render::{render_layout, render_layout_with_opts, DebugRenderOptions};
use darkfi::system::CondVar;
pub struct AsyncGlobal<T> {
cv: CondVar,
val: OnceLock<AsyncMutex<T>>,
}
impl<T> AsyncGlobal<T> {
const fn new() -> Self {
Self { cv: CondVar::new(), val: OnceLock::new() }
}
fn set(&self, val: T) {
self.val.set(AsyncMutex::new(val)).ok().unwrap();
self.cv.notify();
}
pub async fn get<'a>(&'a self) -> async_lock::MutexGuard<'a, T> {
self.cv.wait().await;
self.val.get().unwrap().lock().await
}
}
pub static TEXT_CTX: AsyncGlobal<TextContext> = AsyncGlobal::new();
pub fn init_txt_ctx() {
spawn_thread("init_txt_ctx", || {
// This is quite slow. It takes 300ms
let txt_ctx = TextContext::new();
TEXT_CTX.set(txt_ctx);
// Global shared FontContext (thread-safe via internal Arc<Mutex<>>)
pub static GLOBAL_FONT_CTX: Lazy<parley::FontContext> = Lazy::new(|| {
let collection = Collection::new(CollectionOptions {
shared: true,
system_fonts: false,
});
let source_cache = SourceCache::new(SourceCacheOptions {
shared: true,
});
let mut font_ctx = parley::FontContext { collection, source_cache };
let font_data = include_bytes!("../../ibm-plex-mono-regular.otf") as &[u8];
font_ctx.collection.register_fonts(
peniko::Blob::new(Arc::new(font_data)),
None
);
let font_data = include_bytes!("../../NotoColorEmoji.ttf") as &[u8];
font_ctx.collection.register_fonts(
peniko::Blob::new(Arc::new(font_data)),
None
);
font_ctx
});
// Thread-local LayoutContext
thread_local! {
pub static THREAD_LAYOUT_CTX: RefCell<parley::LayoutContext<'static, Color>> =
RefCell::new(parley::LayoutContext::new());
}
/// Initializing this is expensive ~300ms, but storage is ~2kb.
/// It has to be created once and reused. Currently we use thread local storage.
pub struct TextContext {
font_ctx: parley::FontContext,
layout_ctx: parley::LayoutContext<Color>,
// Public constants
pub const FONT_STACK: &[parley::FontFamily<'_>] = &[
parley::FontFamily::Named(std::borrow::Cow::Borrowed("IBM Plex Mono")),
parley::FontFamily::Named(std::borrow::Cow::Borrowed("Noto Color Emoji")),
];
// FREE FUNCTIONS (no TextContext wrapper!)
pub fn make_layout(
text: &str,
text_color: Color,
font_size: f32,
lineheight: f32,
window_scale: f32,
width: Option<f32>,
underlines: &[Range<usize>],
) -> parley::Layout<Color> {
make_layout2(
text,
text_color,
font_size,
lineheight,
window_scale,
width,
underlines,
&[],
)
}
impl TextContext {
fn new() -> Self {
let layout_ctx = parley::LayoutContext::new();
let mut font_ctx = parley::FontContext::new();
pub fn make_layout2(
text: &str,
text_color: Color,
font_size: f32,
lineheight: f32,
window_scale: f32,
width: Option<f32>,
underlines: &[Range<usize>],
foreground_colors: &[(Range<usize>, Color)],
) -> parley::Layout<Color> {
THREAD_LAYOUT_CTX.with(|layout_ctx| {
let mut layout_ctx = layout_ctx.borrow_mut();
let mut font_ctx = GLOBAL_FONT_CTX.clone();
let font_data = include_bytes!("../../ibm-plex-mono-regular.otf") as &[u8];
let _font_inf =
font_ctx.collection.register_fonts(peniko::Blob::new(Arc::new(font_data)), None);
let font_data = include_bytes!("../../NotoColorEmoji.ttf") as &[u8];
let _font_inf =
font_ctx.collection.register_fonts(peniko::Blob::new(Arc::new(font_data)), None);
//for (family_id, _) in font_inf {
// let family_name = font_ctx.collection.family_name(family_id).unwrap();
// trace!(target: "text", "Loaded font: {family_name}");
//}
Self { font_ctx, layout_ctx }
}
#[cfg(not(target_os = "android"))]
pub fn borrow(&mut self) -> (&mut parley::FontContext, &mut parley::LayoutContext<Color>) {
(&mut self.font_ctx, &mut self.layout_ctx)
}
pub fn make_layout(
&mut self,
text: &str,
text_color: Color,
font_size: f32,
lineheight: f32,
window_scale: f32,
width: Option<f32>,
underlines: &[Range<usize>],
) -> parley::Layout<Color> {
self.make_layout2(
text,
text_color,
font_size,
lineheight,
window_scale,
width,
underlines,
&[],
)
}
pub fn make_layout2(
&mut self,
text: &str,
text_color: Color,
font_size: f32,
lineheight: f32,
window_scale: f32,
width: Option<f32>,
underlines: &[Range<usize>],
foreground_colors: &[(Range<usize>, Color)],
) -> parley::Layout<Color> {
let mut builder =
self.layout_ctx.ranged_builder(&mut self.font_ctx, &text, window_scale, false);
layout_ctx.ranged_builder(&mut font_ctx, text, window_scale, false);
builder.push_default(parley::LineHeight::FontSizeRelative(lineheight));
builder.push_default(parley::StyleProperty::FontSize(font_size));
builder.push_default(parley::StyleProperty::FontStack(parley::FontStack::List(
@@ -147,14 +127,9 @@ impl TextContext {
builder.push(parley::StyleProperty::Brush(*color), range.clone());
}
let mut layout: parley::Layout<Color> = builder.build(&text);
let mut layout: parley::Layout<Color> = builder.build(text);
layout.break_all_lines(width);
layout.align(width, parley::Alignment::Start, parley::AlignmentOptions::default());
layout
}
})
}
pub const FONT_STACK: &[parley::FontFamily<'_>] = &[
parley::FontFamily::Named(std::borrow::Cow::Borrowed("IBM Plex Mono")),
parley::FontFamily::Named(std::borrow::Cow::Borrowed("Noto Color Emoji")),
];

View File

@@ -109,7 +109,6 @@ impl PrivMessage {
fn cache_txt_layout(
&mut self,
txt_ctx: &mut text::TextContext,
clip: &Rectangle,
line_height: f32,
timestamp_width: f32,
@@ -130,7 +129,7 @@ impl PrivMessage {
let nick_color = select_nick_color(&self.nick, nick_colors);
let txt_layout = if self.nick == "NOTICE" {
txt_ctx.make_layout(
text::make_layout(
&linetext,
text_color,
self.font_size,
@@ -142,7 +141,7 @@ impl PrivMessage {
} else {
let body_color = if self.confirmed { text_color } else { UNCONF_COLOR };
let nick_end = self.nick.len() + 1;
txt_ctx.make_layout2(
text::make_layout2(
&linetext,
body_color,
self.font_size,
@@ -173,11 +172,9 @@ impl PrivMessage {
return instrs.clone()
}
let mut txt_ctx = text::TEXT_CTX.get().await;
// Timestamp layout
let timestr = Self::gen_timestr(self.timestamp);
let timestamp_layout = txt_ctx.make_layout(
let timestamp_layout = text::make_layout(
&timestr,
timestamp_color,
self.timestamp_font_size,
@@ -188,7 +185,6 @@ impl PrivMessage {
);
self.cache_txt_layout(
&mut txt_ctx,
clip,
line_height,
timestamp_width,
@@ -196,8 +192,6 @@ impl PrivMessage {
text_color,
);
drop(txt_ctx);
let mut all_instrs = vec![];
// Draw selection background if selected
@@ -307,8 +301,7 @@ impl DateMessage {
let datestr = Self::datestr(self.timestamp);
let mut txt_ctx = text::TEXT_CTX.get().await;
let layout = txt_ctx.make_layout(
let layout = text::make_layout(
&datestr,
timestamp_color,
self.font_size,
@@ -317,7 +310,6 @@ impl DateMessage {
None,
&[],
);
drop(txt_ctx);
let mut instrs = text::render_layout(&layout, render_api, gfxtag!("chatview_datemsg"));
// Cache the instructions
@@ -519,9 +511,8 @@ impl FileMessage {
let file_strs = Self::filestr(&self.file_url, &self.status);
let mut layouts = Vec::with_capacity(file_strs.len());
let mut txt_ctx = text::TEXT_CTX.get().await;
for file_str in &file_strs {
let layout = txt_ctx.make_layout(
let layout = text::make_layout(
file_str,
color,
self.font_size,
@@ -532,7 +523,6 @@ impl FileMessage {
);
layouts.push(layout);
}
drop(txt_ctx);
all_instrs
.push(DrawInstruction::Move(Point::new(timestamp_width + Self::BOX_PADDING_X, 0.)));
@@ -654,7 +644,6 @@ impl Message {
fn cache_txt_layout(
&mut self,
txt_ctx: &mut text::TextContext,
clip: &Rectangle,
line_height: f32,
timestamp_width: f32,
@@ -664,7 +653,6 @@ impl Message {
match self {
Self::Priv(m) => {
m.cache_txt_layout(
txt_ctx,
clip,
line_height,
timestamp_width,
@@ -871,16 +859,13 @@ impl MessageBuffer {
height += msg_spacing;
}
let mut txt_ctx = text::TEXT_CTX.get().await;
msg.cache_txt_layout(
&mut txt_ctx,
&rect,
line_height,
timestamp_width,
&nick_colors,
text_color,
);
drop(txt_ctx);
height += msg.height(line_height);
}
@@ -939,16 +924,13 @@ impl MessageBuffer {
text,
);
let mut txt_ctx = text::TEXT_CTX.get().await;
msg.cache_txt_layout(
&mut txt_ctx,
&rect,
line_height,
timestamp_width,
&nick_colors,
text_color,
);
drop(txt_ctx);
if self.msgs.is_empty() {
self.msgs.push(msg);
@@ -1012,16 +994,13 @@ impl MessageBuffer {
text,
);
let mut txt_ctx = text::TEXT_CTX.get().await;
msg.cache_txt_layout(
&mut txt_ctx,
rect,
line_height,
timestamp_width,
&nick_colors,
text_color,
);
drop(txt_ctx);
let msg_height = msg.height(self.line_height.get());
self.msgs.push(msg);

View File

@@ -501,6 +501,7 @@ impl BaseEdit {
async fn handle_shortcut(
&self,
layout_ctx: &mut parley::LayoutContext<Color>,
key: char,
mods: &KeyMods,
atom: &mut PropertyAtomicGuard,
@@ -516,9 +517,8 @@ impl BaseEdit {
match key {
'a' => {
if action_mod {
let mut txt_ctx = text::TEXT_CTX.get().await;
let mut editor = self.lock_editor().await;
let mut drv = editor.driver(&mut txt_ctx).unwrap();
let mut drv = editor.driver(layout_ctx);
drv.select_all();
if let Some(seltext) = editor.selected_text() {
@@ -553,6 +553,7 @@ impl BaseEdit {
async fn handle_key(
&self,
layout_ctx: &mut parley::LayoutContext<Color>,
key: &KeyCode,
mods: &KeyMods,
atom: &mut PropertyAtomicGuard,
@@ -565,9 +566,8 @@ impl BaseEdit {
t!("handle_key({:?}, {:?}) action_mod={action_mod}", key, mods);
let mut txt_ctx = text::TEXT_CTX.get().await;
let mut editor = self.lock_editor().await;
let mut drv = editor.driver(&mut txt_ctx).unwrap();
let mut drv = editor.driver(layout_ctx);
match key {
KeyCode::Left => {
@@ -1594,7 +1594,10 @@ impl UIObject for BaseEdit {
if repeat {
return false
}
return self.handle_shortcut(key, &mods, atom).await
return text::THREAD_LAYOUT_CTX.with(|layout_ctx| {
let mut layout_ctx = layout_ctx.borrow_mut();
self.handle_shortcut(&mut layout_ctx, key, &mods, atom).await
})
}
// Do nothing
@@ -1637,9 +1640,10 @@ impl UIObject for BaseEdit {
let mut is_handled = false;
for _ in 0..actions {
if self.handle_key(&key, &mods, atom).await {
is_handled = true;
}
is_handled = text::THREAD_LAYOUT_CTX.with(|layout_ctx| {
let mut layout_ctx = layout_ctx.borrow_mut();
self.handle_key(&mut layout_ctx, &key, &mods, atom).await
});
}
is_handled
}
@@ -1680,12 +1684,12 @@ impl UIObject for BaseEdit {
// Move mouse pos within this widget
self.abs_to_local(&mut mouse_pos);
{
let mut txt_ctx = text::TEXT_CTX.get().await;
text::THREAD_LAYOUT_CTX.with(|layout_ctx| {
let mut layout_ctx = layout_ctx.borrow_mut();
let mut editor = self.lock_editor().await;
let mut drv = editor.driver(&mut txt_ctx).unwrap();
let mut drv = editor.driver(&mut layout_ctx);
drv.move_to_point(mouse_pos.x, mouse_pos.y);
}
});
if !self.select_text.is_null(0).unwrap() {
self.select_text.clone().set_null(atom, Role::Internal, 0).unwrap();

View File

@@ -68,11 +68,8 @@ impl EmojiMeshes {
/// Make mesh for this emoji centered at (0, 0)
async fn gen_emoji_mesh(&self, emoji: &str) -> DrawMesh {
//d!("rendering emoji: '{emoji}'");
let mut txt_ctx = text::TEXT_CTX.get().await;
// The params here don't actually matter since we're talking about BMP fixed sizes
let layout = txt_ctx.make_layout(emoji, COLOR_WHITE, self.emoji_size, 1., 1., None, &[]);
drop(txt_ctx);
let layout = text::make_layout(emoji, COLOR_WHITE, self.emoji_size, 1., 1., None, &[]);
let instrs = text::render_layout(&layout, &self.render_api, gfxtag!("emoji_mesh"));

View File

@@ -30,7 +30,7 @@ use crate::{
PropertyRect, PropertyStr, PropertyUint32, Role,
},
scene::{Pimpl, SceneNodeWeak},
text::{self, TEXT_CTX},
text,
util::i18n::I18nBabelFish,
ExecutorPtr,
};
@@ -123,10 +123,7 @@ impl Text {
text
};
let layout = {
let mut txt_ctx = TEXT_CTX.get().await;
txt_ctx.make_layout(&text, text_color, font_size, lineheight, window_scale, None, &[])
};
let layout = text::make_layout(&text, text_color, font_size, lineheight, window_scale, None, &[]);
let mut debug_opts = text::DebugRenderOptions::OFF;
if self.debug.get() {