editable.rs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  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. prop::{
  20. PropertyBool, PropertyColor, PropertyFloat32, PropertyPtr, PropertyRect, PropertyStr,
  21. PropertyUint32, Role,
  22. },
  23. text::{self, Glyph, GlyphPositionIter, TextShaperPtr},
  24. util::enumerate_ref,
  25. };
  26. pub type TextPos = usize;
  27. pub type TextIdx = usize;
  28. /// Android composing text from autosuggest.
  29. /// We need this because IMEs can arbitrary set a composing region after
  30. /// the text has been committed.
  31. #[derive(Clone)]
  32. struct ComposingText {
  33. /// Text that is being composed
  34. compose_text: String,
  35. /// Text that has been committed
  36. commit_text: String,
  37. region_start: usize,
  38. region_end: usize,
  39. }
  40. impl ComposingText {
  41. fn new() -> Self {
  42. Self {
  43. compose_text: String::new(),
  44. commit_text: String::new(),
  45. region_start: 0,
  46. region_end: 0,
  47. }
  48. }
  49. fn clear(&mut self) -> String {
  50. self.region_start = 0;
  51. self.region_end = 0;
  52. let final_text =
  53. std::mem::take(&mut self.commit_text) + &std::mem::take(&mut self.compose_text);
  54. final_text
  55. }
  56. /// Set composing text.
  57. fn compose(&mut self, text: String) {
  58. self.compose_text = text;
  59. self.region_start = self.commit_text.len();
  60. self.region_end = self.region_start + self.compose_text.len();
  61. }
  62. /// Commit the composing text.
  63. fn commit(&mut self) {
  64. self.commit_text += &self.compose_text;
  65. self.compose_text.clear();
  66. self.region_start = self.commit_text.len();
  67. self.region_end = self.commit_text.len();
  68. }
  69. /// Override the composing region for display.
  70. /// Anyone who looks closely at this impl might thing it's wrong that subsequent
  71. /// calls to compose() will ignore what's set here, but indeed this is how Android behaves.
  72. fn set_compose_region(&mut self, start: usize, end: usize) {
  73. assert!(start <= end);
  74. assert!(end <= self.commit_text.len() + self.compose_text.len());
  75. self.region_start = start;
  76. self.region_end = end;
  77. }
  78. }
  79. #[derive(Clone)]
  80. pub struct RenderedEditable {
  81. pub glyphs: Vec<Glyph>,
  82. pub under_start: TextPos,
  83. pub under_end: TextPos,
  84. }
  85. impl RenderedEditable {
  86. fn new(glyphs: Vec<Glyph>, under_start: TextPos, under_end: TextPos) -> Self {
  87. let mut self_ = Self { glyphs, under_start: 0, under_end: 0 };
  88. self_.under_start = self_.idx_to_pos(under_start);
  89. self_.under_end = self_.idx_to_pos(under_end);
  90. self_
  91. }
  92. /// Which glyph contains the char at idx?
  93. fn idx_to_pos(&self, idx: TextIdx) -> TextPos {
  94. let mut total = 0;
  95. for (i, glyph) in enumerate_ref(&self.glyphs) {
  96. total += glyph.substr.len();
  97. if idx < total {
  98. return i
  99. }
  100. }
  101. return self.glyphs.len()
  102. }
  103. /// Converts glyph pos to idx in the string
  104. pub fn pos_to_idx(&self, pos: TextPos) -> TextIdx {
  105. let mut idx = 0;
  106. for (i, glyph) in enumerate_ref(&self.glyphs) {
  107. if i == pos {
  108. return idx
  109. }
  110. idx += glyph.substr.len();
  111. }
  112. return idx
  113. }
  114. pub fn has_underline(&self) -> bool {
  115. self.under_start != self.under_end
  116. }
  117. /// Converts x offset to glyph pos. Will round to the closest side.
  118. pub fn x_to_pos(&self, x: f32, font_size: f32, window_scale: f32, baseline: f32) -> TextPos {
  119. debug!(target: "ui::editbox", "x_to_pos({x})");
  120. let glyph_pos_iter =
  121. GlyphPositionIter::new(font_size, window_scale, &self.glyphs, baseline);
  122. for (glyph_idx, glyph_rect) in glyph_pos_iter.enumerate() {
  123. if x >= glyph_rect.rhs() {
  124. continue
  125. }
  126. let midpoint = glyph_rect.x + glyph_rect.w / 2.;
  127. if x < midpoint {
  128. return glyph_idx
  129. }
  130. assert!(x >= midpoint);
  131. return glyph_idx + 1
  132. }
  133. // Everything to the right is at the end
  134. self.glyphs.len()
  135. }
  136. pub fn pos_to_xw(
  137. &self,
  138. pos: TextPos,
  139. font_size: f32,
  140. window_scale: f32,
  141. baseline: f32,
  142. ) -> (f32, f32) {
  143. debug!(target: "ui::editbox", "pos_to_xw({pos}) [glyphs_len={}]", self.glyphs.len());
  144. let mut glyph_pos_iter =
  145. GlyphPositionIter::new(font_size, window_scale, &self.glyphs, baseline);
  146. let mut end = 0.;
  147. for (glyph_idx, glyph_rect) in glyph_pos_iter.enumerate() {
  148. if glyph_idx == pos {
  149. return (glyph_rect.x, glyph_rect.w)
  150. }
  151. end = glyph_rect.rhs();
  152. }
  153. (end, 0.)
  154. }
  155. }
  156. /// Represents a string with a cursor. The cursor can be moved in terms of glyphs, which does
  157. /// not always correspond to chars in the string. We refer to byte indexes as idx, and glyph
  158. /// indexes as pos.
  159. ///
  160. /// The full string is: `before_text + commit_text + compose_text + after_text`.
  161. /// The commit/compose text is the current text being composed which is needed for Android
  162. /// autosuggest input.
  163. ///
  164. /// Before and after text refers to the cursor. The cursor position is always everything
  165. /// before `after_text`, that is: `before_text + commit_text + compose_text`.
  166. ///
  167. /// We have to be careful when adjusting the cursor and other related ops like selecting text
  168. /// to first render and move in terms of glyphs due to kerning. For example the chars "ae"
  169. /// may be rendered as a single glyph in some fonts. Same for emojis represented by multiple
  170. /// chars which are often not even a single byte.
  171. pub struct Editable {
  172. text_shaper: TextShaperPtr,
  173. composer: ComposingText,
  174. before_text: String,
  175. after_text: String,
  176. font_size: PropertyFloat32,
  177. window_scale: PropertyFloat32,
  178. baseline: PropertyFloat32,
  179. }
  180. impl Editable {
  181. pub fn new(
  182. text_shaper: TextShaperPtr,
  183. font_size: PropertyFloat32,
  184. window_scale: PropertyFloat32,
  185. baseline: PropertyFloat32,
  186. ) -> Self {
  187. Self {
  188. text_shaper,
  189. composer: ComposingText::new(),
  190. before_text: String::new(),
  191. after_text: String::new(),
  192. font_size,
  193. window_scale,
  194. baseline,
  195. }
  196. }
  197. // reset composition
  198. // set text
  199. // find pos
  200. // compose
  201. // commit
  202. // set_compose_region
  203. // delete (forward, back)
  204. // set cursor
  205. /// Reset any composition in progress
  206. pub fn end_compose(&mut self) {
  207. //#[cfg(target_os = "android")]
  208. //crate::android::cancel_composition();
  209. //debug!(target: "ui::editbox", "end_compose() [editable={self:?}]");
  210. let final_text = self.composer.clear();
  211. self.before_text += &final_text;
  212. }
  213. pub fn get_text_before(&self) -> String {
  214. let text =
  215. self.before_text.clone() + &self.composer.commit_text + &self.composer.compose_text;
  216. text
  217. }
  218. pub fn get_text(&self) -> String {
  219. let text = self.get_text_before() + &self.after_text;
  220. text
  221. }
  222. pub fn set_text(&mut self, before: String, after: String) {
  223. self.before_text = before;
  224. self.after_text = after;
  225. }
  226. pub fn compose(&mut self, suggest_text: &str, is_commit: bool) {
  227. //composer.activate_or_cont(self.cursor_pos.get() as usize);
  228. self.composer.compose(suggest_text.to_string());
  229. if is_commit {
  230. self.composer.commit();
  231. }
  232. }
  233. /// Convenience function
  234. pub fn set_compose_region(&mut self, start: usize, end: usize) {
  235. self.composer.set_compose_region(start, end);
  236. }
  237. pub fn delete(&mut self, before: usize, after: usize) {
  238. self.end_compose();
  239. let mut chars = self.before_text.chars();
  240. // nightly feature, commenting it for now
  241. //chars.advance_back_by(before);
  242. self.before_text = chars.as_str().to_string();
  243. let mut chars = self.after_text.chars();
  244. // nightly feature, commenting it for now
  245. //chars.advance_by(after);
  246. self.after_text = chars.as_str().to_string();
  247. }
  248. /// Move the cursor. This offset should be computed from the glyphs.
  249. pub fn move_cursor(&mut self, dir: isize) {
  250. self.end_compose();
  251. let rendered = self.render();
  252. let mut cursor_pos = self.get_cursor_pos(&rendered);
  253. // Move the cursor pos
  254. if dir < 0 {
  255. assert!(-dir >= 0);
  256. let dir = -dir as usize;
  257. if cursor_pos > 0 {
  258. cursor_pos -= dir;
  259. }
  260. } else {
  261. assert!(dir >= 0);
  262. cursor_pos += dir as usize;
  263. let glyphs_len = rendered.glyphs.len();
  264. if cursor_pos > glyphs_len {
  265. cursor_pos = glyphs_len;
  266. }
  267. }
  268. // Convert cursor pos to string idx
  269. let idx = rendered.pos_to_idx(cursor_pos);
  270. self.set_cursor_idx(idx);
  271. }
  272. pub fn set_cursor_idx(&mut self, idx: TextPos) {
  273. // move_cursor() also calls this, but should be fine.
  274. self.end_compose();
  275. let mut text = self.get_text();
  276. let after_text = text.split_off(idx);
  277. self.before_text = text;
  278. self.after_text = after_text;
  279. }
  280. pub fn move_start(&mut self) {
  281. self.end_compose();
  282. self.after_text = self.get_text();
  283. self.before_text.clear();
  284. }
  285. pub fn move_end(&mut self) {
  286. self.end_compose();
  287. self.before_text = self.get_text();
  288. self.after_text.clear();
  289. }
  290. pub fn get_cursor_pos(&self, rendered: &RenderedEditable) -> TextPos {
  291. let cursor_off = self.get_text_before().len();
  292. let cursor_pos = rendered.idx_to_pos(cursor_off);
  293. cursor_pos
  294. }
  295. pub fn render(&self) -> RenderedEditable {
  296. let font_size = self.font_size.get();
  297. let window_scale = self.window_scale.get();
  298. let text = self.get_text();
  299. let glyphs = self.text_shaper.shape(text, font_size, window_scale);
  300. let compose_off = self.before_text.len();
  301. RenderedEditable::new(
  302. glyphs,
  303. compose_off + self.composer.region_start,
  304. compose_off + self.composer.region_end,
  305. )
  306. }
  307. }
  308. impl std::fmt::Debug for Editable {
  309. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  310. write!(
  311. f,
  312. "({}, {}, {}, {})",
  313. self.before_text,
  314. self.composer.commit_text,
  315. self.composer.compose_text,
  316. self.after_text
  317. )
  318. }
  319. }
  320. fn glyphs_to_string(glyphs: &Vec<Glyph>) -> String {
  321. let mut text = String::new();
  322. for (i, glyph) in glyphs.iter().enumerate() {
  323. text.push_str(&glyph.substr);
  324. }
  325. text
  326. }
  327. #[derive(Clone)]
  328. pub struct Selection {
  329. pub start: TextPos,
  330. pub end: TextPos,
  331. }
  332. impl Selection {
  333. pub fn new(start: TextPos, end: TextPos) -> Self {
  334. Self { start, end }
  335. }
  336. }
  337. impl std::fmt::Debug for Selection {
  338. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  339. write!(f, "[{}, {}]", self.start, self.end)
  340. }
  341. }