/* This file is part of DarkFi (https://dark.fi) * * Copyright (C) 2020-2025 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 . */ use crate::{ android, gfx::Point, mesh::Color, prop::{PropertyColor, PropertyFloat32}, text2::{TextContext, TEXT_CTX}, }; macro_rules! t { ($($arg:tt)*) => { trace!(target: "text::editor::android", $($arg)*); } } // You must be careful working with string indexes in Java. They are UTF16 string indexs, not UTF8 fn char16_to_byte_index(s: &str, char_idx: usize) -> Option { let utf16_data: Vec<_> = s.encode_utf16().take(char_idx).collect(); let prestr = String::from_utf16(&utf16_data).ok()?; Some(prestr.len()) } fn byte_to_char16_index(s: &str, byte_idx: usize) -> Option { if byte_idx > s.len() || !s.is_char_boundary(byte_idx) { return None } Some(s[..byte_idx].encode_utf16().count()) } pub struct Editor { pub composer_id: usize, is_init: bool, layout: parley::Layout, width: Option, font_size: PropertyFloat32, text_color: PropertyColor, window_scale: PropertyFloat32, lineheight: PropertyFloat32, } impl Editor { pub async fn new( font_size: PropertyFloat32, text_color: PropertyColor, window_scale: PropertyFloat32, lineheight: PropertyFloat32, ) -> Self { Self { composer_id: usize::MAX, is_init: false, layout: Default::default(), width: None, font_size, text_color, window_scale, lineheight, } } pub fn init(&mut self) { android::focus(self.composer_id).unwrap(); } pub fn setup(&mut self) { assert!(self.composer_id != usize::MAX); t!("Initialized composer [{}]", self.composer_id); let atxt = "A berry is small 😊 and pulpy."; android::set_text(self.composer_id, atxt).unwrap(); self.is_init = true; } pub async fn refresh(&mut self) { let font_size = self.font_size.get(); let text_color = self.text_color.get(); let window_scale = self.window_scale.get(); let lineheight = self.lineheight.get(); let edit = android::get_editable(self.composer_id).unwrap(); t!("refesh buffer = {}", edit.buffer); let mut txt_ctx = TEXT_CTX.get().await; self.layout = txt_ctx.make_layout( &edit.buffer, text_color, font_size, lineheight, window_scale, self.width, ); } pub fn layout(&self) -> &parley::Layout { &self.layout } pub fn move_to_pos(&self, pos: Point) { let cursor = parley::Cursor::from_point(&self.layout, pos.x, pos.y); let edit = android::get_editable(self.composer_id).unwrap(); let cursor_idx = cursor.index(); let pos = byte_to_char16_index(&edit.buffer, cursor_idx).unwrap(); android::set_selection(self.composer_id, pos, pos); t!(" {cursor_idx} => {pos}"); } pub fn get_cursor_pos(&self) -> Option { if !self.is_init { return None } let lineheight = self.lineheight.get(); let edit = android::get_editable(self.composer_id).unwrap(); let cursor_byte_idx = char16_to_byte_index(&edit.buffer, edit.select_start).unwrap(); let cursor = if cursor_byte_idx >= edit.buffer.len() { parley::Cursor::from_byte_index( &self.layout, edit.buffer.len(), parley::Affinity::Upstream, ) } else { parley::Cursor::from_byte_index( &self.layout, cursor_byte_idx, parley::Affinity::Downstream, ) }; let cursor_rect = cursor.geometry(&self.layout, lineheight); let cursor_pos = Point::new(cursor_rect.x0 as f32, cursor_rect.y0 as f32); Some(cursor_pos) } pub async fn driver<'a>( &'a mut self, txt_ctx: &'a mut TextContext, ) -> Option> { None } pub fn set_width(&mut self, w: f32) { self.width = Some(w); } pub fn height(&self) -> f32 { self.layout().height() } }