mod.rs 73 KB

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