chatedit.rs 60 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764
  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 async_lock::Mutex as AsyncMutex;
  19. use async_trait::async_trait;
  20. use atomic_float::AtomicF32;
  21. use darkfi::system::msleep;
  22. use darkfi_serial::{deserialize, Decodable, Encodable, SerialDecodable, SerialEncodable};
  23. use miniquad::{window, KeyCode, KeyMods, MouseButton, TouchPhase};
  24. use parking_lot::Mutex as SyncMutex;
  25. use rand::{rngs::OsRng, Rng};
  26. use std::{
  27. collections::HashMap,
  28. io::Cursor,
  29. sync::{
  30. atomic::{AtomicBool, Ordering},
  31. Arc, OnceLock, Weak,
  32. },
  33. time::Instant,
  34. };
  35. use crate::{
  36. error::Result,
  37. gfx::{
  38. GfxDrawCall, GfxDrawInstruction, GfxDrawMesh, GfxTextureId, GraphicsEventPublisherPtr,
  39. Point, Rectangle, RenderApi, Vertex,
  40. },
  41. mesh::{Color, MeshBuilder, MeshInfo, COLOR_BLUE, COLOR_RED, COLOR_WHITE},
  42. prop::{
  43. PropertyAtomicGuard, PropertyBool, PropertyColor, PropertyFloat32, PropertyPtr,
  44. PropertyRect, PropertyStr, PropertyUint32, Role,
  45. },
  46. pubsub::Subscription,
  47. scene::{MethodCallSub, Pimpl, SceneNodePtr, SceneNodeWeak},
  48. text::{self, Glyph, GlyphPositionIter, TextShaperPtr},
  49. text2::{self, Editor},
  50. util::{enumerate_ref, is_whitespace, min_f32, unixtime, zip4},
  51. AndroidSuggestEvent, ExecutorPtr,
  52. };
  53. use super::{
  54. editbox::{
  55. editable::{Editable, RenderedEditable, Selection, TextIdx, TextPos},
  56. eol_nudge,
  57. repeat::{PressedKey, PressedKeysSmoothRepeat},
  58. ALLOWED_KEYCODES, DISALLOWED_CHARS,
  59. },
  60. DrawUpdate, OnModify, UIObject,
  61. };
  62. // Minimum dist to update scroll when finger scrolling.
  63. // Avoid updating too much makes scrolling smoother.
  64. const VERT_SCROLL_UPDATE_INC: f32 = 1.;
  65. macro_rules! d { ($($arg:tt)*) => { debug!(target: "ui::chatedit", $($arg)*); } }
  66. macro_rules! t { ($($arg:tt)*) => { trace!(target: "ui::chatedit", $($arg)*); } }
  67. // You must be careful working with string indexes in Java. They are UTF16 string indexs, not UTF8
  68. fn char16_to_byte_index(s: &str, char_idx: usize) -> Option<usize> {
  69. let utf16_data: Vec<_> = s.encode_utf16().take(char_idx).collect();
  70. let prestr = String::from_utf16(&utf16_data).ok()?;
  71. Some(prestr.len())
  72. }
  73. fn byte_to_char16_index(s: &str, byte_idx: usize) -> Option<usize> {
  74. if byte_idx > s.len() || !s.is_char_boundary(byte_idx) {
  75. return None
  76. }
  77. Some(s[..byte_idx].encode_utf16().count())
  78. }
  79. #[derive(Clone)]
  80. struct TouchStartInfo {
  81. pos: Point,
  82. instant: std::time::Instant,
  83. }
  84. impl TouchStartInfo {
  85. fn new(pos: Point) -> Self {
  86. Self { pos, instant: std::time::Instant::now() }
  87. }
  88. }
  89. #[derive(Clone)]
  90. enum TouchStateAction {
  91. Inactive,
  92. Started { pos: Point, instant: std::time::Instant },
  93. StartSelect,
  94. Select,
  95. DragSelectHandle { side: isize },
  96. ScrollVert { start_pos: Point, scroll_start: f32 },
  97. SetCursorPos,
  98. }
  99. struct TouchInfo {
  100. state: TouchStateAction,
  101. start: Option<TouchStartInfo>,
  102. scroll: PropertyFloat32,
  103. }
  104. impl TouchInfo {
  105. fn new(scroll: PropertyFloat32) -> Self {
  106. Self { state: TouchStateAction::Inactive, start: None, scroll }
  107. }
  108. fn start(&mut self, pos: Point) {
  109. debug!(target: "ui::chatedit::touch", "start touch: Started state");
  110. self.state = TouchStateAction::Started { pos, instant: std::time::Instant::now() };
  111. }
  112. fn stop(&mut self) -> TouchStateAction {
  113. debug!(target: "ui::chatedit::touch", "stop touch: Inactive state");
  114. std::mem::replace(&mut self.state, TouchStateAction::Inactive)
  115. }
  116. fn update(&mut self, pos: &Point) {
  117. match &self.state {
  118. TouchStateAction::Started { pos: start_pos, instant } => {
  119. let travel_dist = pos.dist_sq(*start_pos);
  120. let grad = (pos.y - start_pos.y) / (pos.x - start_pos.x);
  121. let elapsed = instant.elapsed().as_millis();
  122. //debug!(target: "ui::chatedit::touch", "TouchInfo::update() [travel_dist={travel_dist}, grad={grad}]");
  123. if travel_dist < 5. {
  124. if elapsed > 1000 {
  125. debug!(target: "ui::chatedit::touch", "update touch state: Started -> StartSelect");
  126. self.state = TouchStateAction::StartSelect;
  127. }
  128. } else if grad.abs() > 0.5 {
  129. // Vertical movement
  130. debug!(target: "ui::chatedit::touch", "update touch state: Started -> ScrollVert");
  131. let scroll_start = self.scroll.get();
  132. self.state =
  133. TouchStateAction::ScrollVert { start_pos: *start_pos, scroll_start };
  134. } else {
  135. // Horizontal movement
  136. debug!(target: "ui::chatedit::touch", "update touch state: Started -> SetCursorPos");
  137. self.state = TouchStateAction::SetCursorPos;
  138. }
  139. }
  140. _ => {}
  141. }
  142. }
  143. }
  144. /*
  145. impl std::fmt::Debug for Editor {
  146. fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
  147. let mut changes = vec![];
  148. let sel = self.editor.raw_selection();
  149. if sel.is_collapsed() {
  150. let cursor = sel.focus().index();
  151. changes.push((cursor, '|'));
  152. } else {
  153. let sel = sel.text_range();
  154. changes.push((sel.start, '{'));
  155. changes.push((sel.end, '}'));
  156. }
  157. if let Some(compose) = self.editor.compose() {
  158. changes.push((compose.start, '['));
  159. changes.push((compose.end, ']'));
  160. }
  161. changes.sort_by(|a, b| b.0.cmp(&a.0));
  162. write!(f, "'")?;
  163. let mut buffer = self.editor.raw_text();
  164. for (byte_idx, c) in buffer.char_indices() {
  165. while let Some((idx, d)) = changes.last() {
  166. if *idx > byte_idx {
  167. break
  168. }
  169. write!(f, "{}", d)?;
  170. let _ = changes.pop();
  171. }
  172. write!(f, "{}", c)?;
  173. }
  174. write!(f, "'")
  175. }
  176. }
  177. */
  178. enum ColoringState {
  179. Start,
  180. IsCommand,
  181. Normal,
  182. }
  183. pub type ChatEditPtr = Arc<ChatEdit>;
  184. pub struct ChatEdit {
  185. node: SceneNodeWeak,
  186. tasks: OnceLock<Vec<smol::Task<()>>>,
  187. render_api: RenderApi,
  188. text_shaper: TextShaperPtr,
  189. key_repeat: SyncMutex<PressedKeysSmoothRepeat>,
  190. glyphs: SyncMutex<Vec<Glyph>>,
  191. main_dc_key: u64,
  192. /// DC key for the text
  193. text_dc_key: u64,
  194. cursor_mesh: SyncMutex<Option<GfxDrawMesh>>,
  195. /// DC key for the cursor. Allows updating cursor independently.
  196. cursor_dc_key: u64,
  197. is_active: PropertyBool,
  198. is_focused: PropertyBool,
  199. min_height: PropertyFloat32,
  200. max_height: PropertyFloat32,
  201. content_height: PropertyFloat32,
  202. rect: PropertyRect,
  203. baseline: PropertyFloat32,
  204. linespacing: PropertyFloat32,
  205. descent: PropertyFloat32,
  206. lineheight: PropertyFloat32,
  207. scroll: PropertyFloat32,
  208. scroll_speed: PropertyFloat32,
  209. padding: PropertyPtr,
  210. font_size: PropertyFloat32,
  211. text: PropertyStr,
  212. text_color: PropertyColor,
  213. text_hi_color: PropertyColor,
  214. text_cmd_color: PropertyColor,
  215. cursor_color: PropertyColor,
  216. cursor_width: PropertyFloat32,
  217. cursor_ascent: PropertyFloat32,
  218. cursor_descent: PropertyFloat32,
  219. cursor_blink_time: PropertyUint32,
  220. cursor_idle_time: PropertyUint32,
  221. hi_bg_color: PropertyColor,
  222. cmd_bg_color: PropertyColor,
  223. select_ascent: PropertyFloat32,
  224. select_descent: PropertyFloat32,
  225. handle_descent: PropertyFloat32,
  226. select_text: PropertyPtr,
  227. z_index: PropertyUint32,
  228. priority: PropertyUint32,
  229. debug: PropertyBool,
  230. mouse_btn_held: AtomicBool,
  231. cursor_is_visible: AtomicBool,
  232. blink_is_paused: AtomicBool,
  233. /// Used to explicitly hide the cursor. Must be manually re-enabled.
  234. hide_cursor: AtomicBool,
  235. touch_info: SyncMutex<TouchInfo>,
  236. is_phone_select: AtomicBool,
  237. old_window_scale: AtomicF32,
  238. window_scale: PropertyFloat32,
  239. parent_rect: SyncMutex<Option<Rectangle>>,
  240. is_mouse_hover: AtomicBool,
  241. editor: AsyncMutex<Editor>,
  242. }
  243. impl ChatEdit {
  244. pub async fn new(
  245. node: SceneNodeWeak,
  246. window_scale: PropertyFloat32,
  247. render_api: RenderApi,
  248. text_shaper: TextShaperPtr,
  249. ex: ExecutorPtr,
  250. ) -> Pimpl {
  251. t!("ChatEdit::new()");
  252. let node_ref = &node.upgrade().unwrap();
  253. let is_active = PropertyBool::wrap(node_ref, Role::Internal, "is_active", 0).unwrap();
  254. let is_focused = PropertyBool::wrap(node_ref, Role::Internal, "is_focused", 0).unwrap();
  255. let min_height =
  256. PropertyFloat32::wrap(node_ref, Role::Internal, "height_range", 0).unwrap();
  257. let max_height =
  258. PropertyFloat32::wrap(node_ref, Role::Internal, "height_range", 1).unwrap();
  259. let content_height =
  260. PropertyFloat32::wrap(node_ref, Role::Internal, "content_height", 0).unwrap();
  261. let rect = PropertyRect::wrap(node_ref, Role::Internal, "rect").unwrap();
  262. let baseline = PropertyFloat32::wrap(node_ref, Role::Internal, "baseline", 0).unwrap();
  263. let linespacing =
  264. PropertyFloat32::wrap(node_ref, Role::Internal, "linespacing", 0).unwrap();
  265. let descent = PropertyFloat32::wrap(node_ref, Role::Internal, "descent", 0).unwrap();
  266. let lineheight = PropertyFloat32::wrap(node_ref, Role::Internal, "lineheight", 0).unwrap();
  267. let scroll = PropertyFloat32::wrap(node_ref, Role::Internal, "scroll", 0).unwrap();
  268. let scroll_speed =
  269. PropertyFloat32::wrap(node_ref, Role::Internal, "scroll_speed", 0).unwrap();
  270. let padding = node_ref.get_property("padding").unwrap();
  271. let font_size = PropertyFloat32::wrap(node_ref, Role::Internal, "font_size", 0).unwrap();
  272. let text = PropertyStr::wrap(node_ref, Role::Internal, "text", 0).unwrap();
  273. let text_color = PropertyColor::wrap(node_ref, Role::Internal, "text_color").unwrap();
  274. let text_hi_color = PropertyColor::wrap(node_ref, Role::Internal, "text_hi_color").unwrap();
  275. let text_cmd_color =
  276. PropertyColor::wrap(node_ref, Role::Internal, "text_cmd_color").unwrap();
  277. let cursor_color = PropertyColor::wrap(node_ref, Role::Internal, "cursor_color").unwrap();
  278. let cursor_width =
  279. PropertyFloat32::wrap(node_ref, Role::Internal, "cursor_width", 0).unwrap();
  280. let cursor_ascent =
  281. PropertyFloat32::wrap(node_ref, Role::Internal, "cursor_ascent", 0).unwrap();
  282. let cursor_descent =
  283. PropertyFloat32::wrap(node_ref, Role::Internal, "cursor_descent", 0).unwrap();
  284. let hi_bg_color = PropertyColor::wrap(node_ref, Role::Internal, "hi_bg_color").unwrap();
  285. let cmd_bg_color = PropertyColor::wrap(node_ref, Role::Internal, "cmd_bg_color").unwrap();
  286. let select_ascent =
  287. PropertyFloat32::wrap(node_ref, Role::Internal, "select_ascent", 0).unwrap();
  288. let select_descent =
  289. PropertyFloat32::wrap(node_ref, Role::Internal, "select_descent", 0).unwrap();
  290. let handle_descent =
  291. PropertyFloat32::wrap(node_ref, Role::Internal, "handle_descent", 0).unwrap();
  292. let select_text = node_ref.get_property("select_text").unwrap();
  293. let cursor_blink_time =
  294. PropertyUint32::wrap(node_ref, Role::Internal, "cursor_blink_time", 0).unwrap();
  295. let cursor_idle_time =
  296. PropertyUint32::wrap(node_ref, Role::Internal, "cursor_idle_time", 0).unwrap();
  297. let z_index = PropertyUint32::wrap(node_ref, Role::Internal, "z_index", 0).unwrap();
  298. let priority = PropertyUint32::wrap(node_ref, Role::Internal, "priority", 0).unwrap();
  299. let debug = PropertyBool::wrap(node_ref, Role::Internal, "debug", 0).unwrap();
  300. let node_name = node_ref.name.clone();
  301. let node_id = node_ref.id;
  302. // Must do this whenever the text changes
  303. let glyphs = text_shaper.shape(text.get(), font_size.get(), window_scale.get());
  304. let self_ = Arc::new(Self {
  305. node,
  306. tasks: OnceLock::new(),
  307. render_api,
  308. text_shaper: text_shaper.clone(),
  309. key_repeat: SyncMutex::new(PressedKeysSmoothRepeat::new(400, 50)),
  310. glyphs: SyncMutex::new(glyphs),
  311. main_dc_key: OsRng.gen(),
  312. text_dc_key: OsRng.gen(),
  313. cursor_mesh: SyncMutex::new(None),
  314. cursor_dc_key: OsRng.gen(),
  315. is_active,
  316. is_focused,
  317. min_height,
  318. max_height,
  319. content_height,
  320. rect,
  321. baseline,
  322. linespacing,
  323. descent,
  324. lineheight: lineheight.clone(),
  325. scroll: scroll.clone(),
  326. scroll_speed,
  327. padding,
  328. font_size: font_size.clone(),
  329. text: text.clone(),
  330. text_color: text_color.clone(),
  331. text_hi_color,
  332. text_cmd_color,
  333. cursor_color,
  334. cursor_width,
  335. cursor_ascent,
  336. cursor_descent,
  337. cursor_blink_time,
  338. cursor_idle_time,
  339. hi_bg_color,
  340. cmd_bg_color,
  341. select_ascent,
  342. select_descent,
  343. handle_descent,
  344. select_text,
  345. z_index,
  346. priority,
  347. debug,
  348. mouse_btn_held: AtomicBool::new(false),
  349. cursor_is_visible: AtomicBool::new(true),
  350. blink_is_paused: AtomicBool::new(false),
  351. hide_cursor: AtomicBool::new(false),
  352. touch_info: SyncMutex::new(TouchInfo::new(scroll)),
  353. is_phone_select: AtomicBool::new(false),
  354. old_window_scale: AtomicF32::new(window_scale.get()),
  355. window_scale: window_scale.clone(),
  356. parent_rect: SyncMutex::new(None),
  357. is_mouse_hover: AtomicBool::new(false),
  358. editor: AsyncMutex::new(
  359. Editor::new(text, font_size, text_color, window_scale, lineheight).await,
  360. ),
  361. });
  362. //self_
  363. // .text_wrap
  364. // .lock()
  365. // .editable
  366. // .set_text("".to_string(), "king!😁🍆jelly 🍆1234".to_string());
  367. //self_.text_wrap.lock().editable.set_text(
  368. // "".to_string(),
  369. // "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.".to_string()
  370. //);
  371. Pimpl::ChatEdit(self_)
  372. }
  373. fn node(&self) -> SceneNodePtr {
  374. self.node.upgrade().unwrap()
  375. }
  376. fn padding_top(&self) -> f32 {
  377. self.padding.get_f32(0).unwrap()
  378. }
  379. fn padding_bottom(&self) -> f32 {
  380. self.padding.get_f32(1).unwrap()
  381. }
  382. fn abs_to_local(&self, point: &mut Point) {
  383. let rect = self.rect.get();
  384. *point -= rect.pos();
  385. point.y -= self.padding_top();
  386. point.y += self.scroll.get();
  387. }
  388. fn regen_cursor_mesh(&self) -> GfxDrawMesh {
  389. let cursor_width = self.cursor_width.get();
  390. let cursor_ascent = self.cursor_ascent.get();
  391. let cursor_descent = self.cursor_descent.get();
  392. let baseline = self.baseline.get();
  393. let cursor_rect = Rectangle {
  394. x: 0.,
  395. y: baseline - cursor_ascent,
  396. w: cursor_width,
  397. h: cursor_ascent + cursor_descent,
  398. };
  399. let cursor_color = self.cursor_color.get();
  400. let mut mesh = MeshBuilder::new();
  401. mesh.draw_filled_box(&cursor_rect, cursor_color);
  402. mesh.alloc(&self.render_api).draw_untextured()
  403. }
  404. fn draw_phone_select_handle(&self, mesh: &mut MeshBuilder, pos: Point, side: f32) {
  405. debug!(target: "ui::chatedit", "draw_phone_select_handle(..., {pos:?}, {side})");
  406. let baseline = self.baseline.get();
  407. let select_ascent = self.select_ascent.get();
  408. let handle_descent = self.handle_descent.get();
  409. let color = self.text_hi_color.get();
  410. let linespacing = self.linespacing.get();
  411. let scroll = self.scroll.get();
  412. // Transparent for fade
  413. let mut color_trans = color.clone();
  414. color_trans[3] = 0.;
  415. // find start_x
  416. let x = pos.x;
  417. let y_off = pos.y - scroll;
  418. let y = y_off + baseline + handle_descent;
  419. if y < 0. {
  420. return
  421. }
  422. // Vertical line downwards. We use this instead of draw_box() so we have a fade.
  423. let verts = vec![
  424. Vertex {
  425. pos: [x - side * 1., y_off + baseline - select_ascent],
  426. color: color_trans,
  427. uv: [0., 0.],
  428. },
  429. Vertex {
  430. pos: [x + side * 4., y_off + baseline - select_ascent],
  431. color: color_trans,
  432. uv: [0., 0.],
  433. },
  434. Vertex {
  435. pos: [x - side * 1., y_off + baseline + handle_descent + 5.],
  436. color,
  437. uv: [0., 0.],
  438. },
  439. Vertex {
  440. pos: [x + side * 4., y_off + baseline + handle_descent + 5.],
  441. color,
  442. uv: [0., 0.],
  443. },
  444. ];
  445. let indices = vec![0, 2, 1, 1, 2, 3];
  446. mesh.append(verts, indices);
  447. // The arrow itself.
  448. // Go anti-clockwise
  449. let verts = vec![
  450. Vertex { pos: [x, y], color, uv: [0., 0.] },
  451. Vertex { pos: [x, y + 50.], color, uv: [0., 0.] },
  452. Vertex { pos: [x + side * 30., y + 50.], color, uv: [0., 0.] },
  453. Vertex { pos: [x + side * 50., y + 25.], color, uv: [0., 0.] },
  454. ];
  455. let indices = vec![0, 1, 2, 0, 2, 3];
  456. mesh.append(verts, indices);
  457. }
  458. async fn change_focus(self: Arc<Self>) {
  459. if !self.is_active.get() {
  460. return
  461. }
  462. t!("Focus changed");
  463. // Cursor visibility will change so just redraw everything lol
  464. self.redraw().await;
  465. }
  466. async fn insert_char(&self, key: char) {
  467. t!("insert_char({key})");
  468. let mut tmp = [0; 4];
  469. let key_str = key.encode_utf8(&mut tmp);
  470. let mut txt_ctx = text2::TEXT_CTX.get().await;
  471. let mut editor = self.editor.lock().await;
  472. let mut drv = editor.driver(&mut txt_ctx).await.unwrap();
  473. drv.insert_or_replace_selection(&key_str);
  474. }
  475. async fn handle_shortcut(
  476. &self,
  477. key: char,
  478. mods: &KeyMods,
  479. atom: &mut PropertyAtomicGuard,
  480. ) -> bool {
  481. t!("handle_shortcut({:?}, {:?})", key, mods);
  482. #[cfg(not(target_os = "macos"))]
  483. let action_mod = mods.ctrl;
  484. #[cfg(target_os = "macos")]
  485. let action_mod = mods.logo;
  486. let mut txt_ctx = text2::TEXT_CTX.get().await;
  487. let mut editor = self.editor.lock().await;
  488. let mut drv = editor.driver(&mut txt_ctx).await.unwrap();
  489. match key {
  490. 'a' => {
  491. if action_mod {
  492. drv.select_all();
  493. }
  494. }
  495. 'c' => {
  496. if action_mod {
  497. if let Some(text) = editor.selected_text() {
  498. miniquad::window::clipboard_set(&text);
  499. }
  500. }
  501. }
  502. 'v' => {
  503. if action_mod {
  504. if let Some(text) = miniquad::window::clipboard_get() {
  505. drv.insert_or_replace_selection(&text);
  506. }
  507. }
  508. }
  509. _ => return false,
  510. }
  511. drop(editor);
  512. drop(txt_ctx);
  513. self.redraw().await;
  514. true
  515. }
  516. async fn handle_key(
  517. &self,
  518. key: &KeyCode,
  519. mods: &KeyMods,
  520. atom: &mut PropertyAtomicGuard,
  521. ) -> bool {
  522. #[cfg(not(target_os = "macos"))]
  523. let action_mod = mods.ctrl;
  524. #[cfg(target_os = "macos")]
  525. let action_mod = mods.logo;
  526. t!("handle_key({:?}, {:?}) action_mod={action_mod}", key, mods);
  527. let mut txt_ctx = text2::TEXT_CTX.get().await;
  528. let mut editor = self.editor.lock().await;
  529. let mut drv = editor.driver(&mut txt_ctx).await.unwrap();
  530. match key {
  531. KeyCode::Left => {
  532. if action_mod {
  533. if mods.shift {
  534. drv.select_word_left();
  535. } else {
  536. drv.move_word_left();
  537. }
  538. } else if mods.shift {
  539. drv.select_left();
  540. } else {
  541. drv.move_left();
  542. }
  543. }
  544. KeyCode::Right => {
  545. if action_mod {
  546. if mods.shift {
  547. drv.select_word_right();
  548. } else {
  549. drv.move_word_right();
  550. }
  551. } else if mods.shift {
  552. drv.select_right();
  553. } else {
  554. drv.move_right();
  555. }
  556. }
  557. KeyCode::Up => {
  558. if mods.shift {
  559. drv.select_up();
  560. } else {
  561. drv.move_up();
  562. }
  563. }
  564. KeyCode::Down => {
  565. if mods.shift {
  566. drv.select_down();
  567. } else {
  568. drv.move_down();
  569. }
  570. }
  571. KeyCode::Enter | KeyCode::KpEnter => {
  572. if mods.shift {
  573. drv.insert_or_replace_selection("\n");
  574. }
  575. }
  576. KeyCode::Delete => {
  577. if action_mod {
  578. drv.delete_word();
  579. } else {
  580. drv.delete();
  581. }
  582. }
  583. KeyCode::Backspace => {
  584. if action_mod {
  585. drv.backdelete_word();
  586. } else {
  587. drv.backdelete();
  588. }
  589. }
  590. KeyCode::Home => {
  591. if action_mod {
  592. if mods.shift {
  593. drv.select_to_text_start();
  594. } else {
  595. drv.move_to_text_start();
  596. }
  597. } else if mods.shift {
  598. drv.select_to_line_start();
  599. } else {
  600. drv.move_to_line_start();
  601. }
  602. }
  603. KeyCode::End => {
  604. if action_mod {
  605. if mods.shift {
  606. drv.select_to_text_end();
  607. } else {
  608. drv.move_to_text_end();
  609. }
  610. } else if mods.shift {
  611. drv.select_to_line_end();
  612. } else {
  613. drv.move_to_line_end();
  614. }
  615. }
  616. _ => return false,
  617. }
  618. drop(editor);
  619. drop(txt_ctx);
  620. self.pause_blinking();
  621. self.redraw().await;
  622. return true
  623. }
  624. /*
  625. fn delete(&self, before: usize, after: usize, atom: &mut PropertyAtomicGuard) {
  626. let mut text_wrap = &mut self.text_wrap.lock();
  627. if text_wrap.select.is_empty() {
  628. text_wrap.editable.delete(before, after);
  629. text_wrap.clear_cache();
  630. } else {
  631. text_wrap.delete_selected();
  632. self.update_select_text(&mut text_wrap, atom);
  633. }
  634. self.is_phone_select.store(false, Ordering::Relaxed);
  635. // Reshow cursor (if hidden)
  636. self.hide_cursor.store(false, Ordering::Relaxed);
  637. let text = text_wrap.editable.get_text();
  638. self.text.set(atom, text);
  639. }
  640. */
  641. /// This will select the entire word rather than move the cursor to that location
  642. async fn start_touch_select(&self, touch_pos: Point, atom: &mut PropertyAtomicGuard) {
  643. t!("start_touch_select({touch_pos:?})");
  644. let mut editor = self.editor.lock().await;
  645. editor.select_word_at_point(touch_pos);
  646. editor.refresh(atom).await;
  647. let seltext = editor.selected_text().unwrap();
  648. d!("Selected {seltext:?} from {touch_pos:?}");
  649. self.select_text.clone().set_str(atom, Role::Internal, 0, seltext).unwrap();
  650. drop(editor);
  651. // if start != end {
  652. self.is_phone_select.store(true, Ordering::Relaxed);
  653. self.hide_cursor.store(true, Ordering::Relaxed);
  654. // }
  655. }
  656. async fn handle_touch_start(&self, mut touch_pos: Point) -> bool {
  657. t!("handle_touch_start({touch_pos:?})");
  658. if self.try_handle_drag(touch_pos).await {
  659. return true
  660. }
  661. let rect = self.rect.get();
  662. if !rect.contains(touch_pos) {
  663. t!("rect!cont rect={rect:?}, touch_pos={touch_pos:?}");
  664. return false
  665. }
  666. let mut touch_info = self.touch_info.lock();
  667. touch_info.start(touch_pos);
  668. true
  669. }
  670. async fn get_select_handles(&self) -> Option<(Point, Point)> {
  671. let editor = self.editor.lock().await;
  672. let layout = editor.layout();
  673. let sel = editor.selection(1);
  674. if sel.is_collapsed() {
  675. assert!(!self.is_phone_select.load(Ordering::Relaxed));
  676. return None
  677. }
  678. assert!(self.is_phone_select.load(Ordering::Relaxed));
  679. let mut first_pos = None;
  680. let mut last_pos = Default::default();
  681. sel.geometry_with(layout, |rect: parley::Rect, _| {
  682. let rect = Rectangle::from(rect);
  683. if first_pos.is_none() {
  684. first_pos = Some(rect.bot_left());
  685. }
  686. last_pos = rect.corner();
  687. });
  688. let first_pos = first_pos.unwrap();
  689. Some((first_pos, last_pos))
  690. }
  691. async fn try_handle_drag(&self, mut touch_pos: Point) -> bool {
  692. let Some((mut first_pos, mut last_pos)) = self.get_select_handles().await else {
  693. return false
  694. };
  695. self.abs_to_local(&mut touch_pos);
  696. t!("localize touch_pos = {touch_pos:?}");
  697. let handle_off_y = self.handle_descent.get();
  698. first_pos.y += handle_off_y;
  699. last_pos.y += handle_off_y;
  700. // Are we within range of either one?
  701. t!("handle center points = ({first_pos:?}, {last_pos:?})");
  702. const TOUCH_RADIUS_SQ: f32 = 10_000.;
  703. if first_pos.dist_sq(touch_pos) <= TOUCH_RADIUS_SQ {
  704. d!("start touch: DragSelectHandle state [side=-1]");
  705. // Set touch_state status to enable begin dragging them
  706. let mut touch_info = self.touch_info.lock();
  707. touch_info.state = TouchStateAction::DragSelectHandle { side: -1 };
  708. return true
  709. }
  710. if last_pos.dist_sq(touch_pos) <= TOUCH_RADIUS_SQ {
  711. d!("start touch: DragSelectHandle state [side=1]");
  712. // Set touch_state status to enable begin dragging them
  713. let mut touch_info = self.touch_info.lock();
  714. touch_info.state = TouchStateAction::DragSelectHandle { side: 1 };
  715. return true
  716. }
  717. false
  718. }
  719. async fn handle_touch_move(&self, mut touch_pos: Point) -> bool {
  720. t!("handle_touch_move({touch_pos:?})");
  721. let atom = &mut PropertyAtomicGuard::new();
  722. // We must update with non relative touch_pos bcos when doing vertical scrolling
  723. // we will modify the scroll, which is used by abs_to_local(), which is used
  724. // to then calculate the max scroll. So it ends up jumping around.
  725. // We use the abs touch_pos without scroll adjust applied for vert scrolling.
  726. let touch_state = {
  727. let mut touch_info = self.touch_info.lock();
  728. touch_info.update(&touch_pos);
  729. touch_info.state.clone()
  730. };
  731. match &touch_state {
  732. TouchStateAction::Inactive => return false,
  733. TouchStateAction::StartSelect => {
  734. if self.text.get().is_empty() {
  735. let node = self.node.upgrade().unwrap();
  736. node.trigger("paste_request", vec![]).await.unwrap();
  737. } else {
  738. self.abs_to_local(&mut touch_pos);
  739. self.start_touch_select(touch_pos, atom).await;
  740. self.redraw().await;
  741. }
  742. d!("touch state: StartSelect -> Select");
  743. self.touch_info.lock().state = TouchStateAction::Select;
  744. }
  745. TouchStateAction::DragSelectHandle { side } => {
  746. let handle_descent = self.handle_descent.get();
  747. self.abs_to_local(&mut touch_pos);
  748. let editor = self.editor.lock().await;
  749. let sel = editor.selection(*side);
  750. assert!(*side == -1 || *side == 1);
  751. assert!(self.is_phone_select.load(Ordering::Relaxed));
  752. assert!(!sel.is_collapsed());
  753. let mut pos = touch_pos;
  754. // Only allow selecting text that is visible in the box
  755. // We do our calcs relative to (0, 0) so bhs = rect_h
  756. pos.y -= handle_descent + 25.;
  757. //let bhs = wrapped_lines.height();
  758. //pos.y = min_f32(pos.y, bhs);
  759. let layout = editor.layout();
  760. let sel = sel.extend_to_point(layout, pos.x, pos.y);
  761. let sel = sel.text_range();
  762. editor.set_selection(sel.start, sel.end);
  763. drop(editor);
  764. self.redraw().await;
  765. }
  766. TouchStateAction::ScrollVert { start_pos, scroll_start } => {
  767. /*
  768. let max_scroll = {
  769. let mut text_wrap = self.text_wrap.lock();
  770. self.max_scroll(&mut text_wrap)
  771. };
  772. let y_dist = start_pos.y - touch_pos.y;
  773. let mut scroll = scroll_start + y_dist;
  774. scroll = scroll.clamp(0., max_scroll);
  775. if (self.scroll.get() - scroll).abs() < VERT_SCROLL_UPDATE_INC {
  776. return true
  777. }
  778. self.scroll.set(atom, scroll);
  779. self.redraw().await;
  780. */
  781. }
  782. TouchStateAction::SetCursorPos => {
  783. // TBH I can't even see the cursor under my thumb so I'll just
  784. // comment this for now.
  785. self.abs_to_local(&mut touch_pos);
  786. self.touch_set_cursor_pos(touch_pos, atom).await
  787. }
  788. _ => {}
  789. }
  790. true
  791. }
  792. async fn handle_touch_end(&self, mut touch_pos: Point) -> bool {
  793. t!("handle_touch_end({touch_pos:?})");
  794. let atom = &mut PropertyAtomicGuard::new();
  795. self.abs_to_local(&mut touch_pos);
  796. let state = self.touch_info.lock().stop();
  797. match state {
  798. TouchStateAction::Inactive => return false,
  799. TouchStateAction::Started { pos: _, instant: _ } | TouchStateAction::SetCursorPos => {
  800. self.touch_set_cursor_pos(touch_pos, atom).await;
  801. self.redraw().await;
  802. }
  803. _ => {}
  804. }
  805. let node = self.node.upgrade().unwrap();
  806. node.trigger("keyboard_request", vec![]).await.unwrap();
  807. true
  808. }
  809. async fn touch_set_cursor_pos(&self, mut touch_pos: Point, atom: &mut PropertyAtomicGuard) {
  810. t!("touch_set_cursor_pos({touch_pos:?})");
  811. let mut editor = self.editor.lock().await;
  812. editor.move_to_pos(touch_pos);
  813. editor.refresh(atom).await;
  814. drop(editor);
  815. self.pause_blinking();
  816. self.hide_cursor.store(false, Ordering::Relaxed);
  817. //let layout = editor.layout();
  818. //let cursor = parley::Cursor::from_point(layout, touch_pos.x, touch_pos.y);
  819. //drop(editor);
  820. //let cursor_idx = cursor.index();
  821. //let buffer = crate::android::get_raw_text().unwrap();
  822. //let cursor_clsr = byte_to_char16_index(&buffer, cursor_idx).unwrap();
  823. //crate::android::set_cursor_pos(cursor_clsr as i32);
  824. //t!(" {cursor_idx} => {cursor_clsr}");
  825. /*
  826. // This is my own type that contains the editor and contexts.
  827. let mut editor = self.editor.lock();
  828. t!(" editor (before): {editor:?}");
  829. //if editor.editor.is_composing() {
  830. // let mut drv = editor.driver();
  831. // // commit the existing compose text
  832. // drv.finish_compose();
  833. // t!(" editor (finish_compose): {editor:?}");
  834. //}
  835. let mut drv = editor.driver();
  836. drv.move_to_point(touch_pos.x, touch_pos.y);
  837. let focus = editor.editor.raw_selection().focus();
  838. let layout = editor.layout();
  839. let idx = focus.index();
  840. // Android uses chars, so we have to convert the byte index to a char index
  841. let buffer = editor.editor.raw_text();
  842. let cursor_idx = byte_to_char16_index(buffer, idx).unwrap();
  843. t!(" idx = {idx} => cursor_idx = {cursor_idx}");
  844. crate::android::set_cursor_pos(cursor_idx as i32);
  845. t!(" editor (after): {editor:?}");
  846. t!(" editable: {}", crate::android::get_debug_editable());
  847. */
  848. // OLD
  849. /*
  850. let last_suggest_text = std::mem::take(&mut editor.last_suggest_text);
  851. let is_composing = editor.editor.is_composing();
  852. if is_composing {
  853. let mut drv = editor.driver();
  854. t!("clear and commit compose: {last_suggest_text}");
  855. // commit the existing compose text
  856. drv.clear_compose();
  857. drv.insert_or_replace_selection(&last_suggest_text);
  858. t!(" text: '{}'", editor.editor.raw_text());
  859. }
  860. let mut drv = editor.driver();
  861. // We move the cursor here first so we can get the offset within the composing word
  862. drv.move_to_point(touch_pos.x, touch_pos.y);
  863. drop(drv);
  864. let focus = editor.editor.raw_selection().focus();
  865. let layout = editor.layout();
  866. let start = focus.previous_logical_word(layout).index();
  867. let curr_idx = focus.index();
  868. assert!(curr_idx >= start);
  869. let curr_off = curr_idx - start;
  870. let txt = editor.editor.raw_text();
  871. t!("{}[{}|{}", &txt[..start], &txt[start..curr_idx], &txt[curr_idx..]);
  872. t!("start={start}, curr_idx={curr_idx}");
  873. let mut drv = editor.driver();
  874. // Move the word under the cursor to the composer
  875. drv.select_word_at_point(touch_pos.x, touch_pos.y);
  876. let current_word = drv.editor.selected_text().unwrap().to_string();
  877. drv.delete_selection();
  878. t!("selected word: '{}|{}'", &current_word[..curr_off], &current_word[curr_off..]);
  879. drv.set_compose(&current_word, Some((curr_off, curr_off)));
  880. drop(drv);
  881. let comp = editor.editor.compose().clone().unwrap();
  882. let txt = editor.editor.raw_text();
  883. let (pre, rest) = txt.split_at(comp.start);
  884. let (cmp, post) = rest.split_at(comp.end - comp.start);
  885. t!("after compose: '{}[{}]{}'", pre, cmp, post);
  886. // This will then trigger handlers that call drv.set_compose(...)
  887. crate::android::set_compose(&current_word, curr_off as i32);
  888. //crate::android::set_compose(&"", 0);
  889. editor.last_suggest_text = current_word;
  890. */
  891. /*
  892. let width = self.wrap_width();
  893. {
  894. let mut text_wrap = self.text_wrap.lock();
  895. let cursor_pos = text_wrap.set_cursor_with_point(touch_pos, width);
  896. self.update_cursor_pos(&mut text_wrap, atom);
  897. let select = &mut text_wrap.select;
  898. let select_is_empty = select.is_empty();
  899. select.clear();
  900. if !select_is_empty {
  901. self.update_select_text(&mut text_wrap, atom);
  902. }
  903. }
  904. self.is_phone_select.store(false, Ordering::Relaxed);
  905. // Reshow cursor (if hidden)
  906. self.pause_blinking();
  907. self.hide_cursor.store(false, Ordering::Relaxed);
  908. self.redraw().await;
  909. */
  910. }
  911. /// Whenever the cursor property is modified this MUST be called
  912. /// to recalculate the scroll x property.
  913. fn apply_cursor_scrolling(&self, atom: &mut PropertyAtomicGuard) {
  914. /*
  915. let rect = self.rect.get();
  916. let cursor_pos = self.cursor_pos.get() as usize;
  917. let cursor_width = self.cursor_width.get();
  918. let mut scroll = self.scroll.get();
  919. let cursor_x = {
  920. let font_size = self.font_size.get();
  921. let window_scale = self.window_scale.get();
  922. let baseline = self.baseline.get();
  923. let glyphs = self.glyphs.lock().clone();
  924. let mut glyph_pos_iter =
  925. GlyphPositionIter::new(font_size, window_scale, &glyphs, baseline);
  926. if cursor_pos == 0 {
  927. 0.
  928. } else if cursor_pos == glyphs.len() {
  929. let glyph_pos = glyph_pos_iter.last().unwrap();
  930. let rhs = glyph_pos.rhs() + eol_nudge(font_size, &glyphs);
  931. rhs
  932. } else {
  933. assert!(cursor_pos < glyphs.len());
  934. let glyph_pos = glyph_pos_iter.nth(cursor_pos).expect("glyph pos mismatch glyphs");
  935. glyph_pos.x
  936. }
  937. };
  938. // The LHS and RHS of the cursor box
  939. let cursor_lhs = cursor_x - scroll;
  940. let cursor_rhs = cursor_lhs + cursor_width;
  941. // RHS is outside
  942. if cursor_rhs > rect.w {
  943. // We want a scroll so RHS = w
  944. // cursor_x - scroll + CURSOR_WIDTH = rect.w
  945. scroll = cursor_x + cursor_width - rect.w;
  946. // LHS is negative
  947. } else if cursor_lhs < 0. {
  948. // We want scroll so LHS = 0
  949. // cursor_x - scroll = 0
  950. scroll = cursor_x;
  951. }
  952. self.scroll.set(atom, scroll);
  953. */
  954. }
  955. /*
  956. fn max_scroll(&self, text_wrap: &mut TextWrap) -> f32 {
  957. let width = self.wrap_width();
  958. let mut inner_height = text_wrap.wrap(width).height();
  959. // Top padding
  960. inner_height += self.padding.get_f32(0).unwrap();
  961. // Bottom padding
  962. inner_height += self.padding.get_f32(1).unwrap();
  963. let max_height = self.max_height.get();
  964. if inner_height < max_height {
  965. return 0.
  966. }
  967. let max_scroll = inner_height - max_height;
  968. max_scroll.clamp(0., f32::MAX)
  969. }
  970. */
  971. /// When we resize the screen, the rect changes so we may need to alter the scroll.
  972. /// Or if we delete text.
  973. /*
  974. fn clamp_scroll(&self, text_wrap: &mut TextWrap, atom: &mut PropertyAtomicGuard) {
  975. let max_scroll = self.max_scroll(text_wrap);
  976. let mut scroll = self.scroll.get();
  977. if scroll > max_scroll {
  978. self.scroll.set(atom, max_scroll);
  979. }
  980. }
  981. */
  982. fn pause_blinking(&self) {
  983. self.blink_is_paused.store(true, Ordering::Relaxed);
  984. self.cursor_is_visible.store(true, Ordering::Relaxed);
  985. }
  986. async fn redraw(&self) {
  987. let atom = &mut PropertyAtomicGuard::new();
  988. let trace_id = rand::random();
  989. let timest = unixtime();
  990. t!("redraw()");
  991. let Some(draw_update) = self.make_draw_calls(trace_id, atom).await else {
  992. error!(target: "ui::chatedit", "Text failed to draw");
  993. return
  994. };
  995. self.render_api.replace_draw_calls(timest, draw_update.draw_calls);
  996. }
  997. async fn redraw_cursor(&self) {
  998. let timest = unixtime();
  999. let cursor_instrs = self.get_cursor_instrs().await;
  1000. let draw_calls = vec![(
  1001. self.cursor_dc_key,
  1002. GfxDrawCall { instrs: cursor_instrs, dcs: vec![], z_index: self.z_index.get() },
  1003. )];
  1004. self.render_api.replace_draw_calls(timest, draw_calls);
  1005. }
  1006. async fn get_cursor_instrs(&self) -> Vec<GfxDrawInstruction> {
  1007. if !self.is_focused.get() ||
  1008. !self.cursor_is_visible.load(Ordering::Relaxed) ||
  1009. self.hide_cursor.load(Ordering::Relaxed)
  1010. {
  1011. return vec![]
  1012. }
  1013. let lineheight = self.lineheight.get();
  1014. let mut cursor_instrs = vec![];
  1015. let Some(mut cursor_pos) = self.editor.lock().await.get_cursor_pos() else { return vec![] };
  1016. cursor_pos.y += self.padding_top();
  1017. cursor_instrs.push(GfxDrawInstruction::Move(cursor_pos));
  1018. let cursor_mesh = {
  1019. let mut cursor_mesh = self.cursor_mesh.lock();
  1020. if cursor_mesh.is_none() {
  1021. *cursor_mesh = Some(self.regen_cursor_mesh());
  1022. }
  1023. cursor_mesh.clone().unwrap()
  1024. };
  1025. cursor_instrs.push(GfxDrawInstruction::Draw(cursor_mesh));
  1026. cursor_instrs
  1027. }
  1028. async fn regen_txt_mesh(&self) -> Vec<GfxDrawInstruction> {
  1029. let font_size = self.font_size.get();
  1030. let text_color = self.text_color.get();
  1031. let window_scale = self.window_scale.get();
  1032. let lineheight = self.lineheight.get();
  1033. let padding_top = self.padding_top();
  1034. let padding_bottom = self.padding_bottom();
  1035. let mut instrs = vec![];
  1036. // BUG FIXME SHOULDNT NEED THIS!
  1037. instrs.push(GfxDrawInstruction::Move(Point::zero()));
  1038. if self.debug.get() {
  1039. let mut rect = self.rect.get();
  1040. rect.x = 0.;
  1041. rect.y = 0.;
  1042. let mut mesh = MeshBuilder::new();
  1043. mesh.draw_outline(&rect, [0., 1., 0., 1.], 1.);
  1044. instrs.push(GfxDrawInstruction::Draw(mesh.alloc(&self.render_api).draw_untextured()));
  1045. rect.y = padding_top;
  1046. rect.h -= padding_top + padding_bottom;
  1047. let mut mesh = MeshBuilder::new();
  1048. mesh.draw_outline(&rect, [0., 1., 0., 0.5], 1.);
  1049. instrs.push(GfxDrawInstruction::Draw(mesh.alloc(&self.render_api).draw_untextured()));
  1050. }
  1051. let mut inner_pos = Point::zero();
  1052. inner_pos.y += padding_top;
  1053. inner_pos.y -= self.scroll.get();
  1054. instrs.push(GfxDrawInstruction::Move(inner_pos));
  1055. let editor = self.editor.lock().await;
  1056. let layout = editor.layout();
  1057. let sel = editor.selection(1);
  1058. let sel_color = self.hi_bg_color.get();
  1059. if !sel.is_collapsed() {
  1060. let mut first_pos = None;
  1061. let mut last_pos = Default::default();
  1062. let mut mesh = MeshBuilder::new();
  1063. sel.geometry_with(layout, |rect: parley::Rect, _| {
  1064. let rect = Rectangle::from(rect);
  1065. if first_pos.is_none() {
  1066. first_pos = Some(rect.pos());
  1067. }
  1068. last_pos = rect.top_right();
  1069. mesh.draw_filled_box(&rect, sel_color);
  1070. });
  1071. if self.is_phone_select.load(Ordering::Relaxed) {
  1072. self.draw_phone_select_handle(&mut mesh, first_pos.unwrap(), -1.);
  1073. self.draw_phone_select_handle(&mut mesh, last_pos, 1.);
  1074. }
  1075. instrs.push(GfxDrawInstruction::Draw(mesh.alloc(&self.render_api).draw_untextured()));
  1076. }
  1077. let mut render_instrs = text2::render_layout(layout, &self.render_api);
  1078. instrs.append(&mut render_instrs);
  1079. instrs
  1080. }
  1081. fn bounded_height(&self, mut height: f32) -> f32 {
  1082. let min_height = self.min_height.get();
  1083. let max_height = self.max_height.get();
  1084. if height < min_height {
  1085. height = min_height;
  1086. }
  1087. if height > max_height {
  1088. height = max_height;
  1089. }
  1090. height
  1091. }
  1092. async fn eval_rect(&self, atom: &mut PropertyAtomicGuard) {
  1093. let parent_rect = self.parent_rect.lock().clone().unwrap();
  1094. // First we evaluate the width based off the parent dimensions
  1095. self.rect
  1096. .eval_with(
  1097. vec![2],
  1098. vec![
  1099. ("parent_w".to_string(), parent_rect.w),
  1100. ("parent_h".to_string(), parent_rect.h),
  1101. ],
  1102. )
  1103. .unwrap();
  1104. // Use the width to adjust the height calcs
  1105. let rect_w = self.rect.get_width();
  1106. let inner_height = {
  1107. let mut editor = self.editor.lock().await;
  1108. editor.set_width(rect_w);
  1109. editor.refresh(atom).await;
  1110. editor.height()
  1111. };
  1112. let content_height = inner_height + self.padding_top() + self.padding_bottom();
  1113. let rect_h = self.bounded_height(content_height);
  1114. self.rect.prop().set_f32(atom, Role::Internal, 3, rect_h);
  1115. // Finally calculate the position
  1116. self.rect
  1117. .eval_with(
  1118. vec![0, 1],
  1119. vec![
  1120. ("parent_w".to_string(), parent_rect.w),
  1121. ("parent_h".to_string(), parent_rect.h),
  1122. ("rect_w".to_string(), rect_w),
  1123. ("rect_h".to_string(), rect_h),
  1124. ],
  1125. )
  1126. .unwrap();
  1127. }
  1128. async fn make_draw_calls(
  1129. &self,
  1130. trace_id: u32,
  1131. atom: &mut PropertyAtomicGuard,
  1132. ) -> Option<DrawUpdate> {
  1133. self.eval_rect(atom).await;
  1134. let rect = self.rect.get();
  1135. let cursor_instrs = self.get_cursor_instrs().await;
  1136. let txt_instrs = self.regen_txt_mesh().await;
  1137. Some(DrawUpdate {
  1138. key: self.main_dc_key,
  1139. draw_calls: vec![
  1140. (
  1141. self.main_dc_key,
  1142. GfxDrawCall {
  1143. instrs: vec![GfxDrawInstruction::ApplyView(rect)],
  1144. dcs: vec![self.text_dc_key, self.cursor_dc_key],
  1145. z_index: self.z_index.get(),
  1146. },
  1147. ),
  1148. (
  1149. self.text_dc_key,
  1150. GfxDrawCall { instrs: txt_instrs, dcs: vec![], z_index: self.z_index.get() },
  1151. ),
  1152. (
  1153. self.cursor_dc_key,
  1154. GfxDrawCall { instrs: cursor_instrs, dcs: vec![], z_index: self.z_index.get() },
  1155. ),
  1156. ],
  1157. })
  1158. }
  1159. async fn process_insert_text_method(me: &Weak<Self>, sub: &MethodCallSub) -> bool {
  1160. let Ok(method_call) = sub.receive().await else {
  1161. debug!(target: "ui::chatedit", "Event relayer closed");
  1162. return false
  1163. };
  1164. t!("method called: insert_line({method_call:?})");
  1165. assert!(method_call.send_res.is_none());
  1166. fn decode_data(data: &[u8]) -> std::io::Result<String> {
  1167. let mut cur = Cursor::new(&data);
  1168. let text = String::decode(&mut cur)?;
  1169. Ok(text)
  1170. }
  1171. let Ok(text) = decode_data(&method_call.data) else {
  1172. error!(target: "ui::chatedit", "insert_text() method invalid arg data");
  1173. return true
  1174. };
  1175. let Some(self_) = me.upgrade() else {
  1176. // Should not happen
  1177. panic!("self destroyed before insert_text_method_task was stopped!");
  1178. };
  1179. let atom = &mut PropertyAtomicGuard::new();
  1180. //self_.insert_text(&text, atom).await;
  1181. true
  1182. }
  1183. async fn handle_android_event(&self, ev: AndroidSuggestEvent) {
  1184. t!("handle_android_event({ev:?})");
  1185. if !self.is_active.get() {
  1186. return
  1187. }
  1188. let mut editor = self.editor.lock().await;
  1189. match ev {
  1190. AndroidSuggestEvent::Init => {
  1191. editor.init();
  1192. return
  1193. }
  1194. AndroidSuggestEvent::CreateInputConnect => editor.setup(),
  1195. _ => {} //editor.update(text.clone(), select_start, select_end, compose_start, compose_end),
  1196. }
  1197. let atom = &mut PropertyAtomicGuard::new();
  1198. editor.refresh(atom).await;
  1199. drop(editor);
  1200. self.redraw().await;
  1201. }
  1202. }
  1203. impl Drop for ChatEdit {
  1204. fn drop(&mut self) {
  1205. self.render_api
  1206. .replace_draw_calls(unixtime(), vec![(self.text_dc_key, Default::default())]);
  1207. }
  1208. }
  1209. #[async_trait]
  1210. impl UIObject for ChatEdit {
  1211. fn priority(&self) -> u32 {
  1212. self.priority.get()
  1213. }
  1214. async fn start(self: Arc<Self>, ex: ExecutorPtr) {
  1215. let me = Arc::downgrade(&self);
  1216. let node_ref = &self.node.upgrade().unwrap();
  1217. let method_sub = node_ref.subscribe_method_call("insert_text").unwrap();
  1218. let me2 = me.clone();
  1219. let insert_text_task =
  1220. ex.spawn(
  1221. async move { while Self::process_insert_text_method(&me2, &method_sub).await {} },
  1222. );
  1223. let mut on_modify = OnModify::new(ex.clone(), self.node.clone(), me.clone());
  1224. on_modify.when_change(self.is_focused.prop(), Self::change_focus);
  1225. // When text has been changed.
  1226. // Cursor and selection might be invalidated.
  1227. async fn reset(self_: Arc<ChatEdit>) {
  1228. let atom = &mut PropertyAtomicGuard::new();
  1229. //self_.select_text.set_null(Role::Internal, 0).unwrap();
  1230. self_.scroll.set(atom, 0.);
  1231. self_.redraw().await;
  1232. }
  1233. async fn redraw(self_: Arc<ChatEdit>) {
  1234. self_.redraw().await;
  1235. }
  1236. async fn set_text(self_: Arc<ChatEdit>) {
  1237. {
  1238. let text = self_.text.get();
  1239. //let mut text_wrap = self_.text_wrap.lock();
  1240. //text_wrap.editable.end_compose();
  1241. //text_wrap.editable.set_text(text, String::new());
  1242. //text_wrap.clear_cache();
  1243. //let select = &mut text_wrap.select;
  1244. //select.clear();
  1245. }
  1246. self_.redraw().await;
  1247. }
  1248. on_modify.when_change(self.rect.prop(), redraw);
  1249. on_modify.when_change(self.baseline.prop(), redraw);
  1250. on_modify.when_change(self.linespacing.prop(), redraw);
  1251. on_modify.when_change(self.lineheight.prop(), redraw);
  1252. on_modify.when_change(self.select_ascent.prop(), redraw);
  1253. on_modify.when_change(self.select_descent.prop(), redraw);
  1254. on_modify.when_change(self.handle_descent.prop(), redraw);
  1255. on_modify.when_change(self.padding.clone(), redraw);
  1256. on_modify.when_change(self.text.prop(), set_text);
  1257. // The commented properties are modified on input events
  1258. // So then redraw() will get repeatedly triggered when these properties
  1259. // are changed. We should find a solution. For now the hooks are disabled.
  1260. //on_modify.when_change(scroll.prop(), redraw);
  1261. //on_modify.when_change(cursor_pos.prop(), redraw);
  1262. on_modify.when_change(self.font_size.prop(), redraw);
  1263. // We must also reshape text
  1264. on_modify.when_change(self.text.prop(), reset);
  1265. on_modify.when_change(self.text_color.prop(), redraw);
  1266. on_modify.when_change(self.hi_bg_color.prop(), redraw);
  1267. //on_modify.when_change(selected.clone(), redraw);
  1268. on_modify.when_change(self.z_index.prop(), redraw);
  1269. on_modify.when_change(self.debug.prop(), redraw);
  1270. async fn regen_cursor(self_: Arc<ChatEdit>) {
  1271. let mesh = std::mem::take(&mut *self_.cursor_mesh.lock());
  1272. }
  1273. on_modify.when_change(self.cursor_color.prop(), regen_cursor);
  1274. on_modify.when_change(self.cursor_ascent.prop(), regen_cursor);
  1275. on_modify.when_change(self.cursor_descent.prop(), regen_cursor);
  1276. on_modify.when_change(self.cursor_width.prop(), regen_cursor);
  1277. let me2 = me.clone();
  1278. let cursor_blink_time = self.cursor_blink_time.clone();
  1279. let cursor_idle_time = self.cursor_idle_time.clone();
  1280. let blinking_cursor_task = ex.spawn(async move {
  1281. loop {
  1282. msleep(cursor_blink_time.get() as u64).await;
  1283. let self_ = me2.upgrade().unwrap();
  1284. if self_.blink_is_paused.swap(false, Ordering::Relaxed) {
  1285. msleep(cursor_idle_time.get() as u64).await;
  1286. continue
  1287. }
  1288. if !self_.rect.has_cached() {
  1289. continue
  1290. }
  1291. // Invert the bool
  1292. self_.cursor_is_visible.fetch_not(Ordering::Relaxed);
  1293. self_.redraw_cursor().await;
  1294. }
  1295. });
  1296. let mut tasks = vec![insert_text_task, blinking_cursor_task];
  1297. tasks.append(&mut on_modify.tasks);
  1298. #[cfg(target_os = "android")]
  1299. {
  1300. let (sender, recvr) = async_channel::unbounded();
  1301. let composer_id = crate::android::create_composer(sender);
  1302. self.editor.lock().await.composer_id = composer_id;
  1303. t!("Created composer [{composer_id}]");
  1304. let me2 = me.clone();
  1305. let autosuggest_task = ex.spawn(async move {
  1306. loop {
  1307. let Ok(ev) = recvr.recv().await else {
  1308. t!("Event relayer closed");
  1309. break
  1310. };
  1311. let Some(self_) = me2.upgrade() else {
  1312. // Should not happen
  1313. panic!("self destroyed before autosuggest_task was stopped!");
  1314. };
  1315. self_.handle_android_event(ev).await;
  1316. }
  1317. });
  1318. tasks.push(autosuggest_task);
  1319. }
  1320. self.tasks.set(tasks);
  1321. }
  1322. async fn draw(
  1323. &self,
  1324. parent_rect: Rectangle,
  1325. trace_id: u32,
  1326. atom: &mut PropertyAtomicGuard,
  1327. ) -> Option<DrawUpdate> {
  1328. t!("ChatEdit::draw({:?}, {trace_id})", self.node.upgrade().unwrap());
  1329. *self.parent_rect.lock() = Some(parent_rect);
  1330. self.make_draw_calls(trace_id, atom).await
  1331. }
  1332. async fn handle_char(&self, key: char, mods: KeyMods, repeat: bool) -> bool {
  1333. t!("handle_char({key}, {mods:?}, {repeat})");
  1334. // First filter for only single digit keys
  1335. if DISALLOWED_CHARS.contains(&key) {
  1336. return false
  1337. }
  1338. if !self.is_focused.get() {
  1339. return false
  1340. }
  1341. // Must be updated before checking the mods. You can press ctrl+a, then release ctrl
  1342. // before a is released. Then the repeater never gets reset, and uses any old value
  1343. // it has from before for a.
  1344. let actions = {
  1345. let mut repeater = self.key_repeat.lock();
  1346. repeater.key_down(PressedKey::Char(key), repeat)
  1347. };
  1348. let atom = &mut PropertyAtomicGuard::new();
  1349. if mods.ctrl || mods.alt || mods.logo {
  1350. if repeat {
  1351. return false
  1352. }
  1353. return self.handle_shortcut(key, &mods, atom).await
  1354. }
  1355. let atom = &mut PropertyAtomicGuard::new();
  1356. t!("Key {:?} has {} actions", key, actions);
  1357. for _ in 0..actions {
  1358. self.insert_char(key).await;
  1359. }
  1360. self.redraw().await;
  1361. true
  1362. }
  1363. async fn handle_key_down(&self, key: KeyCode, mods: KeyMods, repeat: bool) -> bool {
  1364. t!("handle_key_down({key:?}, {mods:?}, {repeat})");
  1365. // First filter for only single digit keys
  1366. // Avoid processing events handled by insert_char()
  1367. if !ALLOWED_KEYCODES.contains(&key) {
  1368. return false
  1369. }
  1370. if !self.is_focused.get() {
  1371. return false
  1372. }
  1373. let actions = {
  1374. let mut repeater = self.key_repeat.lock();
  1375. repeater.key_down(PressedKey::Key(key), repeat)
  1376. };
  1377. // Suppress noisy message
  1378. if actions > 0 {
  1379. t!("Key {:?} has {} actions", key, actions);
  1380. }
  1381. let atom = &mut PropertyAtomicGuard::new();
  1382. let mut is_handled = false;
  1383. for _ in 0..actions {
  1384. if self.handle_key(&key, &mods, atom).await {
  1385. is_handled = true;
  1386. }
  1387. }
  1388. is_handled
  1389. }
  1390. async fn handle_mouse_btn_down(&self, btn: MouseButton, mut mouse_pos: Point) -> bool {
  1391. if !self.is_active.get() {
  1392. return false
  1393. }
  1394. let rect = self.rect.get();
  1395. if btn != MouseButton::Left {
  1396. if btn == MouseButton::Right && rect.contains(mouse_pos) {
  1397. if self.text.get().is_empty() {
  1398. let node = self.node.upgrade().unwrap();
  1399. node.trigger("paste_request", vec![]).await.unwrap();
  1400. }
  1401. return true
  1402. }
  1403. return false
  1404. }
  1405. if !rect.contains(mouse_pos) {
  1406. return false
  1407. }
  1408. let atom = &mut PropertyAtomicGuard::new();
  1409. // clicking inside box will:
  1410. // 1. make it active
  1411. // 2. begin selection
  1412. if self.is_focused.get() {
  1413. d!("ChatEdit clicked");
  1414. } else {
  1415. d!("ChatEdit focused");
  1416. self.is_focused.set(atom, true);
  1417. }
  1418. // Move mouse pos within this widget
  1419. self.abs_to_local(&mut mouse_pos);
  1420. {
  1421. let mut txt_ctx = text2::TEXT_CTX.get().await;
  1422. let mut editor = self.editor.lock().await;
  1423. let mut drv = editor.driver(&mut txt_ctx).await.unwrap();
  1424. drv.move_to_point(mouse_pos.x, mouse_pos.y);
  1425. }
  1426. if !self.select_text.is_null(0).unwrap() {
  1427. self.select_text.clone().set_null(atom, Role::Internal, 0).unwrap();
  1428. }
  1429. self.mouse_btn_held.store(true, Ordering::Relaxed);
  1430. self.pause_blinking();
  1431. self.redraw().await;
  1432. true
  1433. }
  1434. async fn handle_mouse_btn_up(&self, btn: MouseButton, mouse_pos: Point) -> bool {
  1435. if !self.is_active.get() {
  1436. return false
  1437. }
  1438. // releasing mouse button will end selection
  1439. self.mouse_btn_held.store(false, Ordering::Relaxed);
  1440. false
  1441. }
  1442. async fn handle_mouse_move(&self, mut mouse_pos: Point) -> bool {
  1443. if !self.is_active.get() {
  1444. return false
  1445. }
  1446. let rect = self.rect.get();
  1447. self.is_mouse_hover.store(rect.contains(mouse_pos), Ordering::Relaxed);
  1448. if !self.mouse_btn_held.load(Ordering::Relaxed) {
  1449. return false
  1450. }
  1451. let atom = &mut PropertyAtomicGuard::new();
  1452. // if active and selection_active, then use x to modify the selection.
  1453. // also implement scrolling when cursor is to the left or right
  1454. // just scroll to the end
  1455. // also set cursor_pos too
  1456. // Move mouse pos within this widget
  1457. self.abs_to_local(&mut mouse_pos);
  1458. let seltext = {
  1459. let mut txt_ctx = text2::TEXT_CTX.get().await;
  1460. let mut editor = self.editor.lock().await;
  1461. let mut drv = editor.driver(&mut txt_ctx).await.unwrap();
  1462. drv.extend_selection_to_point(mouse_pos.x, mouse_pos.y);
  1463. editor.selected_text()
  1464. };
  1465. d!("Select {seltext:?} from {mouse_pos:?}");
  1466. // Will be None when drag select just started
  1467. if let Some(seltext) = seltext {
  1468. self.select_text.clone().set_str(atom, Role::Internal, 0, seltext).unwrap();
  1469. }
  1470. self.pause_blinking();
  1471. //self.apply_cursor_scrolling();
  1472. self.redraw().await;
  1473. true
  1474. }
  1475. async fn handle_mouse_wheel(&self, wheel_pos: Point) -> bool {
  1476. if !self.is_mouse_hover.load(Ordering::Relaxed) {
  1477. return false
  1478. }
  1479. let atom = &mut PropertyAtomicGuard::new();
  1480. /*
  1481. let max_scroll = {
  1482. let mut text_wrap = self.text_wrap.lock();
  1483. self.max_scroll(&mut text_wrap)
  1484. };
  1485. let mut scroll = self.scroll.get() - wheel_pos.y * self.scroll_speed.get();
  1486. scroll = scroll.clamp(0., max_scroll);
  1487. t!("handle_mouse_wheel({wheel_pos:?}) [scroll={scroll}]");
  1488. self.scroll.set(atom, scroll);
  1489. self.redraw().await;
  1490. */
  1491. true
  1492. }
  1493. async fn handle_touch(&self, phase: TouchPhase, id: u64, touch_pos: Point) -> bool {
  1494. if !self.is_active.get() {
  1495. return false
  1496. }
  1497. // Ignore multi-touch
  1498. if id != 0 {
  1499. return false
  1500. }
  1501. match phase {
  1502. TouchPhase::Started => self.handle_touch_start(touch_pos).await,
  1503. TouchPhase::Moved => self.handle_touch_move(touch_pos).await,
  1504. TouchPhase::Ended => self.handle_touch_end(touch_pos).await,
  1505. TouchPhase::Cancelled => false,
  1506. }
  1507. }
  1508. }