app: migrate emoji_picker to the new text API

This commit is contained in:
jkds
2026-01-05 17:38:47 +01:00
parent 0a8be750d3
commit cc53053ef2
3 changed files with 78 additions and 68 deletions

View File

@@ -48,7 +48,7 @@ mod android_ui_consts {
pub const NETSTATUS_ICON_SIZE: f32 = 140.;
pub const SETTINGS_ICON_SIZE: f32 = 140.;
pub const NETLOGO_SCALE: f32 = 50.;
pub const EMOJI_PICKER_ICON_SIZE: f32 = 100.;
pub const EMOJI_PICKER_ICON_SIZE: f32 = 120.;
}
#[cfg(target_os = "android")]
@@ -114,7 +114,7 @@ mod ui_consts {
pub const NETSTATUS_ICON_SIZE: f32 = 60.;
pub const SETTINGS_ICON_SIZE: f32 = 60.;
pub const NETLOGO_SCALE: f32 = 25.;
pub const EMOJI_PICKER_ICON_SIZE: f32 = 40.;
pub const EMOJI_PICKER_ICON_SIZE: f32 = 50.;
pub use super::desktop_paths::*;
}
@@ -502,18 +502,14 @@ pub async fn make(app: &App, window: SceneNodePtr, i18n_fish: &I18nBabelFish) {
settingslayer_node.link(node);
*/
let emoji_meshes = emoji_picker::EmojiMeshes::new(
app.render_api.clone(),
app.text_shaper.clone(),
EMOJI_PICKER_ICON_SIZE,
);
let emoji_meshes =
emoji_picker::EmojiMeshes::new(app.render_api.clone(), EMOJI_PICKER_ICON_SIZE);
let emoji_meshes2 = emoji_meshes.clone();
spawn_thread("load-emojis", move || {
app.ex.spawn(async move {
for i in (0..500).step_by(20) {
let mut emoji = emoji_meshes2.lock();
for j in i..(i + 20) {
emoji.get(j);
emoji_meshes2.lock().await.get(j).await;
}
}
});

View File

@@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use parking_lot::Mutex as SyncMutex;
use async_lock::Mutex as AsyncMutex;
use std::{
fs::File,
io::{BufRead, BufReader},
@@ -25,9 +25,9 @@ use std::{
};
use crate::{
gfx::{gfxtag, DrawMesh, Rectangle, RenderApi},
gfx::{gfxtag, DrawInstruction, DrawMesh, Rectangle, RenderApi},
mesh::{MeshBuilder, COLOR_WHITE},
text::{self, TextShaperPtr},
text2,
};
use super::default;
@@ -42,25 +42,19 @@ pub fn get_emoji_list_path() -> PathBuf {
dirs::data_local_dir().unwrap().join("darkfi/emoji.txt")
}
pub type EmojiMeshesPtr = Arc<SyncMutex<EmojiMeshes>>;
pub type EmojiMeshesPtr = Arc<AsyncMutex<EmojiMeshes>>;
pub struct EmojiMeshes {
render_api: RenderApi,
text_shaper: TextShaperPtr,
emoji_size: f32,
emoji_list: LazyLock<Vec<String>>,
meshes: Vec<DrawMesh>,
}
impl EmojiMeshes {
pub fn new(
render_api: RenderApi,
text_shaper: TextShaperPtr,
emoji_size: f32,
) -> EmojiMeshesPtr {
Arc::new(SyncMutex::new(Self {
pub fn new(render_api: RenderApi, emoji_size: f32) -> EmojiMeshesPtr {
Arc::new(AsyncMutex::new(Self {
render_api,
text_shaper,
emoji_size,
emoji_list: LazyLock::new(load_emoji_list),
meshes: vec![],
@@ -71,7 +65,7 @@ impl EmojiMeshes {
self.meshes.clear();
}
pub fn get(&mut self, i: usize) -> DrawMesh {
pub async fn get(&mut self, i: usize) -> DrawMesh {
let emoji_list = self.get_list();
assert!(i < emoji_list.len());
self.meshes.reserve_exact(emoji_list.len());
@@ -80,7 +74,7 @@ impl EmojiMeshes {
//d!("EmojiMeshes loading new glyphs");
for j in self.meshes.len()..=i {
let emoji = &self.emoji_list[j];
let mesh = self.gen_emoji_mesh(emoji);
let mesh = self.gen_emoji_mesh(emoji).await;
self.meshes.push(mesh);
}
}
@@ -89,26 +83,41 @@ impl EmojiMeshes {
}
/// Make mesh for this emoji centered at (0, 0)
fn gen_emoji_mesh(&self, emoji: &str) -> DrawMesh {
async fn gen_emoji_mesh(&self, emoji: &str) -> DrawMesh {
//d!("rendering emoji: '{emoji}'");
let mut txt_ctx = text2::TEXT_CTX.get().await;
// The params here don't actually matter since we're talking about BMP fixed sizes
let glyphs = self.text_shaper.shape(emoji.to_string(), 10., 1.);
assert_eq!(glyphs.len(), 1);
let atlas = text::make_texture_atlas(&self.render_api, gfxtag!("emoji_mesh"), &glyphs);
let glyph = glyphs.into_iter().next().unwrap();
let layout = txt_ctx.make_layout(emoji, COLOR_WHITE, self.emoji_size, 1., 1., None, &[]);
drop(txt_ctx);
let instrs = text2::render_layout(&layout, &self.render_api, gfxtag!("emoji_mesh"));
// Extract the mesh from the draw instructions
// For a single emoji, we should get exactly one Draw instruction with a mesh
let mesh = match instrs.first() {
Some(DrawInstruction::Draw(mesh)) => mesh.clone(),
_ => panic!("Expected Draw instruction for emoji"),
};
// Emoji's vary in size. We make them all a consistent size.
// We need to scale the mesh to match emoji_size.
// TODO: Implement proper scaling for text2 API
/*
let bbox = layout.metrics().bounds;
let orig_w = bbox.width().max(1.0);
let orig_h = bbox.height().max(1.0);
let w = self.emoji_size;
let h =
(glyph.sprite.bmp_height as f32) * self.emoji_size / (glyph.sprite.bmp_width as f32);
// Center at origin
let h = self.emoji_size * orig_h / orig_w;
// Create a new mesh with the scaled size centered at origin
let x = -w / 2.;
let y = -h / 2.;
let mut mesh_builder = MeshBuilder::new(gfxtag!("emoji_mesh"));
*/
let uv = atlas.fetch_uv(glyph.glyph_id).expect("missing glyph UV rect");
let mut mesh = MeshBuilder::new(gfxtag!("emoji_mesh"));
mesh.draw_box(&Rectangle::new(x, y, w, h), COLOR_WHITE, &uv);
mesh.alloc(&self.render_api).draw_with_textures(vec![atlas.texture])
// For now, just return the original mesh since scaling is complex with textures
mesh
}
pub fn get_list<'a>(&'a self) -> &'a Vec<String> {

View File

@@ -44,6 +44,7 @@ pub use emoji::{EmojiMeshes, EmojiMeshesPtr};
macro_rules! d { ($($arg:tt)*) => { debug!(target: "ui::emoji_picker", $($arg)*) } }
macro_rules! t { ($($arg:tt)*) => { trace!(target: "ui::emoji_picker", $($arg)*) } }
#[derive(Clone)]
struct TouchInfo {
start_pos: Point,
start_scroll: f32,
@@ -124,8 +125,8 @@ impl EmojiPicker {
off_x
}
fn max_scroll(&self) -> f32 {
let emojis_len = self.emoji_meshes.lock().get_list().len() as f32;
async fn max_scroll(&self) -> f32 {
let emojis_len = self.emoji_meshes.lock().await.get_list().len() as f32;
let emoji_size = self.emoji_size.get();
let cols = self.emojis_per_line();
let rows = (emojis_len / cols).ceil();
@@ -159,7 +160,7 @@ impl EmojiPicker {
//d!(" = {idx}, emoji_len = {}", emoji::EMOJI_LIST.len());
let emoji_selected = {
let emoji_meshes = self.emoji_meshes.lock();
let emoji_meshes = self.emoji_meshes.lock().await;
let emoji_list = emoji_meshes.get_list();
if idx < emoji_list.len() {
@@ -182,17 +183,17 @@ impl EmojiPicker {
}
#[instrument(target = "ui::emoji_picker")]
fn redraw(&self, atom: &mut PropertyAtomicGuard) {
async fn redraw(&self, atom: &mut PropertyAtomicGuard) {
let Some(parent_rect) = self.parent_rect.lock().clone() else { return };
let Some(draw_update) = self.get_draw_calls(parent_rect, atom) else {
let Some(draw_update) = self.get_draw_calls(parent_rect, atom).await else {
error!(target: "ui:emoji_picker", "Emoji picker failed to draw");
return
};
self.render_api.replace_draw_calls(atom.batch_id, draw_update.draw_calls);
}
fn get_draw_calls(
async fn get_draw_calls(
&self,
parent_rect: Rectangle,
atom: &mut PropertyAtomicGuard,
@@ -203,7 +204,7 @@ impl EmojiPicker {
}
// Clamp scroll if needed due to window size change
let max_scroll = self.max_scroll();
let max_scroll = self.max_scroll().await;
if self.scroll.get() > max_scroll {
self.scroll.set(atom, max_scroll);
}
@@ -214,14 +215,16 @@ impl EmojiPicker {
let off_x = self.calc_off_x();
let emoji_size = self.emoji_size.get();
let mut emoji_meshes = self.emoji_meshes.lock();
let emoji_list_len = emoji_meshes.get_list().len();
let emoji_list_len = {
let emoji_meshes = self.emoji_meshes.lock().await;
emoji_meshes.get_list().len()
};
let mut x = emoji_size / 2.;
let mut y = emoji_size / 2. - self.scroll.get();
for i in 0..emoji_list_len {
let pos = Point::new(x, y);
let mesh = emoji_meshes.get(i);
let mesh = self.emoji_meshes.lock().await.get(i).await;
instrs.extend_from_slice(&[DrawInstruction::SetPos(pos), DrawInstruction::Draw(mesh)]);
x += off_x;
@@ -257,7 +260,7 @@ impl UIObject for EmojiPicker {
async fn redraw(self_: Arc<EmojiPicker>, batch: BatchGuardPtr) {
let atom = &mut batch.spawn();
self_.redraw(atom);
self_.redraw(atom).await;
}
let mut on_modify = OnModify::new(ex, self.node.clone(), me.clone());
@@ -269,7 +272,8 @@ impl UIObject for EmojiPicker {
fn stop(&self) {
self.tasks.lock().clear();
self.emoji_meshes.lock().clear();
// TODO: Figure out how to call async clear from sync context
// self.emoji_meshes.lock().await.clear();
}
#[instrument(target = "ui::emoji_picker")]
@@ -279,7 +283,7 @@ impl UIObject for EmojiPicker {
atom: &mut PropertyAtomicGuard,
) -> Option<DrawUpdate> {
*self.parent_rect.lock() = Some(parent_rect);
self.get_draw_calls(parent_rect, atom)
self.get_draw_calls(parent_rect, atom).await
}
async fn handle_mouse_move(&self, mouse_pos: Point) -> bool {
@@ -297,10 +301,10 @@ impl UIObject for EmojiPicker {
let mut scroll = self.scroll.get();
scroll -= self.mouse_scroll_speed.get() * wheel_pos.y;
scroll = scroll.clamp(0., self.max_scroll());
scroll = scroll.clamp(0., self.max_scroll().await);
self.scroll.set(atom, scroll);
self.redraw(atom);
self.redraw(atom).await;
true
}
@@ -332,9 +336,9 @@ impl UIObject for EmojiPicker {
// todo: clean this up
let mut emoji_is_clicked = false;
{
let mut touch_info = self.touch_info.lock();
match phase {
TouchPhase::Started => {
let mut touch_info = self.touch_info.lock();
if !rect.contains(touch_pos) {
return false
}
@@ -346,31 +350,32 @@ impl UIObject for EmojiPicker {
});
}
TouchPhase::Moved => {
if let Some(touch_info) = touch_info.as_mut() {
let (touch_info, y_diff) = {
let mut touch_info = self.touch_info.lock();
let Some(touch_info) = touch_info.as_mut() else {
return false;
};
let y_diff = touch_info.start_pos.y - pos.y;
if y_diff.abs() > 0.5 {
touch_info.is_scroll = true;
}
(touch_info.clone(), y_diff)
};
if touch_info.is_scroll {
let mut scroll = touch_info.start_scroll + y_diff;
scroll = scroll.clamp(0., self.max_scroll());
self.scroll.set(atom, scroll);
self.redraw(atom);
}
} else {
return false
if touch_info.is_scroll {
let mut scroll = touch_info.start_scroll + y_diff;
scroll = scroll.clamp(0., self.max_scroll().await);
self.scroll.set(atom, scroll);
self.redraw(atom).await;
}
}
TouchPhase::Ended | TouchPhase::Cancelled => {
if let Some(touch_info) = &*touch_info {
if !touch_info.is_scroll {
emoji_is_clicked = true;
}
} else {
return false
let touch_info = std::mem::take(&mut *self.touch_info.lock());
let Some(touch_info) = touch_info else { return false };
if !touch_info.is_scroll {
emoji_is_clicked = true;
}
*touch_info = None;
}
}
}