app/editor: add ParleyDriverWrapper which hugely simplifies driver access pattern

This commit is contained in:
darkfi
2026-01-06 13:21:33 +01:00
parent 5c2bd621df
commit 68e73036af
8 changed files with 344 additions and 142 deletions

View File

@@ -16,6 +16,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use std::cmp::{max, min};
use super::driver::ParleyDriverWrapper;
use crate::{
android::{
self,
@@ -26,7 +29,6 @@ use crate::{
prop::{PropertyAtomicGuard, PropertyColor, PropertyFloat32, PropertyStr},
text,
};
use std::cmp::{max, min};
macro_rules! t { ($($arg:tt)*) => { trace!(target: "text::editor::android", $($arg)*); } }
@@ -172,11 +174,8 @@ impl Editor {
self.on_buffer_changed(atom).await;
}
pub fn driver<'a>(
&'a mut self,
_layout_ctx: &'a mut parley::LayoutContext<Color>,
) -> Option<parley::PlainEditorDriver<'a, Color>> {
None
pub fn driver(&mut self) -> ParleyDriverWrapper {
ParleyDriverWrapper::new(&mut self.layout)
}
pub fn set_width(&mut self, w: f32) {

View File

@@ -0,0 +1,282 @@
/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2026 Dyne.org foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#[cfg(not(target_os = "android"))]
use crate::{mesh::Color, text};
pub struct ParleyDriverWrapper<'a> {
#[cfg(not(target_os = "android"))]
editor: &'a mut parley::PlainEditor<Color>,
}
impl<'a> ParleyDriverWrapper<'a> {
#[cfg(not(target_os = "android"))]
pub fn new(editor: &'a mut parley::PlainEditor<Color>) -> Self {
Self { editor }
}
#[cfg(target_os = "android")]
pub fn new(_layout: &mut parley::Layout<Color>) -> Self {
Self {}
}
#[cfg(not(target_os = "android"))]
fn with_driver<F, R>(&mut self, f: F) -> R
where
F: FnOnce(&mut parley::PlainEditorDriver<'_, Color>) -> R,
{
let mut font_ctx = text::GLOBAL_FONT_CTX.clone();
text::THREAD_LAYOUT_CTX.with(|layout_ctx| {
let mut layout_ctx = layout_ctx.borrow_mut();
let mut driver = self.editor.driver(&mut font_ctx, &mut layout_ctx);
f(&mut driver)
})
}
pub fn select_all(&mut self) {
#[cfg(not(target_os = "android"))]
self.with_driver(|drv| drv.select_all());
#[cfg(target_os = "android")]
unimplemented!()
}
pub fn select_word_left(&mut self) {
#[cfg(not(target_os = "android"))]
self.with_driver(|drv| drv.select_word_left());
#[cfg(target_os = "android")]
unimplemented!()
}
pub fn move_word_left(&mut self) {
#[cfg(not(target_os = "android"))]
self.with_driver(|drv| drv.move_word_left());
#[cfg(target_os = "android")]
unimplemented!()
}
pub fn select_left(&mut self) {
#[cfg(not(target_os = "android"))]
self.with_driver(|drv| drv.select_left());
#[cfg(target_os = "android")]
unimplemented!()
}
pub fn move_left(&mut self) {
#[cfg(not(target_os = "android"))]
self.with_driver(|drv| drv.move_left());
#[cfg(target_os = "android")]
unimplemented!()
}
pub fn select_word_right(&mut self) {
#[cfg(not(target_os = "android"))]
self.with_driver(|drv| drv.select_word_right());
#[cfg(target_os = "android")]
unimplemented!()
}
pub fn move_word_right(&mut self) {
#[cfg(not(target_os = "android"))]
self.with_driver(|drv| drv.move_word_right());
#[cfg(target_os = "android")]
unimplemented!()
}
pub fn select_right(&mut self) {
#[cfg(not(target_os = "android"))]
self.with_driver(|drv| drv.select_right());
#[cfg(target_os = "android")]
unimplemented!()
}
pub fn move_right(&mut self) {
#[cfg(not(target_os = "android"))]
self.with_driver(|drv| drv.move_right());
#[cfg(target_os = "android")]
unimplemented!()
}
pub fn select_up(&mut self) {
#[cfg(not(target_os = "android"))]
self.with_driver(|drv| drv.select_up());
#[cfg(target_os = "android")]
unimplemented!()
}
pub fn move_up(&mut self) {
#[cfg(not(target_os = "android"))]
self.with_driver(|drv| drv.move_up());
#[cfg(target_os = "android")]
unimplemented!()
}
pub fn select_down(&mut self) {
#[cfg(not(target_os = "android"))]
self.with_driver(|drv| drv.select_down());
#[cfg(target_os = "android")]
unimplemented!()
}
pub fn move_down(&mut self) {
#[cfg(not(target_os = "android"))]
self.with_driver(|drv| drv.move_down());
#[cfg(target_os = "android")]
unimplemented!()
}
pub fn insert_or_replace_selection(&mut self, text: &str) {
#[cfg(not(target_os = "android"))]
self.with_driver(|drv| drv.insert_or_replace_selection(text));
#[cfg(target_os = "android")]
unimplemented!()
}
pub fn delete(&mut self) {
#[cfg(not(target_os = "android"))]
self.with_driver(|drv| drv.delete());
#[cfg(target_os = "android")]
unimplemented!()
}
pub fn delete_word(&mut self) {
#[cfg(not(target_os = "android"))]
self.with_driver(|drv| drv.delete_word());
#[cfg(target_os = "android")]
unimplemented!()
}
pub fn backdelete(&mut self) {
#[cfg(not(target_os = "android"))]
self.with_driver(|drv| drv.backdelete());
#[cfg(target_os = "android")]
unimplemented!()
}
pub fn backdelete_word(&mut self) {
#[cfg(not(target_os = "android"))]
self.with_driver(|drv| drv.backdelete_word());
#[cfg(target_os = "android")]
unimplemented!()
}
pub fn select_to_text_start(&mut self) {
#[cfg(not(target_os = "android"))]
self.with_driver(|drv| drv.select_to_text_start());
#[cfg(target_os = "android")]
unimplemented!()
}
pub fn move_to_text_start(&mut self) {
#[cfg(not(target_os = "android"))]
self.with_driver(|drv| drv.move_to_text_start());
#[cfg(target_os = "android")]
unimplemented!()
}
pub fn select_to_line_start(&mut self) {
#[cfg(not(target_os = "android"))]
self.with_driver(|drv| drv.select_to_line_start());
#[cfg(target_os = "android")]
unimplemented!()
}
pub fn move_to_line_start(&mut self) {
#[cfg(not(target_os = "android"))]
self.with_driver(|drv| drv.move_to_line_start());
#[cfg(target_os = "android")]
unimplemented!()
}
pub fn select_to_text_end(&mut self) {
#[cfg(not(target_os = "android"))]
self.with_driver(|drv| drv.select_to_text_end());
#[cfg(target_os = "android")]
unimplemented!()
}
pub fn move_to_text_end(&mut self) {
#[cfg(not(target_os = "android"))]
self.with_driver(|drv| drv.move_to_text_end());
#[cfg(target_os = "android")]
unimplemented!()
}
pub fn select_to_line_end(&mut self) {
#[cfg(not(target_os = "android"))]
self.with_driver(|drv| drv.select_to_line_end());
#[cfg(target_os = "android")]
unimplemented!()
}
pub fn move_to_line_end(&mut self) {
#[cfg(not(target_os = "android"))]
self.with_driver(|drv| drv.move_to_line_end());
#[cfg(target_os = "android")]
unimplemented!()
}
pub fn move_to_point(&mut self, x: f32, y: f32) {
#[cfg(not(target_os = "android"))]
self.with_driver(|drv| drv.move_to_point(x, y));
#[cfg(target_os = "android")]
unimplemented!()
}
pub fn extend_selection_to_point(&mut self, x: f32, y: f32) {
#[cfg(not(target_os = "android"))]
self.with_driver(|drv| drv.extend_selection_to_point(x, y));
#[cfg(target_os = "android")]
unimplemented!()
}
pub fn select_byte_range(&mut self, start: usize, end: usize) {
#[cfg(not(target_os = "android"))]
self.with_driver(|drv| drv.select_byte_range(start, end));
#[cfg(target_os = "android")]
unimplemented!()
}
}

View File

@@ -18,6 +18,7 @@
#[cfg(target_os = "android")]
mod android;
mod driver;
#[cfg(target_os = "android")]
pub use android::Editor;

View File

@@ -23,6 +23,8 @@ use crate::{
text::{self, FONT_STACK},
};
use super::driver::ParleyDriverWrapper;
pub struct Editor {
editor: parley::PlainEditor<Color>,
@@ -108,20 +110,12 @@ impl Editor {
}
pub async fn insert(&mut self, txt: &str, atom: &mut PropertyAtomicGuard) {
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;
});
self.driver().insert_or_replace_selection(&txt);
self.on_buffer_changed(atom).await;
}
pub fn driver<'a>(
&'a mut self,
layout_ctx: &'a mut parley::LayoutContext<Color>,
) -> Option<parley::PlainEditorDriver<'a, Color>> {
let mut font_ctx = text::GLOBAL_FONT_CTX.clone();
Some(self.editor.driver(&mut font_ctx, layout_ctx))
pub fn driver(&mut self) -> ParleyDriverWrapper {
ParleyDriverWrapper::new(&mut self.editor)
}
pub fn set_width(&mut self, w: f32) {
@@ -144,11 +138,7 @@ 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) {
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);
});
self.driver().select_byte_range(select_start, select_end);
}
#[allow(dead_code)]

View File

@@ -16,14 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use std::{
cell::RefCell,
ops::Range,
sync::Arc,
};
use fontique::{Collection, CollectionOptions, SourceCache, SourceCacheOptions};
use once_cell::sync::Lazy;
use std::{cell::RefCell, ops::Range, sync::{Arc, LazyLock}};
use parley::fontique::{Collection, CollectionOptions, SourceCache, SourceCacheOptions};
use crate::{mesh::Color, util::spawn_thread};
@@ -34,36 +28,25 @@ mod render;
pub use render::{render_layout, render_layout_with_opts, DebugRenderOptions};
// 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,
});
pub static GLOBAL_FONT_CTX: LazyLock<parley::FontContext> = LazyLock::new(|| {
let collection = Collection::new(CollectionOptions { shared: true, system_fonts: false });
let source_cache = SourceCache::new(SourceCacheOptions {
shared: true,
});
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
);
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.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>> =
pub static THREAD_LAYOUT_CTX: RefCell<parley::LayoutContext<Color>> =
RefCell::new(parley::LayoutContext::new());
}
@@ -83,16 +66,7 @@ pub fn make_layout(
width: Option<f32>,
underlines: &[Range<usize>],
) -> parley::Layout<Color> {
make_layout2(
text,
text_color,
font_size,
lineheight,
window_scale,
width,
underlines,
&[],
)
make_layout2(text, text_color, font_size, lineheight, window_scale, width, underlines, &[])
}
pub fn make_layout2(
@@ -109,8 +83,7 @@ pub fn make_layout2(
let mut layout_ctx = layout_ctx.borrow_mut();
let mut font_ctx = GLOBAL_FONT_CTX.clone();
let mut builder =
layout_ctx.ranged_builder(&mut font_ctx, text, window_scale, false);
let mut builder = 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(

View File

@@ -184,13 +184,7 @@ impl PrivMessage {
&[],
);
self.cache_txt_layout(
clip,
line_height,
timestamp_width,
nick_colors,
text_color,
);
self.cache_txt_layout(clip, line_height, timestamp_width, nick_colors, text_color);
let mut all_instrs = vec![];
@@ -652,13 +646,7 @@ impl Message {
) {
match self {
Self::Priv(m) => {
m.cache_txt_layout(
clip,
line_height,
timestamp_width,
nick_colors,
text_color,
);
m.cache_txt_layout(clip, line_height, timestamp_width, nick_colors, text_color);
}
Self::Date(_) => {}
Self::File(_) => {}
@@ -859,13 +847,7 @@ impl MessageBuffer {
height += msg_spacing;
}
msg.cache_txt_layout(
&rect,
line_height,
timestamp_width,
&nick_colors,
text_color,
);
msg.cache_txt_layout(&rect, line_height, timestamp_width, &nick_colors, text_color);
height += msg.height(line_height);
}
@@ -924,13 +906,7 @@ impl MessageBuffer {
text,
);
msg.cache_txt_layout(
&rect,
line_height,
timestamp_width,
&nick_colors,
text_color,
);
msg.cache_txt_layout(&rect, line_height, timestamp_width, &nick_colors, text_color);
if self.msgs.is_empty() {
self.msgs.push(msg);
@@ -994,13 +970,7 @@ impl MessageBuffer {
text,
);
msg.cache_txt_layout(
rect,
line_height,
timestamp_width,
&nick_colors,
text_color,
);
msg.cache_txt_layout(rect, line_height, timestamp_width, &nick_colors, text_color);
let msg_height = msg.height(self.line_height.get());
self.msgs.push(msg);

View File

@@ -40,7 +40,7 @@ use tracing::instrument;
use crate::android::textinput::AndroidTextInputState;
use crate::{
gfx::{gfxtag, DrawCall, DrawInstruction, DrawMesh, Point, Rectangle, RenderApi, Vertex},
mesh::MeshBuilder,
mesh::{Color, MeshBuilder},
prop::{
BatchGuardId, BatchGuardPtr, PropertyAtomicGuard, PropertyBool, PropertyColor,
PropertyFloat32, PropertyPtr, PropertyRect, PropertyStr, PropertyUint32, Role,
@@ -501,7 +501,6 @@ impl BaseEdit {
async fn handle_shortcut(
&self,
layout_ctx: &mut parley::LayoutContext<Color>,
key: char,
mods: &KeyMods,
atom: &mut PropertyAtomicGuard,
@@ -518,9 +517,7 @@ impl BaseEdit {
'a' => {
if action_mod {
let mut editor = self.lock_editor().await;
let mut drv = editor.driver(layout_ctx);
drv.select_all();
editor.driver().select_all();
if let Some(seltext) = editor.selected_text() {
self.select_text.clone().set_str(atom, Role::Internal, 0, seltext).unwrap();
}
@@ -553,7 +550,6 @@ impl BaseEdit {
async fn handle_key(
&self,
layout_ctx: &mut parley::LayoutContext<Color>,
key: &KeyCode,
mods: &KeyMods,
atom: &mut PropertyAtomicGuard,
@@ -567,53 +563,52 @@ impl BaseEdit {
t!("handle_key({:?}, {:?}) action_mod={action_mod}", key, mods);
let mut editor = self.lock_editor().await;
let mut drv = editor.driver(layout_ctx);
match key {
KeyCode::Left => {
if action_mod {
if mods.shift {
drv.select_word_left();
editor.driver().select_word_left();
} else {
drv.move_word_left();
editor.driver().move_word_left();
}
} else if mods.shift {
drv.select_left();
editor.driver().select_left();
} else {
drv.move_left();
editor.driver().move_left();
}
}
KeyCode::Right => {
if action_mod {
if mods.shift {
drv.select_word_right();
editor.driver().select_word_right();
} else {
drv.move_word_right();
editor.driver().move_word_right();
}
} else if mods.shift {
drv.select_right();
editor.driver().select_right();
} else {
drv.move_right();
editor.driver().move_right();
}
}
KeyCode::Up => {
if mods.shift {
drv.select_up();
editor.driver().select_up();
} else {
drv.move_up();
editor.driver().move_up();
}
}
KeyCode::Down => {
if mods.shift {
drv.select_down();
editor.driver().select_down();
} else {
drv.move_down();
editor.driver().move_down();
}
}
KeyCode::Enter | KeyCode::KpEnter => {
if mods.shift {
if self.behave.allow_endl() {
drv.insert_or_replace_selection("\n");
editor.driver().insert_or_replace_selection("\n");
editor.on_buffer_changed(atom).await;
}
} else {
@@ -624,44 +619,44 @@ impl BaseEdit {
}
KeyCode::Delete => {
if action_mod {
drv.delete_word();
editor.driver().delete_word();
} else {
drv.delete();
editor.driver().delete();
}
editor.on_buffer_changed(atom).await;
}
KeyCode::Backspace => {
if action_mod {
drv.backdelete_word();
editor.driver().backdelete_word();
} else {
drv.backdelete();
editor.driver().backdelete();
}
editor.on_buffer_changed(atom).await;
}
KeyCode::Home => {
if action_mod {
if mods.shift {
drv.select_to_text_start();
editor.driver().select_to_text_start();
} else {
drv.move_to_text_start();
editor.driver().move_to_text_start();
}
} else if mods.shift {
drv.select_to_line_start();
editor.driver().select_to_line_start();
} else {
drv.move_to_line_start();
editor.driver().move_to_line_start();
}
}
KeyCode::End => {
if action_mod {
if mods.shift {
drv.select_to_text_end();
editor.driver().select_to_text_end();
} else {
drv.move_to_text_end();
editor.driver().move_to_text_end();
}
} else if mods.shift {
drv.select_to_line_end();
editor.driver().select_to_line_end();
} else {
drv.move_to_line_end();
editor.driver().move_to_line_end();
}
}
_ => return false,
@@ -674,7 +669,6 @@ impl BaseEdit {
}
drop(editor);
drop(txt_ctx);
self.eval_rect().await;
self.behave.apply_cursor_scroll().await;
@@ -1594,10 +1588,7 @@ impl UIObject for BaseEdit {
if repeat {
return false
}
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
})
return self.handle_shortcut(key, &mods, atom).await
}
// Do nothing
@@ -1640,10 +1631,7 @@ impl UIObject for BaseEdit {
let mut is_handled = false;
for _ in 0..actions {
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 = self.handle_key(&key, &mods, atom).await;
}
is_handled
}
@@ -1684,12 +1672,10 @@ impl UIObject for BaseEdit {
// Move mouse pos within this widget
self.abs_to_local(&mut mouse_pos);
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 layout_ctx);
drv.move_to_point(mouse_pos.x, mouse_pos.y);
});
editor.driver().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

@@ -123,7 +123,8 @@ impl Text {
text
};
let layout = text::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() {