diff --git a/bin/app/src/text/editor/android.rs b/bin/app/src/text/editor/android.rs index 271c22093..71d98bb3c 100644 --- a/bin/app/src/text/editor/android.rs +++ b/bin/app/src/text/editor/android.rs @@ -16,6 +16,9 @@ * along with this program. If not, see . */ +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, - ) -> Option> { - None + pub fn driver(&mut self) -> ParleyDriverWrapper { + ParleyDriverWrapper::new(&mut self.layout) } pub fn set_width(&mut self, w: f32) { diff --git a/bin/app/src/text/editor/driver.rs b/bin/app/src/text/editor/driver.rs new file mode 100644 index 000000000..7a1995095 --- /dev/null +++ b/bin/app/src/text/editor/driver.rs @@ -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 . + */ + +#[cfg(not(target_os = "android"))] +use crate::{mesh::Color, text}; + +pub struct ParleyDriverWrapper<'a> { + #[cfg(not(target_os = "android"))] + editor: &'a mut parley::PlainEditor, +} + +impl<'a> ParleyDriverWrapper<'a> { + #[cfg(not(target_os = "android"))] + pub fn new(editor: &'a mut parley::PlainEditor) -> Self { + Self { editor } + } + + #[cfg(target_os = "android")] + pub fn new(_layout: &mut parley::Layout) -> Self { + Self {} + } + + #[cfg(not(target_os = "android"))] + fn with_driver(&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!() + } +} diff --git a/bin/app/src/text/editor/mod.rs b/bin/app/src/text/editor/mod.rs index 4399a6cff..4618d6bc2 100644 --- a/bin/app/src/text/editor/mod.rs +++ b/bin/app/src/text/editor/mod.rs @@ -18,6 +18,7 @@ #[cfg(target_os = "android")] mod android; +mod driver; #[cfg(target_os = "android")] pub use android::Editor; diff --git a/bin/app/src/text/editor/parley.rs b/bin/app/src/text/editor/parley.rs index 50aa69bde..7e7bdbf98 100644 --- a/bin/app/src/text/editor/parley.rs +++ b/bin/app/src/text/editor/parley.rs @@ -23,6 +23,8 @@ use crate::{ text::{self, FONT_STACK}, }; +use super::driver::ParleyDriverWrapper; + pub struct Editor { editor: parley::PlainEditor, @@ -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, - ) -> Option> { - 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)] diff --git a/bin/app/src/text/mod.rs b/bin/app/src/text/mod.rs index bd2b79aa6..1bf57e748 100644 --- a/bin/app/src/text/mod.rs +++ b/bin/app/src/text/mod.rs @@ -16,14 +16,8 @@ * along with this program. If not, see . */ -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>) -pub static GLOBAL_FONT_CTX: Lazy = Lazy::new(|| { - let collection = Collection::new(CollectionOptions { - shared: true, - system_fonts: false, - }); +pub static GLOBAL_FONT_CTX: LazyLock = 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> = + pub static THREAD_LAYOUT_CTX: RefCell> = RefCell::new(parley::LayoutContext::new()); } @@ -83,16 +66,7 @@ pub fn make_layout( width: Option, underlines: &[Range], ) -> parley::Layout { - 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( diff --git a/bin/app/src/ui/chatview/page.rs b/bin/app/src/ui/chatview/page.rs index dc2299317..2d3c16191 100644 --- a/bin/app/src/ui/chatview/page.rs +++ b/bin/app/src/ui/chatview/page.rs @@ -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); diff --git a/bin/app/src/ui/edit/mod.rs b/bin/app/src/ui/edit/mod.rs index 22dddef0e..d1d449980 100644 --- a/bin/app/src/ui/edit/mod.rs +++ b/bin/app/src/ui/edit/mod.rs @@ -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, 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, 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(); diff --git a/bin/app/src/ui/text.rs b/bin/app/src/ui/text.rs index 1640a7fdd..0681d5891 100644 --- a/bin/app/src/ui/text.rs +++ b/bin/app/src/ui/text.rs @@ -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() {