chatedit.rs 59 KB

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