chatedit.rs 61 KB

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