android.rs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2025 Dyne.org foundation
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU Affero General Public License as
  7. * published by the Free Software Foundation, either version 3 of the
  8. * License, or (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU Affero General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Affero General Public License
  16. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. */
  18. use crate::{
  19. android,
  20. gfx::Point,
  21. mesh::Color,
  22. prop::{PropertyAtomicGuard, PropertyColor, PropertyFloat32, PropertyStr},
  23. text2::{TextContext, TEXT_CTX},
  24. AndroidSuggestEvent,
  25. };
  26. use std::sync::{
  27. atomic::{AtomicBool, Ordering},
  28. Arc,
  29. };
  30. macro_rules! t { ($($arg:tt)*) => { trace!(target: "text::editor::android", $($arg)*); } }
  31. macro_rules! w { ($($arg:tt)*) => { warn!(target: "text::editor::android", $($arg)*) } }
  32. // You must be careful working with string indexes in Java. They are UTF16 string indexs, not UTF8
  33. fn char16_to_byte_index(s: &str, char_idx: usize) -> Option<usize> {
  34. let utf16_data: Vec<_> = s.encode_utf16().take(char_idx).collect();
  35. let prestr = String::from_utf16(&utf16_data).ok()?;
  36. Some(prestr.len())
  37. }
  38. fn byte_to_char16_index(s: &str, byte_idx: usize) -> Option<usize> {
  39. if byte_idx > s.len() || !s.is_char_boundary(byte_idx) {
  40. return None
  41. }
  42. Some(s[..byte_idx].encode_utf16().count())
  43. }
  44. pub struct Editor {
  45. pub composer_id: usize,
  46. pub recvr: async_channel::Receiver<AndroidSuggestEvent>,
  47. is_init: bool,
  48. is_setup: bool,
  49. /// We cannot receive focus until `AndroidSuggestEvent::Init` has finished.
  50. /// We use this flag to delay calling `android::focus()` until the init has completed.
  51. is_focus_req: AtomicBool,
  52. layout: parley::Layout<Color>,
  53. width: Option<f32>,
  54. text: PropertyStr,
  55. font_size: PropertyFloat32,
  56. text_color: PropertyColor,
  57. window_scale: PropertyFloat32,
  58. lineheight: PropertyFloat32,
  59. }
  60. impl Editor {
  61. pub fn new(
  62. text: PropertyStr,
  63. font_size: PropertyFloat32,
  64. text_color: PropertyColor,
  65. window_scale: PropertyFloat32,
  66. lineheight: PropertyFloat32,
  67. ) -> Self {
  68. let (sender, recvr) = async_channel::unbounded();
  69. let composer_id = android::create_composer(sender);
  70. t!("Created composer [{composer_id}]");
  71. Self {
  72. composer_id,
  73. recvr,
  74. is_init: false,
  75. is_setup: false,
  76. is_focus_req: AtomicBool::new(false),
  77. layout: Default::default(),
  78. width: None,
  79. text,
  80. font_size,
  81. text_color,
  82. window_scale,
  83. lineheight,
  84. }
  85. }
  86. /// Called on `AndroidSuggestEvent::Init` after the View has been added to the main hierarchy
  87. /// and is ready to receive commands such as focus.
  88. pub fn init(&mut self) {
  89. self.is_init = true;
  90. // Perform any focus requests.
  91. let is_focus_req = self.is_focus_req.swap(false, Ordering::SeqCst);
  92. if is_focus_req {
  93. android::focus(self.composer_id).unwrap();
  94. }
  95. //android::focus(self.composer_id).unwrap();
  96. //let atxt = "A berry is small 😊 and pulpy.";
  97. //let atxt = "A berry is a small, pulpy, and often edible fruit. Typically, berries are juicy, rounded, brightly colored, sweet, sour or tart, and do not have a stone or pit, although many pips or seeds may be present. Common examples of berries in the culinary sense are strawberries, raspberries, blueberries, blackberries, white currants, blackcurrants, and redcurrants. In Britain, soft fruit is a horticultural term for such fruits. The common usage of the term berry is different from the scientific or botanical definition of a berry, which refers to a fruit produced from the ovary of a single flower where the outer layer of the ovary wall develops into an edible fleshy portion (pericarp). The botanical definition includes many fruits that are not commonly known or referred to as berries, such as grapes, tomatoes, cucumbers, eggplants, bananas, and chili peppers.";
  98. //android::set_text(self.composer_id, atxt);
  99. //self.set_selection(2, 7);
  100. //self.refresh(&mut PropertyAtomicGuard::new());
  101. }
  102. /// Called on `AndroidSuggestEvent::CreateInputConnect`, which only happens after the View
  103. /// is focused for the first time.
  104. pub fn setup(&mut self) {
  105. assert!(self.is_init);
  106. self.is_setup = true;
  107. assert!(self.composer_id != usize::MAX);
  108. t!("Initialized composer [{}]", self.composer_id);
  109. }
  110. pub async fn on_text_changed(&mut self) {
  111. // Get modified text property
  112. let txt = self.text.get();
  113. // Update Android text buffer
  114. android::set_text(self.composer_id, &txt);
  115. assert_eq!(android::get_editable(self.composer_id).unwrap().buffer, txt);
  116. // Refresh our layout
  117. self.refresh().await;
  118. }
  119. pub async fn on_buffer_changed(&mut self) {
  120. // Refresh the layout using the Android buffer
  121. self.refresh().await;
  122. // Now update the text attribute
  123. let edit = android::get_editable(self.composer_id).unwrap();
  124. self.text.set(&mut PropertyAtomicGuard::new(), &edit.buffer);
  125. }
  126. /// Can only be called after AndroidSuggestEvent::Init.
  127. pub fn focus(&self) {
  128. // We're not yet ready to receive focus
  129. if !self.is_init {
  130. self.is_focus_req.store(true, Ordering::SeqCst);
  131. return
  132. }
  133. android::focus(self.composer_id).unwrap();
  134. }
  135. pub fn unfocus(&self) {
  136. android::unfocus(self.composer_id).unwrap();
  137. }
  138. pub async fn refresh(&mut self) {
  139. let font_size = self.font_size.get();
  140. let text_color = self.text_color.get();
  141. let window_scale = self.window_scale.get();
  142. let lineheight = self.lineheight.get();
  143. let edit = android::get_editable(self.composer_id).unwrap();
  144. let mut underlines = vec![];
  145. if let Some(compose_start) = edit.compose_start {
  146. let compose_end = edit.compose_end.unwrap();
  147. let compose_start = char16_to_byte_index(&edit.buffer, compose_start).unwrap();
  148. let compose_end = char16_to_byte_index(&edit.buffer, compose_end).unwrap();
  149. underlines.push((compose_start..compose_end));
  150. }
  151. let mut txt_ctx = TEXT_CTX.get().await;
  152. self.layout = txt_ctx.make_layout(
  153. &edit.buffer,
  154. text_color,
  155. font_size,
  156. lineheight,
  157. window_scale,
  158. self.width,
  159. &underlines,
  160. );
  161. }
  162. pub fn layout(&self) -> &parley::Layout<Color> {
  163. &self.layout
  164. }
  165. pub fn move_to_pos(&self, pos: Point) {
  166. let cursor = parley::Cursor::from_point(&self.layout, pos.x, pos.y);
  167. let edit = android::get_editable(self.composer_id).unwrap();
  168. let cursor_idx = cursor.index();
  169. let pos = byte_to_char16_index(&edit.buffer, cursor_idx).unwrap();
  170. t!(" {cursor_idx} => {pos}");
  171. android::set_selection(self.composer_id, pos, pos);
  172. }
  173. pub fn select_word_at_point(&self, pos: Point) {
  174. let select = parley::Selection::word_from_point(&self.layout, pos.x, pos.y);
  175. assert!(!select.is_collapsed());
  176. let select = select.text_range();
  177. self.set_selection(select.start, select.end);
  178. }
  179. pub fn get_cursor_pos(&self) -> Point {
  180. let lineheight = self.lineheight.get();
  181. let edit = android::get_editable(self.composer_id).unwrap();
  182. let cursor_byte_idx = char16_to_byte_index(&edit.buffer, edit.select_start).unwrap();
  183. let cursor = if cursor_byte_idx >= edit.buffer.len() {
  184. parley::Cursor::from_byte_index(
  185. &self.layout,
  186. edit.buffer.len(),
  187. parley::Affinity::Upstream,
  188. )
  189. } else {
  190. parley::Cursor::from_byte_index(
  191. &self.layout,
  192. cursor_byte_idx,
  193. parley::Affinity::Downstream,
  194. )
  195. };
  196. let cursor_rect = cursor.geometry(&self.layout, lineheight);
  197. Point::new(cursor_rect.x0 as f32, cursor_rect.y0 as f32)
  198. }
  199. pub async fn driver<'a>(
  200. &'a mut self,
  201. txt_ctx: &'a mut TextContext,
  202. ) -> Option<parley::PlainEditorDriver<'a, Color>> {
  203. None
  204. }
  205. pub fn set_width(&mut self, w: f32) {
  206. self.width = Some(w);
  207. }
  208. pub fn height(&self) -> f32 {
  209. self.layout().height()
  210. }
  211. pub fn selected_text(&self) -> Option<String> {
  212. let edit = android::get_editable(self.composer_id).unwrap();
  213. if edit.select_start == edit.select_end {
  214. return None
  215. }
  216. let select_start = char16_to_byte_index(&edit.buffer, edit.select_start).unwrap();
  217. let select_end = char16_to_byte_index(&edit.buffer, edit.select_end).unwrap();
  218. Some(edit.buffer[select_start..select_end].to_string())
  219. }
  220. pub fn selection(&self) -> parley::Selection {
  221. let edit = android::get_editable(self.composer_id).unwrap();
  222. let select_start = char16_to_byte_index(&edit.buffer, edit.select_start).unwrap();
  223. let select_end = char16_to_byte_index(&edit.buffer, edit.select_end).unwrap();
  224. //t!("selection() -> ({select_start}, {select_end})");
  225. let anchor = parley::Cursor::from_byte_index(
  226. &self.layout,
  227. select_start,
  228. parley::Affinity::Downstream,
  229. );
  230. let focus =
  231. parley::Cursor::from_byte_index(&self.layout, select_end, parley::Affinity::Downstream);
  232. parley::Selection::new(anchor, focus)
  233. }
  234. pub fn set_selection(&self, select_start: usize, select_end: usize) {
  235. t!("set_selection({select_start}, {select_end})");
  236. let edit = android::get_editable(self.composer_id).unwrap();
  237. let select_start = byte_to_char16_index(&edit.buffer, select_start).unwrap();
  238. let select_end = byte_to_char16_index(&edit.buffer, select_end).unwrap();
  239. android::set_selection(self.composer_id, select_start, select_end);
  240. }
  241. pub fn buffer(&self) -> String {
  242. let edit = android::get_editable(self.composer_id).unwrap();
  243. edit.buffer
  244. }
  245. }