mod.rs 76 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2026 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 super::long_press_timeout;
  19. use async_trait::async_trait;
  20. use atomic_float::AtomicF32;
  21. use darkfi::system::msleep;
  22. use darkfi_serial::Decodable;
  23. use futures::FutureExt;
  24. use miniquad::{KeyCode, KeyMods, MouseButton, TouchPhase};
  25. use parking_lot::Mutex as SyncMutex;
  26. use rand::{rngs::OsRng, Rng};
  27. use std::{
  28. io::Cursor,
  29. mem::swap,
  30. sync::{
  31. atomic::{AtomicBool, Ordering},
  32. Arc, Weak,
  33. },
  34. };
  35. use tracing::instrument;
  36. #[cfg(target_os = "android")]
  37. use crate::android::{is_ime_visible, textinput::AndroidTextInputState};
  38. use crate::{
  39. clipboard,
  40. gfx::{
  41. anim::Frame as AnimFrame, gfxtag, DrawCall, DrawInstruction, DrawMesh, ManagedSeqAnimPtr,
  42. Point, Rectangle, RenderApi, Renderer, Vertex,
  43. },
  44. mesh::MeshBuilder,
  45. prop::{
  46. BatchGuardPtr, PropertyAtomicGuard, PropertyBool, PropertyColor, PropertyFloat32,
  47. PropertyPtr, PropertyRect, PropertyStr, PropertyUint32, Role,
  48. },
  49. scene::{MethodCallSub, Pimpl, SceneNodePtr, SceneNodeWeak},
  50. text::{self, Editor},
  51. ExecutorPtr,
  52. };
  53. const ACTION_COPY: u32 = 0;
  54. const ACTION_PASTE: u32 = 1;
  55. const ACTION_SELALL: u32 = 2;
  56. use super::{DrawUpdate, OnModify, RedrawTrigger, UIObject};
  57. mod action;
  58. mod filter;
  59. use filter::{ALLOWED_KEYCODES, DISALLOWED_CHARS};
  60. mod behave;
  61. pub use behave::BaseEditType;
  62. use behave::{EditorBehavior, MultiLine, ScrollDir, SingleLine};
  63. mod repeat;
  64. use repeat::{PressedKey, PressedKeysSmoothRepeat};
  65. /// The travel threshold on long hold select before activating select.
  66. const HOLD_TRAVEL_THRESHOLD_SQ: f32 = 100.;
  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. /// How often to update the scrolling selection with mouse.
  71. const SELECT_TASK_UPDATE_TIME: u64 = 500;
  72. // Should be a property
  73. const SELECT_SCROLL_TRAVEL_SPEED: f32 = 1.;
  74. macro_rules! d { ($($arg:tt)*) => { debug!(target: "ui::edit", $($arg)*); } }
  75. macro_rules! t { ($($arg:tt)*) => { trace!(target: "ui::edit", $($arg)*); } }
  76. /// Master switch for verbose edit/IME debugging.
  77. /// When true, detailed diagnostics about text content, selection range,
  78. /// compose region, focus state, and touch/keyboard events are emitted to
  79. /// the `ui::edit` log target. Set to false to silence them.
  80. const ENABLE_DEBUG: bool = true;
  81. macro_rules! ed {
  82. ($($arg:tt)*) => {
  83. if ENABLE_DEBUG {
  84. d!($($arg)*);
  85. }
  86. };
  87. }
  88. #[derive(Debug, Clone)]
  89. enum TouchStateAction {
  90. Inactive,
  91. Started { pos: Point, instant: std::time::Instant },
  92. StartSelect,
  93. Select,
  94. DragSelectHandle { side: isize },
  95. ScrollVert { start_pos: Point, scroll_start: f32 },
  96. SetCursorPos,
  97. }
  98. struct TouchInfo {
  99. state: TouchStateAction,
  100. scroll: Arc<AtomicF32>,
  101. scroll_ctrl: ScrollDir,
  102. }
  103. impl TouchInfo {
  104. fn new(scroll: Arc<AtomicF32>, scroll_ctrl: ScrollDir) -> Self {
  105. Self { state: TouchStateAction::Inactive, scroll, scroll_ctrl }
  106. }
  107. fn start(&mut self, pos: Point) {
  108. debug!(target: "ui::chatedit::touch", "start touch: Started state");
  109. self.state = TouchStateAction::Started { pos, instant: std::time::Instant::now() };
  110. }
  111. fn stop(&mut self) -> TouchStateAction {
  112. debug!(target: "ui::chatedit::touch", "stop touch: Inactive state");
  113. std::mem::replace(&mut self.state, TouchStateAction::Inactive)
  114. }
  115. fn update(&mut self, pos: &Point) {
  116. match &self.state {
  117. TouchStateAction::Started { pos: start_pos, instant } => {
  118. let travel_dist_sq = pos.dist_sq(*start_pos);
  119. let grad = (pos.y - start_pos.y) / (pos.x - start_pos.x);
  120. let elapsed = instant.elapsed().as_millis();
  121. //debug!(target: "ui::chatedit::touch", "TouchInfo::update() [travel_dist_sq={travel_dist_sq}, grad={grad}]");
  122. if travel_dist_sq < HOLD_TRAVEL_THRESHOLD_SQ {
  123. if elapsed > long_press_timeout() as u128 {
  124. debug!(target: "ui::chatedit::touch", "update touch state: Started -> StartSelect");
  125. self.state = TouchStateAction::StartSelect;
  126. }
  127. } else if self.scroll_ctrl.cmp(grad) {
  128. // Vertical movement
  129. debug!(target: "ui::chatedit::touch", "update touch state: Started -> ScrollVert");
  130. let scroll_start = self.scroll.load(Ordering::Relaxed);
  131. self.state =
  132. TouchStateAction::ScrollVert { start_pos: *start_pos, scroll_start };
  133. } else {
  134. // Horizontal movement
  135. debug!(target: "ui::chatedit::touch", "update touch state: Started -> SetCursorPos");
  136. self.state = TouchStateAction::SetCursorPos;
  137. }
  138. }
  139. _ => {}
  140. }
  141. }
  142. }
  143. pub type BaseEditPtr = Arc<BaseEdit>;
  144. pub struct BaseEdit {
  145. node: SceneNodeWeak,
  146. tasks: SyncMutex<Vec<smol::Task<()>>>,
  147. ex: ExecutorPtr,
  148. me: Weak<Self>,
  149. renderer: Renderer,
  150. redraw: RedrawTrigger,
  151. key_repeat: SyncMutex<PressedKeysSmoothRepeat>,
  152. // Moves the draw cursor and applies scroll
  153. root_dc_key: u64,
  154. phone_select_handle_dc_key: u64,
  155. // Applies the clipping view
  156. content_dc_key: u64,
  157. select_dc_key: u64,
  158. text_dc_key: u64,
  159. cursor_dc_key: u64,
  160. cursor_anim: SyncMutex<Option<ManagedSeqAnimPtr>>,
  161. is_active: PropertyBool,
  162. is_focused: PropertyBool,
  163. rect: PropertyRect,
  164. baseline: PropertyFloat32,
  165. lineheight: PropertyFloat32,
  166. scroll_speed: PropertyFloat32,
  167. padding: PropertyPtr,
  168. font_size: PropertyFloat32,
  169. text: PropertyStr,
  170. text_color: PropertyColor,
  171. text_hi_color: PropertyColor,
  172. placeholder_text: PropertyStr,
  173. placeholder_color: PropertyColor,
  174. //text_cmd_color: PropertyColor,
  175. cursor_color: PropertyColor,
  176. cursor_width: PropertyFloat32,
  177. cursor_ascent: PropertyFloat32,
  178. cursor_descent: PropertyFloat32,
  179. cursor_blink_time: PropertyUint32,
  180. cursor_idle_time: PropertyUint32,
  181. hi_bg_color: PropertyColor,
  182. //cmd_bg_color: PropertyColor,
  183. select_ascent: PropertyFloat32,
  184. select_descent: PropertyFloat32,
  185. handle_descent: PropertyFloat32,
  186. select_text: PropertyPtr,
  187. z_index: PropertyUint32,
  188. priority: PropertyUint32,
  189. debug: PropertyBool,
  190. android_input_type: PropertyUint32,
  191. action_fg_color: PropertyColor,
  192. action_bg_color: PropertyColor,
  193. action_padding: PropertyFloat32,
  194. action_spacing: PropertyFloat32,
  195. mouse_btn_held: AtomicBool,
  196. /// Used to explicitly hide the cursor. Must be manually re-enabled.
  197. hide_cursor: AtomicBool,
  198. /// Used to start select and scroll when mouse moves outside widget rect.
  199. sel_sender: SyncMutex<Option<async_channel::Sender<Option<(Point, Option<isize>)>>>>,
  200. scroll: Arc<AtomicF32>,
  201. touch_info: SyncMutex<TouchInfo>,
  202. is_phone_select: AtomicBool,
  203. parent_rect: Arc<SyncMutex<Option<Rectangle>>>,
  204. window_scale: PropertyFloat32,
  205. is_mouse_hover: AtomicBool,
  206. action_mode: action::ActionMode,
  207. editor: Arc<SyncMutex<Editor>>,
  208. behave: Box<dyn EditorBehavior>,
  209. }
  210. impl BaseEdit {
  211. pub async fn new(
  212. node: SceneNodeWeak,
  213. window_scale: PropertyFloat32,
  214. renderer: Renderer,
  215. redraw: RedrawTrigger,
  216. edit_type: BaseEditType,
  217. ex: ExecutorPtr,
  218. ) -> Pimpl {
  219. let node_ref = &node.upgrade().unwrap();
  220. let is_active = PropertyBool::wrap(node_ref, Role::Internal, "is_active", 0).unwrap();
  221. let is_focused = PropertyBool::wrap(node_ref, Role::Internal, "is_focused", 0).unwrap();
  222. let rect = PropertyRect::wrap(node_ref, Role::Internal, "rect").unwrap();
  223. let baseline = PropertyFloat32::wrap(node_ref, Role::Internal, "baseline", 0).unwrap();
  224. let lineheight = PropertyFloat32::wrap(node_ref, Role::Internal, "lineheight", 0).unwrap();
  225. let scroll_speed =
  226. PropertyFloat32::wrap(node_ref, Role::Internal, "scroll_speed", 0).unwrap();
  227. let padding = node_ref.get_property("padding").unwrap();
  228. let font_size = PropertyFloat32::wrap(node_ref, Role::Internal, "font_size", 0).unwrap();
  229. let text = PropertyStr::wrap(node_ref, Role::Internal, "text", 0).unwrap();
  230. let text_color = PropertyColor::wrap(node_ref, Role::Internal, "text_color").unwrap();
  231. let text_hi_color = PropertyColor::wrap(node_ref, Role::Internal, "text_hi_color").unwrap();
  232. let placeholder_text =
  233. PropertyStr::wrap(node_ref, Role::Internal, "placeholder_text", 0).unwrap();
  234. let placeholder_color =
  235. PropertyColor::wrap(node_ref, Role::Internal, "placeholder_color").unwrap();
  236. //let text_cmd_color =
  237. // PropertyColor::wrap(node_ref, Role::Internal, "text_cmd_color").unwrap();
  238. let cursor_color = PropertyColor::wrap(node_ref, Role::Internal, "cursor_color").unwrap();
  239. let cursor_width =
  240. PropertyFloat32::wrap(node_ref, Role::Internal, "cursor_width", 0).unwrap();
  241. let cursor_ascent =
  242. PropertyFloat32::wrap(node_ref, Role::Internal, "cursor_ascent", 0).unwrap();
  243. let cursor_descent =
  244. PropertyFloat32::wrap(node_ref, Role::Internal, "cursor_descent", 0).unwrap();
  245. let hi_bg_color = PropertyColor::wrap(node_ref, Role::Internal, "hi_bg_color").unwrap();
  246. //let cmd_bg_color = PropertyColor::wrap(node_ref, Role::Internal, "cmd_bg_color").unwrap();
  247. let select_ascent =
  248. PropertyFloat32::wrap(node_ref, Role::Internal, "select_ascent", 0).unwrap();
  249. let select_descent =
  250. PropertyFloat32::wrap(node_ref, Role::Internal, "select_descent", 0).unwrap();
  251. let handle_descent =
  252. PropertyFloat32::wrap(node_ref, Role::Internal, "handle_descent", 0).unwrap();
  253. let select_text = node_ref.get_property("select_text").unwrap();
  254. let cursor_blink_time =
  255. PropertyUint32::wrap(node_ref, Role::Internal, "cursor_blink_time", 0).unwrap();
  256. let cursor_idle_time =
  257. PropertyUint32::wrap(node_ref, Role::Internal, "cursor_idle_time", 0).unwrap();
  258. let z_index = PropertyUint32::wrap(node_ref, Role::Internal, "z_index", 0).unwrap();
  259. let priority = PropertyUint32::wrap(node_ref, Role::Internal, "priority", 0).unwrap();
  260. let debug = PropertyBool::wrap(node_ref, Role::Internal, "debug", 0).unwrap();
  261. let android_input_type =
  262. PropertyUint32::wrap(node_ref, Role::Internal, "android_input_type", 0).unwrap();
  263. let action_fg_color =
  264. PropertyColor::wrap(node_ref, Role::Internal, "action_fg_color").unwrap();
  265. let action_bg_color =
  266. PropertyColor::wrap(node_ref, Role::Internal, "action_bg_color").unwrap();
  267. let action_padding =
  268. PropertyFloat32::wrap(node_ref, Role::Internal, "action_padding", 0).unwrap();
  269. let action_spacing =
  270. PropertyFloat32::wrap(node_ref, Role::Internal, "action_spacing", 0).unwrap();
  271. let parent_rect = Arc::new(SyncMutex::new(None));
  272. let scroll = Arc::new(AtomicF32::new(0.));
  273. let editor = Arc::new(SyncMutex::new(Editor::new(
  274. text.clone(),
  275. font_size.clone(),
  276. text_color.clone(),
  277. window_scale.clone(),
  278. lineheight.clone(),
  279. )));
  280. let behave: Box<dyn EditorBehavior> = match edit_type {
  281. BaseEditType::SingleLine => Box::new(SingleLine {
  282. rect: rect.clone(),
  283. padding: padding.clone(),
  284. cursor_width: cursor_width.clone(),
  285. parent_rect: parent_rect.clone(),
  286. editor: editor.clone(),
  287. content_height: AtomicF32::new(0.),
  288. scroll: scroll.clone(),
  289. }),
  290. BaseEditType::MultiLine => {
  291. let min_height =
  292. PropertyFloat32::wrap(node_ref, Role::Internal, "height_range", 0).unwrap();
  293. let max_height =
  294. PropertyFloat32::wrap(node_ref, Role::Internal, "height_range", 1).unwrap();
  295. Box::new(MultiLine {
  296. min_height: min_height.clone(),
  297. max_height: max_height.clone(),
  298. rect: rect.clone(),
  299. baseline: baseline.clone(),
  300. padding: padding.clone(),
  301. cursor_descent: cursor_descent.clone(),
  302. parent_rect: parent_rect.clone(),
  303. editor: editor.clone(),
  304. content_height: AtomicF32::new(0.),
  305. scroll: scroll.clone(),
  306. })
  307. }
  308. };
  309. let action_mode = action::ActionMode::new(renderer.clone());
  310. let self_ = Arc::new_cyclic(|me| Self {
  311. node,
  312. tasks: SyncMutex::new(vec![]),
  313. ex,
  314. me: me.clone(),
  315. renderer,
  316. redraw,
  317. key_repeat: SyncMutex::new(PressedKeysSmoothRepeat::new(400, 50)),
  318. root_dc_key: OsRng.gen(),
  319. phone_select_handle_dc_key: OsRng.gen(),
  320. content_dc_key: OsRng.gen(),
  321. select_dc_key: OsRng.gen(),
  322. text_dc_key: OsRng.gen(),
  323. cursor_dc_key: OsRng.gen(),
  324. cursor_anim: SyncMutex::new(None),
  325. is_active,
  326. is_focused,
  327. rect,
  328. baseline,
  329. lineheight: lineheight.clone(),
  330. scroll_speed,
  331. padding,
  332. font_size: font_size.clone(),
  333. text: text.clone(),
  334. text_color: text_color.clone(),
  335. text_hi_color,
  336. placeholder_text,
  337. placeholder_color,
  338. //text_cmd_color,
  339. cursor_color,
  340. cursor_width,
  341. cursor_ascent,
  342. cursor_descent,
  343. cursor_blink_time,
  344. cursor_idle_time,
  345. hi_bg_color,
  346. //cmd_bg_color,
  347. select_ascent,
  348. select_descent,
  349. handle_descent,
  350. select_text,
  351. z_index,
  352. priority,
  353. debug,
  354. android_input_type,
  355. action_fg_color,
  356. action_bg_color,
  357. action_padding,
  358. action_spacing,
  359. mouse_btn_held: AtomicBool::new(false),
  360. hide_cursor: AtomicBool::new(false),
  361. sel_sender: SyncMutex::new(None),
  362. scroll: scroll.clone(),
  363. touch_info: SyncMutex::new(TouchInfo::new(scroll, behave.scroll_ctrl())),
  364. is_phone_select: AtomicBool::new(false),
  365. parent_rect,
  366. window_scale,
  367. is_mouse_hover: AtomicBool::new(false),
  368. action_mode,
  369. editor,
  370. behave,
  371. });
  372. Pimpl::Edit(self_)
  373. }
  374. #[inline]
  375. fn node(&self) -> SceneNodePtr {
  376. self.node.upgrade().unwrap()
  377. }
  378. /// Snapshot of the editor state useful for debugging editing bugs.
  379. fn dbg_state(&self) -> String {
  380. let focused = self.is_focused.get();
  381. let active = self.is_active.get();
  382. let phone_select = self.is_phone_select.load(Ordering::Relaxed);
  383. let editor = self.editor.lock();
  384. let selected = editor.selected_text();
  385. #[cfg(target_os = "android")]
  386. {
  387. format!(
  388. "text={:?} select={:?} compose={:?} selected={:?} focused={} active={} phone_select={}",
  389. editor.state.text,
  390. editor.state.select,
  391. editor.state.compose,
  392. selected,
  393. focused,
  394. active,
  395. phone_select,
  396. )
  397. }
  398. #[cfg(not(target_os = "android"))]
  399. {
  400. format!(
  401. "text={:?} selected={:?} focused={} active={} phone_select={}",
  402. self.text.get(),
  403. selected,
  404. focused,
  405. active,
  406. phone_select,
  407. )
  408. }
  409. }
  410. /// Reset the horizontal scroll to 0.
  411. /// Useful when the input width is dynamically changed and scrolling is not desired.
  412. pub fn reset_scroll(&self) {
  413. self.scroll.store(0., Ordering::Release);
  414. }
  415. /// Update the editor when the text property was changed externally.
  416. /// This should be called after setting the text property directly.
  417. pub fn on_text_prop_changed(&self) {
  418. self.editor.lock().on_text_prop_changed();
  419. }
  420. fn abs_to_local(&self, point: &mut Point) {
  421. let rect = self.rect.get();
  422. *point -= rect.pos();
  423. *point -= self.behave.inner_pos();
  424. *point -= self.behave.scroll();
  425. }
  426. /// Gets the real cursor pos within the rect.
  427. fn get_cursor_pos(&self) -> Point {
  428. // This is the position within the content.
  429. let cursor_pos = self.editor.lock().get_cursor_pos();
  430. // Apply the inner padding
  431. cursor_pos + self.behave.inner_pos()
  432. }
  433. fn regen_cursor_mesh<R: RenderApi>(&self, renderer: &R) -> DrawMesh {
  434. let cursor_width = self.cursor_width.get();
  435. let cursor_ascent = self.cursor_ascent.get();
  436. let cursor_descent = self.cursor_descent.get();
  437. let baseline = self.baseline.get();
  438. let cursor_rect = Rectangle {
  439. x: 0.,
  440. y: baseline - cursor_ascent,
  441. w: cursor_width,
  442. h: cursor_ascent + cursor_descent,
  443. };
  444. let cursor_color = self.cursor_color.get();
  445. let mut mesh = MeshBuilder::new(gfxtag!("chatedit_cursor"));
  446. mesh.draw_filled_box(&cursor_rect, cursor_color);
  447. mesh.alloc(renderer).draw_untextured()
  448. }
  449. fn draw_phone_select_handle(&self, mesh: &mut MeshBuilder, mut pos: Point, side: f32) {
  450. let baseline = self.baseline.get();
  451. let select_ascent = self.select_ascent.get();
  452. let handle_descent = self.handle_descent.get();
  453. let color = self.text_hi_color.get();
  454. // Transparent for fade
  455. let mut color_trans = color.clone();
  456. color_trans[3] = 0.;
  457. pos += self.behave.inner_pos();
  458. let x = pos.x;
  459. let mut y = pos.y + baseline;
  460. // We can cache this
  461. // Vertical line downwards. We use this instead of draw_box() so we have a fade.
  462. let verts = vec![
  463. Vertex { pos: [x - side * 1., y - select_ascent], color: color_trans, uv: [0., 0.] },
  464. Vertex { pos: [x + side * 4., y - select_ascent], color: color_trans, uv: [0., 0.] },
  465. Vertex { pos: [x - side * 1., y + handle_descent + 5.], color, uv: [0., 0.] },
  466. Vertex { pos: [x + side * 4., y + handle_descent + 5.], color, uv: [0., 0.] },
  467. ];
  468. let indices = vec![0, 2, 1, 1, 2, 3];
  469. mesh.append(verts, indices);
  470. y += handle_descent;
  471. // The arrow itself.
  472. // Go anti-clockwise
  473. let verts = vec![
  474. Vertex { pos: [x, y], color, uv: [0., 0.] },
  475. Vertex { pos: [x, y + 50.], color, uv: [0., 0.] },
  476. Vertex { pos: [x + side * 30., y + 50.], color, uv: [0., 0.] },
  477. Vertex { pos: [x + side * 50., y + 25.], color, uv: [0., 0.] },
  478. ];
  479. let indices = vec![0, 1, 2, 0, 2, 3];
  480. mesh.append(verts, indices);
  481. }
  482. async fn change_focus(self: Arc<Self>, _batch: BatchGuardPtr) {
  483. if !self.is_active.get() {
  484. ed!("change_focus: not active, skipping");
  485. return
  486. }
  487. ed!("change_focus: focused={} -> [{}]", self.is_focused.get(), self.dbg_state());
  488. t!("Focus changed");
  489. // Cursor visibility will change so just redraw everything lol
  490. self.redraw.trigger();
  491. }
  492. fn handle_shortcut(&self, key: char, mods: &KeyMods, atom: &mut PropertyAtomicGuard) -> bool {
  493. ed!("handle_shortcut({key:?}, {mods:?}) before=[{}]", self.dbg_state());
  494. t!("handle_shortcut({:?}, {:?})", key, mods);
  495. #[cfg(not(target_os = "macos"))]
  496. let action_mod = mods.ctrl;
  497. #[cfg(target_os = "macos")]
  498. let action_mod = mods.logo;
  499. match key {
  500. 'a' => {
  501. if action_mod {
  502. let mut editor = self.editor.lock();
  503. editor.driver().select_all();
  504. if let Some(seltext) = editor.selected_text() {
  505. self.select_text.clone().set_str(atom, Role::Internal, 0, seltext).unwrap();
  506. }
  507. }
  508. }
  509. 'c' => {
  510. if action_mod {
  511. let editor = self.editor.lock();
  512. if let Some(txt) = editor.selected_text() {
  513. clipboard::set(&txt);
  514. }
  515. }
  516. }
  517. 'v' => {
  518. if action_mod {
  519. if let Some(txt) = clipboard::get() {
  520. self.editor.lock().insert(&txt, atom);
  521. // Maybe insert should call this?
  522. self.behave.apply_cursor_scroll();
  523. }
  524. }
  525. }
  526. _ => return false,
  527. }
  528. // Any edit invalidates the action menu's selection
  529. self.action_mode.clear();
  530. ed!("handle_shortcut: handled key={key:?} after=[{}]", self.dbg_state());
  531. true
  532. }
  533. fn handle_key(&self, key: &KeyCode, mods: &KeyMods, atom: &mut PropertyAtomicGuard) -> bool {
  534. #[cfg(not(target_os = "macos"))]
  535. let action_mod = mods.ctrl;
  536. #[cfg(target_os = "macos")]
  537. let action_mod = mods.logo;
  538. t!("handle_key({:?}, {:?}) action_mod={action_mod}", key, mods);
  539. {
  540. let mut editor = self.editor.lock();
  541. match key {
  542. KeyCode::Left => {
  543. if action_mod {
  544. if mods.shift {
  545. editor.driver().select_word_left();
  546. } else {
  547. editor.driver().move_word_left();
  548. }
  549. } else if mods.shift {
  550. editor.driver().select_left();
  551. } else {
  552. editor.driver().move_left();
  553. }
  554. }
  555. KeyCode::Right => {
  556. if action_mod {
  557. if mods.shift {
  558. editor.driver().select_word_right();
  559. } else {
  560. editor.driver().move_word_right();
  561. }
  562. } else if mods.shift {
  563. editor.driver().select_right();
  564. } else {
  565. editor.driver().move_right();
  566. }
  567. }
  568. KeyCode::Up => {
  569. if mods.shift {
  570. editor.driver().select_up();
  571. } else {
  572. editor.driver().move_up();
  573. }
  574. }
  575. KeyCode::Down => {
  576. if mods.shift {
  577. editor.driver().select_down();
  578. } else {
  579. editor.driver().move_down();
  580. }
  581. }
  582. KeyCode::Enter | KeyCode::KpEnter => {
  583. if mods.shift {
  584. if self.behave.allow_endl() {
  585. editor.driver().insert_or_replace_selection("\n");
  586. editor.on_buffer_changed(atom);
  587. }
  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. editor.driver().delete_word();
  597. } else {
  598. editor.driver().delete();
  599. }
  600. editor.on_buffer_changed(atom);
  601. }
  602. KeyCode::Backspace => {
  603. if action_mod {
  604. editor.driver().backdelete_word();
  605. } else {
  606. editor.driver().backdelete();
  607. }
  608. editor.on_buffer_changed(atom);
  609. }
  610. KeyCode::Home => {
  611. if action_mod {
  612. if mods.shift {
  613. editor.driver().select_to_text_start();
  614. } else {
  615. editor.driver().move_to_text_start();
  616. }
  617. } else if mods.shift {
  618. editor.driver().select_to_line_start();
  619. } else {
  620. editor.driver().move_to_line_start();
  621. }
  622. }
  623. KeyCode::End => {
  624. if action_mod {
  625. if mods.shift {
  626. editor.driver().select_to_text_end();
  627. } else {
  628. editor.driver().move_to_text_end();
  629. }
  630. } else if mods.shift {
  631. editor.driver().select_to_line_end();
  632. } else {
  633. editor.driver().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. }
  644. self.behave.apply_cursor_scroll();
  645. self.pause_blinking();
  646. // Any edit invalidates the action menu's selection
  647. self.action_mode.clear();
  648. ed!("handle_key: handled {key:?} after=[{}]", self.dbg_state());
  649. true
  650. }
  651. /// This will select the entire word rather than move the cursor to that location
  652. fn start_touch_select(&self, touch_pos: Point, atom: &mut PropertyAtomicGuard) {
  653. ed!("start_touch_select({touch_pos:?}) before=[{}]", self.dbg_state());
  654. let mut editor = self.editor.lock();
  655. editor.select_word_at_point(touch_pos);
  656. editor.refresh();
  657. let seltext = editor.selected_text().unwrap();
  658. drop(editor);
  659. self.select_text.clone().set_str(atom, Role::Internal, 0, seltext).unwrap();
  660. self.is_phone_select.store(true, Ordering::Relaxed);
  661. self.hide_cursor.store(true, Ordering::Relaxed);
  662. }
  663. fn handle_touch_start(&self, touch_pos: Point) -> bool {
  664. ed!("handle_touch_start({touch_pos:?}) before=[{}]", self.dbg_state());
  665. let rect = self.rect.get();
  666. let local_pos = touch_pos - rect.pos();
  667. // Grabbing a select handle must keep the action menu visible: the
  668. // user adjusts the selection, then taps Copy/Paste. Check the drag
  669. // BEFORE interact(), which consumes the menu.
  670. if self.try_handle_drag(touch_pos) {
  671. ed!("handle_touch_start: handled by drag handle");
  672. return true
  673. }
  674. let atom = &mut self.redraw.make_guard(gfxtag!("BaseEdit::handle_touch_start_action"));
  675. if let Some(action_id) = self.action_mode.interact(local_pos) {
  676. match action_id {
  677. ACTION_COPY => {
  678. if let Some(txt) = self.editor.lock().selected_text() {
  679. clipboard::set(&txt);
  680. }
  681. }
  682. ACTION_PASTE => {
  683. if let Some(txt) = clipboard::get() {
  684. self.editor.lock().insert(&txt, atom);
  685. self.behave.apply_cursor_scroll();
  686. self.finish_select(atom);
  687. }
  688. }
  689. ACTION_SELALL => {
  690. let mut editor = self.editor.lock();
  691. editor.select_all();
  692. if let Some(seltext) = editor.selected_text() {
  693. self.select_text.clone().set_str(atom, Role::Internal, 0, seltext).unwrap();
  694. }
  695. }
  696. _ => {}
  697. }
  698. return true;
  699. } else {
  700. // interact() already consumed the menu; the pass re-emits the
  701. // (now empty) action overlay when the batch settles.
  702. }
  703. if !rect.contains(touch_pos) {
  704. ed!("handle_touch_start: outside rect={rect:?}, ignoring");
  705. t!("rect!cont rect={rect:?}, touch_pos={touch_pos:?}");
  706. return false
  707. }
  708. let mut touch_info = self.touch_info.lock();
  709. touch_info.start(touch_pos);
  710. ed!("handle_touch_start: touch started, waiting for move/end");
  711. true
  712. }
  713. fn get_select_handles(&self, editor: &Editor) -> Option<(Point, Point)> {
  714. let endpoints = editor.selection_endpoints();
  715. if endpoints.is_none() {
  716. assert!(!self.is_phone_select.load(Ordering::Relaxed));
  717. }
  718. endpoints
  719. }
  720. fn try_handle_drag(&self, mut touch_pos: Point) -> bool {
  721. let editor = self.editor.lock();
  722. let Some((mut first, mut last)) = self.get_select_handles(&editor) else { return false };
  723. self.abs_to_local(&mut touch_pos);
  724. let baseline = self.baseline.get();
  725. let handle_off_y = self.handle_descent.get();
  726. first.y += baseline + handle_off_y;
  727. last.y += baseline + handle_off_y;
  728. // Are we within range of either one?
  729. t!("handle center points = ({first:?}, {last:?})");
  730. const TOUCH_RADIUS_SQ: f32 = 10_000.;
  731. let first_dist_sq = first.dist_sq(touch_pos);
  732. let last_dist_sq = last.dist_sq(touch_pos);
  733. let is_first = first_dist_sq <= TOUCH_RADIUS_SQ;
  734. let is_last = last_dist_sq <= TOUCH_RADIUS_SQ;
  735. let mut side = 0;
  736. if is_first && is_last {
  737. // Are we closer to the first or last?
  738. // Break the tie
  739. if first_dist_sq < last_dist_sq {
  740. side = -1;
  741. } else {
  742. side = 1;
  743. }
  744. } else if is_first {
  745. side = -1;
  746. } else if is_last {
  747. side = 1;
  748. }
  749. if side != 0 {
  750. d!("start touch: DragSelectHandle state [side={side}]");
  751. // Set touch_state status to enable begin dragging them
  752. let mut touch_info = self.touch_info.lock();
  753. touch_info.state = TouchStateAction::DragSelectHandle { side };
  754. ed!("try_handle_drag: grabbing select handle side={side}");
  755. return true
  756. }
  757. ed!("try_handle_drag: no handle grabbed");
  758. false
  759. }
  760. fn handle_touch_move(&self, mut touch_pos: Point) -> bool {
  761. if !self.is_active.get() {
  762. return false
  763. }
  764. // We must update with non relative touch_pos bcos when doing vertical scrolling
  765. // we will modify the scroll, which is used by abs_to_local(), which is used
  766. // to then calculate the max scroll. So it ends up jumping around.
  767. // We use the abs touch_pos without scroll adjust applied for vert scrolling.
  768. let touch_state = {
  769. let mut touch_info = self.touch_info.lock();
  770. touch_info.update(&touch_pos);
  771. touch_info.state.clone()
  772. };
  773. ed!("handle_touch_move({touch_pos:?}) touch_state={touch_state:?}");
  774. //t!("handle_touch_move({touch_pos:?}) touch_state={touch_state:?}");
  775. match &touch_state {
  776. TouchStateAction::Inactive => return false,
  777. TouchStateAction::StartSelect => {
  778. let mut menu = action::Menu::new(
  779. self.font_size.get(),
  780. self.action_fg_color.get(),
  781. self.action_bg_color.get(),
  782. self.action_padding.get(),
  783. self.action_spacing.get(),
  784. self.window_scale.get(),
  785. );
  786. let atom =
  787. &mut self.redraw.make_guard(gfxtag!("BaseEdit::TouchStateAction::StartSelect"));
  788. if self.text.get().is_empty() {
  789. menu.add("Paste", ACTION_PASTE);
  790. // Set the menu pos to the current cursor pos
  791. menu.pos = self.get_cursor_pos();
  792. self.touch_info.lock().state = TouchStateAction::Inactive;
  793. } else {
  794. menu.add("Copy", ACTION_COPY);
  795. menu.add("Paste", ACTION_PASTE);
  796. menu.add("Select All", ACTION_SELALL);
  797. self.abs_to_local(&mut touch_pos);
  798. self.start_touch_select(touch_pos, atom);
  799. {
  800. let editor = self.editor.lock();
  801. let (curs_lhs, _) = self.get_select_handles(&editor).unwrap();
  802. // Set the menu pos to the LHS of the selection
  803. menu.pos = curs_lhs + self.behave.inner_pos();
  804. }
  805. // Adjust menu pos so RHS doesnt leave the RHS of this widget
  806. let rect = self.rect.get();
  807. if menu.pos.x + menu.total_width() > rect.w {
  808. menu.pos.x = rect.w - menu.total_width();
  809. }
  810. d!("touch state: StartSelect -> Select");
  811. self.touch_info.lock().state = TouchStateAction::Select;
  812. }
  813. self.action_mode.set(menu);
  814. ed!("handle_touch_move: StartSelect handled");
  815. }
  816. TouchStateAction::DragSelectHandle { side } => {
  817. // The IME can collapse the selection (finishing phone-select
  818. // mode) while a handle drag is in progress. Abort the drag
  819. // instead of asserting on the stale state.
  820. if !self.is_phone_select.load(Ordering::Relaxed) {
  821. ed!("handle_touch_move: phone select finished mid-drag, aborting");
  822. self.touch_info.lock().state = TouchStateAction::Inactive;
  823. return true
  824. }
  825. let rect = self.rect.get();
  826. let is_touch_hover = rect.contains(touch_pos);
  827. let sel_sender = self.sel_sender.lock().clone().unwrap();
  828. // Mouse is outside rect?
  829. // If so we gotta scroll it while selecting.
  830. if !is_touch_hover {
  831. // This process will begin selecting text and applying scroll too.
  832. sel_sender.try_send(Some((touch_pos, Some(*side)))).unwrap();
  833. } else {
  834. // Stop any existing select/scroll process
  835. sel_sender.try_send(None).unwrap();
  836. // Mouse is inside so just select the text once and be done.
  837. self.handle_select(touch_pos, Some(*side));
  838. }
  839. }
  840. TouchStateAction::ScrollVert { start_pos, scroll_start } => {
  841. let travel_dist = self.behave.scroll_ctrl().travel(*start_pos, touch_pos);
  842. let mut scroll = scroll_start + travel_dist;
  843. scroll = scroll.clamp(0., self.behave.max_scroll());
  844. if (self.scroll.load(Ordering::Relaxed) - scroll).abs() < VERT_SCROLL_UPDATE_INC {
  845. return true
  846. }
  847. self.scroll.store(scroll, Ordering::Release);
  848. self.redraw.trigger();
  849. }
  850. TouchStateAction::SetCursorPos => {
  851. // TBH I can't even see the cursor under my thumb so I'll just
  852. // comment this for now.
  853. }
  854. _ => {}
  855. }
  856. true
  857. }
  858. async fn handle_touch_end(&self, mut touch_pos: Point) -> bool {
  859. //t!("handle_touch_end({touch_pos:?})");
  860. self.abs_to_local(&mut touch_pos);
  861. let state = self.touch_info.lock().stop();
  862. ed!("handle_touch_end({touch_pos:?}) final_state={state:?}");
  863. match state {
  864. TouchStateAction::Inactive => return false,
  865. TouchStateAction::Started { pos: _, instant: _ } | TouchStateAction::SetCursorPos => {
  866. let atom = &mut self.redraw.make_guard(gfxtag!("BaseEdit::handle_touch_end"));
  867. self.touch_set_cursor_pos(atom, touch_pos);
  868. }
  869. _ => {}
  870. }
  871. // Stop any selection scrolling
  872. let scroll_sender = self.sel_sender.lock().clone().unwrap();
  873. scroll_sender.try_send(None).unwrap();
  874. let mut need_focus = !self.is_focused.get();
  875. #[cfg(target_os = "android")]
  876. if !is_ime_visible() {
  877. need_focus = true;
  878. }
  879. if need_focus {
  880. let node = self.node();
  881. node.trigger("focus_request", vec![]).await.unwrap();
  882. }
  883. true
  884. }
  885. fn touch_set_cursor_pos(&self, atom: &mut PropertyAtomicGuard, touch_pos: Point) {
  886. ed!("touch_set_cursor_pos({touch_pos:?}) before=[{}]", self.dbg_state());
  887. let mut editor = self.editor.lock();
  888. editor.move_to_pos(touch_pos);
  889. editor.refresh();
  890. drop(editor);
  891. ed!("touch_set_cursor_pos: moved cursor after=[{}]", self.dbg_state());
  892. self.pause_blinking();
  893. self.finish_select(atom);
  894. }
  895. fn finish_select(&self, atom: &mut PropertyAtomicGuard) {
  896. ed!(
  897. "finish_select: phone_select={} -> false",
  898. self.is_phone_select.load(Ordering::Relaxed)
  899. );
  900. self.is_phone_select.store(false, Ordering::Release);
  901. self.hide_cursor.store(false, Ordering::Release);
  902. self.select_text.clone().set_null(atom, Role::Internal, 0).unwrap();
  903. // The action menu belongs to the selection; dismiss it too.
  904. self.action_mode.clear();
  905. }
  906. fn handle_select(&self, mouse_pos: Point, side: Option<isize>) {
  907. //t!("handle_select({mouse_pos:?}, {side:?})");
  908. let rect = self.rect.get();
  909. let is_mouse_hover = rect.contains(mouse_pos);
  910. let mut clip_mouse_pos = rect.clip_point(mouse_pos);
  911. let atom = &mut self.redraw.make_guard(gfxtag!("BaseEdit::handle_mouse_move"));
  912. // Handle scrolling
  913. if !is_mouse_hover {
  914. let scroll_ctrl = self.behave.scroll_ctrl();
  915. // How far is the cursor outside the widget rect
  916. let travel = scroll_ctrl.travel(mouse_pos, clip_mouse_pos);
  917. //t!("select autoscroll travel={travel}");
  918. let max_scroll = self.behave.max_scroll();
  919. let delta = travel * SELECT_SCROLL_TRAVEL_SPEED;
  920. let scroll = (self.scroll.load(Ordering::Relaxed) + delta).clamp(0., max_scroll);
  921. self.scroll.store(scroll, Ordering::Release);
  922. }
  923. // Move mouse pos within this widget
  924. self.abs_to_local(&mut clip_mouse_pos);
  925. let mut sel_side = 1;
  926. if let Some(side) = side {
  927. sel_side = side;
  928. }
  929. let seltext = {
  930. let mut editor = self.editor.lock();
  931. // The below lines rely on parley driver which android does not use
  932. //let mut txt_ctx = text::TEXT_CTX.get().await;
  933. //let mut drv = editor.driver(&mut txt_ctx).unwrap();
  934. //drv.extend_selection_to_point(clip_mouse_pos.x, clip_mouse_pos.y);
  935. let layout = editor.layout();
  936. let prev_sel = editor.selection(sel_side);
  937. let sel = prev_sel.extend_to_point(layout, clip_mouse_pos.x, clip_mouse_pos.y);
  938. let (mut prev_start, mut prev_end) =
  939. (prev_sel.anchor().index(), prev_sel.focus().index());
  940. let (mut start, mut end) = (sel.anchor().index(), sel.focus().index());
  941. //t!("handle_select() setting ({start}, {end})");
  942. // When dragging phone handles we prevent the selection crossing over itself.
  943. // Mouse select does not have this limitation.
  944. if let Some(side) = side {
  945. assert!(side.abs() == 1);
  946. // Phone-select mode can be finished concurrently (e.g. the
  947. // IME collapsed the selection) while a queued drag update
  948. // was still in flight. Drop the stale update.
  949. if !self.is_phone_select.load(Ordering::Relaxed) {
  950. ed!("handle_select: phone select finished mid-drag, dropping update");
  951. return
  952. }
  953. // Prevent selection crossing itself
  954. if side == -1 {
  955. let max_end = sel.anchor().previous_visual(layout).index();
  956. end = std::cmp::min(end, max_end);
  957. //t!("handle_select(): LHS set focus {end} before {max_end}");
  958. // When selecting from the LHS these will be swapped so swap them back
  959. swap(&mut start, &mut end);
  960. swap(&mut prev_start, &mut prev_end);
  961. } else {
  962. let min_end = sel.anchor().next_visual(layout).index();
  963. end = std::cmp::max(end, min_end);
  964. //t!("handle_select(): RHS set focus {end} after {min_end}");
  965. }
  966. }
  967. if start == prev_start && end == prev_end {
  968. // No change so just return
  969. //t!("handle_select(): no change early exit");
  970. return
  971. }
  972. #[cfg(target_os = "android")]
  973. assert!(start < end);
  974. //t!("handle_select(): set_selection({start}, {end})");
  975. ed!("handle_select: set_selection({start}, {end}) side={side:?} prev=({prev_start}, {prev_end})");
  976. editor.set_selection(start, end);
  977. editor.selected_text()
  978. };
  979. ed!("handle_select: seltext={seltext:?} clip_pos={clip_mouse_pos:?}");
  980. // Android editor impl detail: selection disappears when anchor == index
  981. // But we disallow this so it should never happen. Just making a note of it here.
  982. // See the android only assert that `start < end` in the block above.
  983. #[cfg(target_os = "android")]
  984. {
  985. //if sel_start == sel_end {
  986. // self.finish_select(atom);
  987. //}
  988. }
  989. // Will be None when drag select just started
  990. if let Some(seltext) = seltext {
  991. self.select_text.clone().set_str(atom, Role::Internal, 0, seltext).unwrap();
  992. }
  993. self.pause_blinking();
  994. //self.behave.apply_cursor_scroll();
  995. }
  996. /// Holds the cursor solid while the user interacts. The renderer-managed
  997. /// anim resumes blinking by itself once the idle time passes; there is no
  998. /// app-side timed commit and the committed cursor draw call (which
  999. /// references the anim) never needs to change for pausing.
  1000. fn pause_blinking(&self) {
  1001. let anim = self.cursor_anim.lock().clone();
  1002. let Some(anim) = anim else { return };
  1003. anim.pause(0, self.cursor_idle_time.get() as u64);
  1004. }
  1005. #[instrument(target = "ui::edit")]
  1006. fn get_cursor_instrs(&self) -> Vec<DrawInstruction> {
  1007. if !self.is_focused.get() || self.hide_cursor.load(Ordering::Relaxed) {
  1008. return vec![]
  1009. }
  1010. let pos = self.get_cursor_pos();
  1011. let anim = self.cursor_anim.lock().clone().unwrap();
  1012. vec![DrawInstruction::Move(pos), DrawInstruction::Animation(anim)]
  1013. }
  1014. fn regen_bg_mesh<R: RenderApi>(&self, renderer: &R) -> Vec<DrawInstruction> {
  1015. if !self.debug.get() {
  1016. return vec![]
  1017. }
  1018. let mut rect = self.rect.get().with_zero_pos();
  1019. let mut mesh = MeshBuilder::new(gfxtag!("chatedit_bg"));
  1020. mesh.draw_outline(&rect, [0., 1., 0., 1.], 1.);
  1021. let pad_top = self.padding.get_f32(0).unwrap();
  1022. let pad_right = self.padding.get_f32(1).unwrap();
  1023. let pad_bottom = self.padding.get_f32(2).unwrap();
  1024. let pad_left = self.padding.get_f32(3).unwrap();
  1025. rect.x = pad_left;
  1026. rect.y = pad_top;
  1027. rect.w -= pad_left + pad_right;
  1028. rect.h -= pad_top + pad_bottom;
  1029. mesh.draw_outline(&rect, [0., 1., 0., 0.5], 1.);
  1030. vec![DrawInstruction::Draw(mesh.alloc(renderer).draw_untextured())]
  1031. }
  1032. fn regen_txt_mesh(&self) -> Vec<DrawInstruction> {
  1033. let mut instrs = vec![DrawInstruction::Move(self.behave.inner_pos())];
  1034. // Render placeholder
  1035. let current_text = self.text.get();
  1036. if current_text.is_empty() {
  1037. let placeholder_text = self.placeholder_text.get();
  1038. if !placeholder_text.is_empty() {
  1039. let placeholder_color = self.placeholder_color.get();
  1040. let font_size = self.font_size.get();
  1041. let lineheight = self.lineheight.get();
  1042. let window_scale = self.window_scale.get();
  1043. let placeholder_layout = text::make_layout(
  1044. &placeholder_text,
  1045. placeholder_color,
  1046. font_size,
  1047. lineheight,
  1048. window_scale,
  1049. None,
  1050. &[],
  1051. );
  1052. let mut render_instrs = text::render_layout(
  1053. &placeholder_layout,
  1054. &self.renderer,
  1055. gfxtag!("chatedit_placeholder_mesh"),
  1056. );
  1057. instrs.append(&mut render_instrs);
  1058. return instrs;
  1059. }
  1060. }
  1061. // Render text
  1062. let editor = self.editor.lock();
  1063. let mut render_instrs = editor.render_instrs(&self.renderer, gfxtag!("chatedit_txt_mesh"));
  1064. instrs.append(&mut render_instrs);
  1065. instrs
  1066. }
  1067. fn regen_select_mesh<R: RenderApi>(&self, renderer: &R) -> Vec<DrawInstruction> {
  1068. let mut instrs = vec![DrawInstruction::Move(self.behave.inner_pos())];
  1069. let editor = self.editor.lock();
  1070. let sel_color = self.hi_bg_color.get();
  1071. let sel_rects = editor.selection_rects();
  1072. if !sel_rects.is_empty() {
  1073. let mut mesh = MeshBuilder::new(gfxtag!("chatedit_select_mesh"));
  1074. for rect in sel_rects {
  1075. mesh.draw_filled_box(&rect, sel_color);
  1076. }
  1077. instrs.push(DrawInstruction::Draw(mesh.alloc(renderer).draw_untextured()));
  1078. }
  1079. instrs
  1080. }
  1081. fn regen_phone_select_handle_mesh<R: RenderApi>(&self, renderer: &R) -> Vec<DrawInstruction> {
  1082. if !self.is_phone_select.load(Ordering::Acquire) {
  1083. //t!("regen_phone_select_handle_mesh() [DISABLED]");
  1084. return vec![]
  1085. }
  1086. let editor = self.editor.lock();
  1087. let (first, last) = self.get_select_handles(&editor).unwrap();
  1088. let sel = editor.selection(1);
  1089. assert!(!sel.is_collapsed());
  1090. // We could cache this and use Move instead but why bother?
  1091. let mut mesh = MeshBuilder::new(gfxtag!("chatedit_phone_select_handle"));
  1092. self.draw_phone_select_handle(&mut mesh, first, -1.);
  1093. self.draw_phone_select_handle(&mut mesh, last, 1.);
  1094. vec![DrawInstruction::Draw(mesh.alloc(renderer).draw_untextured())]
  1095. }
  1096. /// This does not make use of its own batch since we want to de-atomize updating this
  1097. /// widget from its dependencies so theres zero latency when typing.
  1098. /// Runs inside the draw pass, so it borrows the pass's atom: property
  1099. /// echoes are deferred there and no extra pass is triggered.
  1100. /// Should be called when text contents changes.
  1101. fn eval_rect(&self, atom: &mut PropertyAtomicGuard) {
  1102. self.behave.eval_rect(atom);
  1103. }
  1104. fn make_draw_calls(&self) -> DrawUpdate {
  1105. let rect = self.rect.get();
  1106. let cursor_instrs = self.get_cursor_instrs();
  1107. let txt_instrs = self.regen_txt_mesh();
  1108. let sel_instrs = self.regen_select_mesh(&self.renderer);
  1109. let phone_sel_instrs = self.regen_phone_select_handle_mesh(&self.renderer);
  1110. let mut content_instrs = vec![DrawInstruction::ApplyView(rect.with_zero_pos())];
  1111. let mut bg_instrs = self.regen_bg_mesh(&self.renderer);
  1112. content_instrs.append(&mut bg_instrs);
  1113. content_instrs.push(DrawInstruction::Move(self.behave.scroll()));
  1114. let action_instrs = self.action_mode.get_instrs();
  1115. // + root (move)
  1116. // -+ content (apply view)
  1117. // └╴select
  1118. // └╴text
  1119. // └╴phone_handle
  1120. // └╴cursor
  1121. // -- action bar
  1122. // Why do we have such a complicated layout?
  1123. // When adjusting selection, it's slow to redraw everything, so the selection
  1124. // must be drawn separately.
  1125. // Lastly the cursor is blinking and that's on top but with clipping.
  1126. DrawUpdate {
  1127. key: self.root_dc_key,
  1128. draw_calls: vec![
  1129. (
  1130. self.root_dc_key,
  1131. DrawCall::new(
  1132. vec![DrawInstruction::Move(rect.pos())],
  1133. vec![self.content_dc_key, self.action_mode.dc_key],
  1134. self.z_index.get(),
  1135. "chatedit_root",
  1136. ),
  1137. ),
  1138. (
  1139. self.content_dc_key,
  1140. DrawCall::new(
  1141. content_instrs,
  1142. vec![
  1143. self.text_dc_key,
  1144. self.phone_select_handle_dc_key,
  1145. self.cursor_dc_key,
  1146. self.select_dc_key,
  1147. ],
  1148. 0,
  1149. "chatedit_content",
  1150. ),
  1151. ),
  1152. (self.select_dc_key, DrawCall::new(sel_instrs, vec![], 0, "chatedit_sel")),
  1153. (self.text_dc_key, DrawCall::new(txt_instrs, vec![], 1, "chatedit_text")),
  1154. (self.cursor_dc_key, DrawCall::new(cursor_instrs, vec![], 2, "chatedit_curs")),
  1155. (
  1156. self.phone_select_handle_dc_key,
  1157. DrawCall::new(phone_sel_instrs, vec![], 1, "chatedit_phone_sel"),
  1158. ),
  1159. (
  1160. self.action_mode.dc_key,
  1161. DrawCall::new(action_instrs, vec![], 0, "chatedit_action"),
  1162. ),
  1163. ],
  1164. }
  1165. }
  1166. async fn process_insert_text_method(me: &Weak<Self>, sub: &MethodCallSub) -> bool {
  1167. let Ok(method_call) = sub.receive().await else {
  1168. debug!(target: "ui::chatedit", "Event relayer closed");
  1169. return false
  1170. };
  1171. t!("method called: insert_line({method_call:?})");
  1172. assert!(method_call.send_res.is_none());
  1173. fn decode_data(data: &[u8]) -> std::io::Result<String> {
  1174. let mut cur = Cursor::new(&data);
  1175. let text = String::decode(&mut cur)?;
  1176. Ok(text)
  1177. }
  1178. let Ok(text) = decode_data(&method_call.data) else {
  1179. error!(target: "ui::chatedit", "insert_text() method invalid arg data");
  1180. return true
  1181. };
  1182. let Some(self_) = me.upgrade() else {
  1183. // Should not happen
  1184. panic!("self destroyed before insert_text_method_task was stopped!");
  1185. };
  1186. let atom = &mut self_.redraw.make_guard(gfxtag!("BaseEdit::process_insert_text_method"));
  1187. self_.editor.lock().insert(&text, atom);
  1188. self_.action_mode.clear();
  1189. ed!("insert_text method: inserted {text:?} after=[{}]", self_.dbg_state());
  1190. true
  1191. }
  1192. async fn process_focus_method(me: &Weak<Self>, sub: &MethodCallSub) -> bool {
  1193. let Ok(method_call) = sub.receive().await else {
  1194. debug!(target: "ui::chatedit", "Event relayer closed");
  1195. return false
  1196. };
  1197. t!("method called: focus({method_call:?})");
  1198. assert!(method_call.send_res.is_none());
  1199. assert!(method_call.data.is_empty());
  1200. let Some(self_) = me.upgrade() else {
  1201. // Should not happen
  1202. panic!("self destroyed before insert_text_method_task was stopped!");
  1203. };
  1204. // Set input type from property
  1205. #[cfg(target_os = "android")]
  1206. {
  1207. let input_type = self_.android_input_type.get();
  1208. ed!("focus method: setting android input_type={input_type}");
  1209. self_.editor.lock().set_input_type(input_type);
  1210. }
  1211. self_.editor.lock().focus();
  1212. let atom = &mut self_.redraw.make_guard(gfxtag!("BaseEdit::process_focus_method"));
  1213. self_.is_focused.set(atom, true);
  1214. ed!("focus method: done after=[{}]", self_.dbg_state());
  1215. true
  1216. }
  1217. async fn process_unfocus_method(me: &Weak<Self>, sub: &MethodCallSub) -> bool {
  1218. let Ok(method_call) = sub.receive().await else {
  1219. debug!(target: "ui::chatedit", "Event relayer closed");
  1220. return false
  1221. };
  1222. t!("method called: focus({method_call:?})");
  1223. assert!(method_call.send_res.is_none());
  1224. assert!(method_call.data.is_empty());
  1225. let Some(self_) = me.upgrade() else {
  1226. // Should not happen
  1227. panic!("self destroyed before insert_text_method_task was stopped!");
  1228. };
  1229. self_.editor.lock().unfocus();
  1230. let atom = &mut self_.redraw.make_guard(gfxtag!("BaseEdit::process_unfocus_method"));
  1231. self_.is_focused.set(atom, false);
  1232. ed!("unfocus method: done after=[{}]", self_.dbg_state());
  1233. true
  1234. }
  1235. #[cfg(target_os = "android")]
  1236. fn handle_android_event(&self, state: AndroidTextInputState) {
  1237. if !self.is_active.get() {
  1238. ed!("handle_android_event: DROP inactive state={state:?}");
  1239. return
  1240. }
  1241. // A handle drag owns the selection. Our own `set_selection` calls
  1242. // push state to the IME and Android's input connection echoes it
  1243. // back, sometimes normalized to a collapsed (cursor-only)
  1244. // selection. Applying such an echo mid-drag would collapse our
  1245. // selection and finish phone-select mode, making the handles
  1246. // vanish. Drop it: the next drag update re-asserts the real
  1247. // selection.
  1248. let drag_in_progress = {
  1249. let touch_info = self.touch_info.lock();
  1250. matches!(touch_info.state, TouchStateAction::DragSelectHandle { .. })
  1251. };
  1252. if drag_in_progress && state.select.0 == state.select.1 {
  1253. ed!("handle_android_event: DROP collapsed IME echo mid-drag state={state:?}");
  1254. return
  1255. }
  1256. ed!("handle_android_event: ENTER incoming={state:?} before=[{}]", self.dbg_state());
  1257. let atom = &mut self.redraw.make_guard(gfxtag!("BaseEdit::handle_android_event"));
  1258. let is_new_select_collapsed = state.select.0 == state.select.1;
  1259. let (is_text_changed, is_select_changed, is_compose_changed) = {
  1260. let mut editor = self.editor.lock();
  1261. // Diff old and new state so we know what changed
  1262. let old_text = editor.state.text.clone();
  1263. let old_select = editor.state.select;
  1264. let old_compose = editor.state.compose;
  1265. let diff = (
  1266. editor.state.text != state.text,
  1267. editor.state.select != state.select,
  1268. editor.state.compose != state.compose,
  1269. );
  1270. ed!(
  1271. "handle_android_event: diff text_changed={} (old={:?} new={:?}) select_changed={} (old={:?} new={:?}) compose_changed={} (old={:?} new={:?})",
  1272. diff.0, old_text, state.text,
  1273. diff.1, old_select, state.select,
  1274. diff.2, old_compose, state.compose,
  1275. );
  1276. editor.state = state;
  1277. editor.on_buffer_changed(atom);
  1278. diff
  1279. };
  1280. // Nothing changed. Just return.
  1281. if !is_text_changed && !is_select_changed && !is_compose_changed {
  1282. ed!("handle_android_event: no-op (nothing changed), after=[{}]", self.dbg_state());
  1283. return
  1284. }
  1285. ed!(
  1286. "handle_android_event: applying text_changed={} select_changed={} compose_changed={} collapsed={}",
  1287. is_text_changed, is_select_changed, is_compose_changed, is_new_select_collapsed
  1288. );
  1289. // Only redraw once we have the parent_rect
  1290. // Can happen when we receive an Android event before the canvas is ready
  1291. if self.parent_rect.lock().is_none() {
  1292. ed!("handle_android_event: parent_rect not ready yet, bailing before redraw");
  1293. return
  1294. }
  1295. // Not sure what to do if only compose changes lol
  1296. // For now just ignore it.
  1297. // Changing the cursor pos will change the compose state if a word is edited.
  1298. // However making selection has a tendency to send spurious compose events.
  1299. // Text changed - finish any active selection
  1300. if is_text_changed {
  1301. self.behave.apply_cursor_scroll();
  1302. self.pause_blinking();
  1303. //assert!(state.text != self.text.get());
  1304. self.finish_select(atom);
  1305. self.action_mode.clear();
  1306. ed!("handle_android_event: handled text change, after=[{}]", self.dbg_state());
  1307. } else if is_select_changed {
  1308. // The IME can collapse a phone-style word selection out from under
  1309. // us (e.g. it repositions the cursor when the keyboard is shown).
  1310. // The selection handles are then stale, so finish phone-select mode.
  1311. // A handle drag always maintains a range, so its IME echoes are
  1312. // unaffected.
  1313. if is_new_select_collapsed && self.is_phone_select.load(Ordering::Relaxed) {
  1314. ed!("handle_android_event: IME collapsed the phone selection, finishing select");
  1315. d!("IME has collapsed selection!");
  1316. self.finish_select(atom);
  1317. }
  1318. ed!("handle_android_event: handled select change, after=[{}]", self.dbg_state());
  1319. } else if is_compose_changed {
  1320. self.editor.lock().refresh();
  1321. ed!("handle_android_event: handled compose change, after=[{}]", self.dbg_state());
  1322. }
  1323. }
  1324. }
  1325. impl Drop for BaseEdit {
  1326. fn drop(&mut self) {
  1327. self.renderer.replace_draw_calls(vec![(self.text_dc_key, Default::default())]);
  1328. }
  1329. }
  1330. #[async_trait]
  1331. impl UIObject for BaseEdit {
  1332. fn priority(&self) -> u32 {
  1333. self.priority.get()
  1334. }
  1335. async fn start(self: Arc<Self>, ex: ExecutorPtr) {
  1336. let me = Arc::downgrade(&self);
  1337. let node_ref = &self.node.upgrade().unwrap();
  1338. let method_sub = node_ref.subscribe_method_call("insert_text").unwrap();
  1339. let me2 = me.clone();
  1340. let insert_text_task =
  1341. ex.spawn(
  1342. async move { while Self::process_insert_text_method(&me2, &method_sub).await {} },
  1343. );
  1344. let method_sub = node_ref.subscribe_method_call("focus").unwrap();
  1345. let me2 = me.clone();
  1346. let focus_task =
  1347. ex.spawn(async move { while Self::process_focus_method(&me2, &method_sub).await {} });
  1348. let method_sub = node_ref.subscribe_method_call("unfocus").unwrap();
  1349. let me2 = me.clone();
  1350. let unfocus_task =
  1351. ex.spawn(async move { while Self::process_unfocus_method(&me2, &method_sub).await {} });
  1352. let mut on_modify = OnModify::new(ex.clone(), self.node.clone(), me.clone());
  1353. on_modify.when_change_external(self.is_focused.prop(), Self::change_focus);
  1354. // When text has been changed.
  1355. // Cursor and selection might be invalidated.
  1356. async fn reset(self_: Arc<BaseEdit>, _batch: BatchGuardPtr) {
  1357. //self_.select_text.set_null(Role::Internal, 0).unwrap();
  1358. self_.scroll.store(0., Ordering::Release);
  1359. self_.redraw.trigger();
  1360. }
  1361. async fn redraw(self_: Arc<BaseEdit>, _batch: BatchGuardPtr) {
  1362. self_.redraw.trigger();
  1363. }
  1364. async fn set_text(self_: Arc<BaseEdit>, _batch: BatchGuardPtr) {
  1365. self_.editor.lock().on_text_prop_changed();
  1366. self_.redraw.trigger();
  1367. }
  1368. on_modify.when_change_external(self.rect.prop(), redraw);
  1369. on_modify.when_change_external(self.baseline.prop(), redraw);
  1370. on_modify.when_change_external(self.lineheight.prop(), redraw);
  1371. on_modify.when_change_external(self.select_ascent.prop(), redraw);
  1372. on_modify.when_change_external(self.select_descent.prop(), redraw);
  1373. on_modify.when_change_external(self.handle_descent.prop(), redraw);
  1374. on_modify.when_change_external(self.padding.clone(), redraw);
  1375. on_modify.when_change_external(self.text.prop(), set_text);
  1376. // The commented properties are modified on input events
  1377. // So then redraw() will get repeatedly triggered when these properties
  1378. // are changed. We should find a solution. For now the hooks are disabled.
  1379. //on_modify.when_change(scroll.prop(), redraw);
  1380. //on_modify.when_change(cursor_pos.prop(), redraw);
  1381. on_modify.when_change_external(self.font_size.prop(), redraw);
  1382. on_modify.when_change_external(self.text.prop(), reset);
  1383. on_modify.when_change_external(self.text_color.prop(), redraw);
  1384. on_modify.when_change_external(self.placeholder_text.prop(), redraw);
  1385. on_modify.when_change_external(self.placeholder_color.prop(), redraw);
  1386. on_modify.when_change_external(self.hi_bg_color.prop(), redraw);
  1387. //on_modify.when_change(selected.clone(), redraw);
  1388. on_modify.when_change_external(self.z_index.prop(), redraw);
  1389. on_modify.when_change_external(self.debug.prop(), redraw);
  1390. async fn regen_cursor(self_: Arc<BaseEdit>, _batch: BatchGuardPtr) {
  1391. // Rebuild the visible frame so cursor style changes apply to
  1392. // the blink animation.
  1393. let mesh = self_.regen_cursor_mesh(&self_.renderer);
  1394. let dc_visible =
  1395. DrawCall::new(vec![DrawInstruction::Draw(mesh)], vec![], 2, "cursor_vis");
  1396. if let Some(anim) = self_.cursor_anim.lock().as_ref() {
  1397. anim.update(0, AnimFrame::new(self_.cursor_blink_time.get(), dc_visible));
  1398. }
  1399. }
  1400. on_modify.when_change_external(self.cursor_color.prop(), regen_cursor);
  1401. on_modify.when_change_external(self.cursor_ascent.prop(), regen_cursor);
  1402. on_modify.when_change_external(self.cursor_descent.prop(), regen_cursor);
  1403. on_modify.when_change_external(self.cursor_width.prop(), regen_cursor);
  1404. // Create the blinking cursor animation using SeqAnim
  1405. // Frame 0: visible cursor
  1406. // Frame 1: invisible (empty)
  1407. let cursor_anim = self.renderer.new_anim(2, false, gfxtag!("baseedit_cursor_blink"));
  1408. let cursor_mesh = self.regen_cursor_mesh(&self.renderer);
  1409. // Frame 0: visible cursor (just the draw, no position)
  1410. let dc_visible =
  1411. DrawCall::new(vec![DrawInstruction::Draw(cursor_mesh)], vec![], 2, "cursor_vis");
  1412. cursor_anim.update(0, AnimFrame::new(self.cursor_blink_time.get(), dc_visible));
  1413. // Frame 1: invisible (empty draw call)
  1414. let dc_invisible = DrawCall::new(vec![], vec![], 2, "cursor_invis");
  1415. cursor_anim.update(1, AnimFrame::new(self.cursor_blink_time.get(), dc_invisible));
  1416. *self.cursor_anim.lock() = Some(cursor_anim);
  1417. let (sel_sender, sel_recvr) = async_channel::unbounded();
  1418. *self.sel_sender.lock() = Some(sel_sender);
  1419. let me2 = me.clone();
  1420. // We don't get continuous mouse move events. Instead this task is used to smoothly
  1421. // scroll when selecting text.
  1422. let sel_task = ex.spawn(async move {
  1423. let mut scroll_stat = None;
  1424. // Too much code duplication here but I didn't find a solution that looks any cleaner.
  1425. loop {
  1426. if scroll_stat.is_some() {
  1427. futures::select! {
  1428. rcv = sel_recvr.recv().fuse() => {
  1429. scroll_stat = rcv.unwrap();
  1430. if let Some((mouse_pos, side)) = scroll_stat {
  1431. let self_ = me2.upgrade().unwrap();
  1432. //t!("select task interrupt: {mouse_pos:?} (side={side:?})");
  1433. self_.handle_select(mouse_pos, side);
  1434. };
  1435. }
  1436. _ = msleep(SELECT_TASK_UPDATE_TIME).fuse() => {
  1437. if let Some((mouse_pos, side)) = scroll_stat {
  1438. let self_ = me2.upgrade().unwrap();
  1439. //t!("select task update: {mouse_pos:?} (side={side:?})");
  1440. self_.handle_select(mouse_pos, side);
  1441. };
  1442. }
  1443. }
  1444. } else {
  1445. scroll_stat = sel_recvr.recv().await.unwrap();
  1446. if let Some((mouse_pos, side)) = scroll_stat {
  1447. let self_ = me2.upgrade().unwrap();
  1448. //t!("select task wake up: {mouse_pos:?} (side={side:?})");
  1449. self_.handle_select(mouse_pos, side);
  1450. };
  1451. }
  1452. }
  1453. });
  1454. let mut tasks = vec![insert_text_task, focus_task, unfocus_task, sel_task];
  1455. tasks.append(&mut on_modify.tasks);
  1456. #[cfg(target_os = "android")]
  1457. {
  1458. let recvr = self.editor.lock().recvr.clone();
  1459. let me2 = me.clone();
  1460. let autosuggest_task = ex.spawn(async move {
  1461. loop {
  1462. let Ok(ev) = recvr.recv().await else {
  1463. t!("Event relayer closed");
  1464. break
  1465. };
  1466. let Some(self_) = me2.upgrade() else {
  1467. // Should not happen
  1468. panic!("self destroyed before autosuggest_task was stopped!");
  1469. };
  1470. self_.handle_android_event(ev);
  1471. }
  1472. });
  1473. tasks.push(autosuggest_task);
  1474. }
  1475. *self.tasks.lock() = tasks;
  1476. }
  1477. fn stop(&self) {
  1478. self.tasks.lock().clear();
  1479. *self.parent_rect.lock() = None;
  1480. self.key_repeat.lock().clear();
  1481. }
  1482. #[instrument(target = "ui::edit")]
  1483. async fn draw(
  1484. &self,
  1485. parent_rect: Rectangle,
  1486. atom: &mut PropertyAtomicGuard,
  1487. ) -> Option<DrawUpdate> {
  1488. *self.parent_rect.lock() = Some(parent_rect);
  1489. self.eval_rect(atom);
  1490. // The fresh eval may have changed the content height, so re-clamp
  1491. // the scroll to keep the cursor in view before computing draw instrs.
  1492. self.behave.apply_cursor_scroll();
  1493. Some(self.make_draw_calls())
  1494. }
  1495. async fn handle_char(&self, key: char, mods: KeyMods, repeat: bool) -> bool {
  1496. ed!("handle_char({key}, {mods:?}, repeat={repeat}) before=[{}]", self.dbg_state());
  1497. t!("handle_char({key}, {mods:?}, {repeat})");
  1498. // First filter for only single digit keys
  1499. if DISALLOWED_CHARS.contains(&key) {
  1500. ed!("handle_char: {key:?} disallowed, ignoring");
  1501. return false
  1502. }
  1503. if !self.is_focused.get() {
  1504. ed!("handle_char: not focused, ignoring");
  1505. return false
  1506. }
  1507. // Must be updated before checking the mods. You can press ctrl+a, then release ctrl
  1508. // before a is released. Then the repeater never gets reset, and uses any old value
  1509. // it has from before for a.
  1510. let actions = {
  1511. let mut repeater = self.key_repeat.lock();
  1512. repeater.key_down(PressedKey::Char(key), repeat)
  1513. };
  1514. let atom = &mut self.redraw.make_guard(gfxtag!("BaseEdit::handle_char"));
  1515. if mods.ctrl || mods.alt || mods.logo {
  1516. if repeat {
  1517. return false
  1518. }
  1519. return self.handle_shortcut(key, &mods, atom);
  1520. }
  1521. // Do nothing
  1522. if actions == 0 {
  1523. return true
  1524. }
  1525. t!("Key {:?} has {} actions", key, actions);
  1526. let key_str = key.to_string().repeat(actions as usize);
  1527. self.editor.lock().insert(&key_str, atom);
  1528. self.behave.apply_cursor_scroll();
  1529. self.pause_blinking();
  1530. // Any edit invalidates the action menu's selection
  1531. self.action_mode.clear();
  1532. ed!("handle_char: inserted {key_str:?} after=[{}]", self.dbg_state());
  1533. true
  1534. }
  1535. async fn handle_key_down(&self, key: KeyCode, mods: KeyMods, repeat: bool) -> bool {
  1536. ed!("handle_key_down({key:?}, {mods:?}, repeat={repeat}) before=[{}]", self.dbg_state());
  1537. t!("handle_key_down({key:?}, {mods:?}, {repeat})");
  1538. // First filter for only single digit keys
  1539. // Avoid processing events handled by handle_char()
  1540. if !ALLOWED_KEYCODES.contains(&key) {
  1541. ed!("handle_key_down: {key:?} not in ALLOWED_KEYCODES, ignoring");
  1542. return false
  1543. }
  1544. if !self.is_focused.get() {
  1545. ed!("handle_key_down: not focused, ignoring");
  1546. return false
  1547. }
  1548. let actions = {
  1549. let mut repeater = self.key_repeat.lock();
  1550. repeater.key_down(PressedKey::Key(key), repeat)
  1551. };
  1552. // Suppress noisy message
  1553. if actions > 0 {
  1554. t!("Key {:?} has {} actions", key, actions);
  1555. }
  1556. let atom = &mut self.redraw.make_guard(gfxtag!("BaseEdit::handle_key_down"));
  1557. let mut is_handled = false;
  1558. for _ in 0..actions {
  1559. is_handled = self.handle_key(&key, &mods, atom);
  1560. }
  1561. ed!("handle_key_down: {key:?} handled={is_handled} after=[{}]", self.dbg_state());
  1562. is_handled
  1563. }
  1564. async fn handle_mouse_btn_down(&self, btn: MouseButton, mut mouse_pos: Point) -> bool {
  1565. if !self.is_active.get() {
  1566. return false
  1567. }
  1568. let rect = self.rect.get();
  1569. if !rect.contains(mouse_pos) {
  1570. return false
  1571. }
  1572. if btn == MouseButton::Right {
  1573. let mut menu = action::Menu::new(
  1574. self.font_size.get(),
  1575. self.action_fg_color.get(),
  1576. self.action_bg_color.get(),
  1577. self.action_padding.get(),
  1578. self.action_spacing.get(),
  1579. self.window_scale.get(),
  1580. );
  1581. if self.text.get().is_empty() {
  1582. menu.add("Paste", ACTION_PASTE);
  1583. } else {
  1584. menu.add("Copy", ACTION_COPY);
  1585. menu.add("Paste", ACTION_PASTE);
  1586. menu.add("Select All", ACTION_SELALL);
  1587. }
  1588. // Mouse pos relative to root layout
  1589. menu.pos = mouse_pos - rect.pos();
  1590. self.action_mode.set(menu);
  1591. self.redraw.trigger();
  1592. return true
  1593. }
  1594. // Handle main case
  1595. if btn != MouseButton::Left {
  1596. return false
  1597. }
  1598. let atom = &mut self.redraw.make_guard(gfxtag!("BaseEdit::handle_mouse_btn_down"));
  1599. // clicking inside box will:
  1600. // 1. make it active
  1601. // 2. begin selection
  1602. if self.is_focused.get() {
  1603. ed!("handle_mouse_btn_down({btn:?}, {mouse_pos:?}): already focused click");
  1604. d!("BaseEdit clicked");
  1605. } else {
  1606. ed!("handle_mouse_btn_down({btn:?}, {mouse_pos:?}): focusing widget");
  1607. d!("BaseEdit focused");
  1608. self.is_focused.set(atom, true);
  1609. let node = self.node();
  1610. node.trigger("focus_request", vec![]).await.unwrap();
  1611. }
  1612. // Move mouse pos within this widget
  1613. self.abs_to_local(&mut mouse_pos);
  1614. self.editor.lock().driver().move_to_point(mouse_pos.x, mouse_pos.y);
  1615. if !self.select_text.is_null(0).unwrap() {
  1616. self.select_text.clone().set_null(atom, Role::Internal, 0).unwrap();
  1617. }
  1618. self.mouse_btn_held.store(true, Ordering::Relaxed);
  1619. self.pause_blinking();
  1620. // A click moves the cursor; any open action menu is stale now
  1621. self.action_mode.clear();
  1622. true
  1623. }
  1624. async fn handle_mouse_btn_up(&self, btn: MouseButton, mut mouse_pos: Point) -> bool {
  1625. if !self.is_active.get() {
  1626. return false
  1627. }
  1628. self.abs_to_local(&mut mouse_pos);
  1629. // Check if action was clicked
  1630. if btn != MouseButton::Left {
  1631. return false
  1632. }
  1633. // releasing mouse button will end selection
  1634. self.mouse_btn_held.store(false, Ordering::Relaxed);
  1635. let atom = &mut self.redraw.make_guard(gfxtag!("BaseEdit::handle_mouse_btn_up"));
  1636. if let Some(action_id) = self.action_mode.interact(mouse_pos) {
  1637. match action_id {
  1638. ACTION_COPY => {
  1639. if let Some(txt) = self.editor.lock().selected_text() {
  1640. clipboard::set(&txt);
  1641. }
  1642. }
  1643. ACTION_PASTE => {
  1644. if let Some(txt) = clipboard::get() {
  1645. self.editor.lock().insert(&txt, atom);
  1646. self.behave.apply_cursor_scroll();
  1647. self.finish_select(atom);
  1648. }
  1649. }
  1650. ACTION_SELALL => {
  1651. let mut editor = self.editor.lock();
  1652. editor.select_all();
  1653. if let Some(seltext) = editor.selected_text() {
  1654. self.select_text.clone().set_str(atom, Role::Internal, 0, seltext).unwrap();
  1655. }
  1656. }
  1657. _ => {}
  1658. }
  1659. return true;
  1660. } else {
  1661. // interact() already consumed the menu; the pass re-emits the
  1662. // (now empty) action overlay when the batch settles.
  1663. }
  1664. // Stop any selection scrolling
  1665. let scroll_sender = self.sel_sender.lock().clone().unwrap();
  1666. scroll_sender.send(None).await.unwrap();
  1667. false
  1668. }
  1669. async fn handle_mouse_move(&self, mouse_pos: Point) -> bool {
  1670. if !self.is_active.get() {
  1671. return false
  1672. }
  1673. let rect = self.rect.get();
  1674. let is_mouse_hover = rect.contains(mouse_pos);
  1675. self.is_mouse_hover.store(is_mouse_hover, Ordering::Relaxed);
  1676. if !self.mouse_btn_held.load(Ordering::Relaxed) {
  1677. return false
  1678. }
  1679. let sel_sender = self.sel_sender.lock().clone().unwrap();
  1680. // Mouse is outside rect?
  1681. // If so we gotta scroll it while selecting.
  1682. if !is_mouse_hover {
  1683. // This process will begin selecting text and applying scroll too.
  1684. sel_sender.send(Some((mouse_pos, None))).await.unwrap();
  1685. } else {
  1686. // Stop any existing select/scroll process
  1687. sel_sender.send(None).await.unwrap();
  1688. // Mouse is inside so just select the text once and be done.
  1689. self.handle_select(mouse_pos, None);
  1690. }
  1691. true
  1692. }
  1693. async fn handle_mouse_wheel(&self, wheel_pos: Point) -> bool {
  1694. if !self.is_mouse_hover.load(Ordering::Relaxed) {
  1695. return false
  1696. }
  1697. let mut scroll =
  1698. self.scroll.load(Ordering::Relaxed) - wheel_pos.y * self.scroll_speed.get();
  1699. scroll = scroll.clamp(0., self.behave.max_scroll());
  1700. t!("handle_mouse_wheel({wheel_pos:?}) [scroll={scroll}]");
  1701. self.scroll.store(scroll, Ordering::Release);
  1702. self.redraw.trigger();
  1703. true
  1704. }
  1705. /// Runs on the Stage thread inside the miniquad event callback.
  1706. /// Converted to mutate + trigger: state mutations happen inline and the
  1707. /// redraw is enqueued for the serialized draw pass (one extra hop of
  1708. /// latency for drag-handle feedback; see design.md D6).
  1709. fn handle_touch_sync(&self, phase: TouchPhase, id: u64, touch_pos: Point) -> bool {
  1710. if !self.is_active.get() {
  1711. return false
  1712. }
  1713. // Ignore multi-touch
  1714. if id != 0 {
  1715. return false
  1716. }
  1717. match phase {
  1718. TouchPhase::Started => self.handle_touch_start(touch_pos),
  1719. TouchPhase::Moved => self.handle_touch_move(touch_pos),
  1720. TouchPhase::Ended | TouchPhase::Cancelled => false,
  1721. }
  1722. }
  1723. async fn handle_touch(&self, phase: TouchPhase, id: u64, touch_pos: Point) -> bool {
  1724. if !self.is_active.get() {
  1725. return false
  1726. }
  1727. // Ignore multi-touch
  1728. if id != 0 {
  1729. return false
  1730. }
  1731. match phase {
  1732. TouchPhase::Started | TouchPhase::Moved | TouchPhase::Cancelled => false,
  1733. TouchPhase::Ended => self.handle_touch_end(touch_pos).await,
  1734. }
  1735. }
  1736. }
  1737. // TODO: impl Drop
  1738. impl std::fmt::Debug for BaseEdit {
  1739. fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
  1740. write!(f, "{:?}", self.node.upgrade().unwrap())
  1741. }
  1742. }