behave.rs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. /* This file is part of DarkFi (https://dark.fi)
  2. * Copyright (C) 2020-2025 Dyne.org foundation
  3. *
  4. * This program is free software: you can redistribute it and/or modify
  5. * it under the terms of the GNU Affero General Public License as
  6. * published by the Free Software Foundation, either version 3 of the
  7. * License, or (at your option) any later version.
  8. *
  9. * This program is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. * GNU Affero General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU Affero General Public License
  15. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  16. */
  17. use async_lock::Mutex as AsyncMutex;
  18. use async_trait::async_trait;
  19. use atomic_float::AtomicF32;
  20. use parking_lot::Mutex as SyncMutex;
  21. use std::sync::{atomic::Ordering, Arc};
  22. use crate::{
  23. gfx::{Point, Rectangle},
  24. prop::{PropertyAtomicGuard, PropertyFloat32, PropertyPtr, PropertyRect, Role},
  25. text2::Editor,
  26. };
  27. use super::EditorHandle;
  28. macro_rules! t { ($($arg:tt)*) => { trace!(target: "ui::edit::behave", $($arg)*); } }
  29. pub enum BaseEditType {
  30. SingleLine,
  31. MultiLine,
  32. }
  33. #[async_trait]
  34. pub(super) trait EditorBehavior: Send + Sync {
  35. async fn eval_rect(&self, atom: &mut PropertyAtomicGuard);
  36. /// Whenever the cursor is modified this MUST be called
  37. /// to recalculate the scroll value.
  38. /// Must call redraw after this.
  39. async fn apply_cursor_scroll(&self, atom: &mut PropertyAtomicGuard);
  40. fn scroll(&self) -> Point;
  41. /// Maximum allowed scroll value
  42. async fn max_scroll(&self) -> f32;
  43. /// Inner position used for rendering
  44. fn inner_pos(&self) -> Point;
  45. fn allow_endl(&self) -> bool;
  46. fn scroll_ctrl(&self) -> ScrollDir;
  47. }
  48. pub(super) enum ScrollDir {
  49. Vert,
  50. Horiz,
  51. }
  52. impl ScrollDir {
  53. pub fn cmp(&self, grad: f32) -> bool {
  54. match self {
  55. Self::Vert => grad.abs() > 0.5,
  56. Self::Horiz => grad.abs() < 0.5,
  57. }
  58. }
  59. pub fn travel(&self, start_pos: Point, touch_pos: Point) -> f32 {
  60. match self {
  61. Self::Vert => start_pos.y - touch_pos.y,
  62. Self::Horiz => start_pos.x - touch_pos.x,
  63. }
  64. }
  65. }
  66. pub(super) struct MultiLine {
  67. pub min_height: PropertyFloat32,
  68. pub max_height: PropertyFloat32,
  69. pub rect: PropertyRect,
  70. pub baseline: PropertyFloat32,
  71. pub padding: PropertyPtr,
  72. pub cursor_descent: PropertyFloat32,
  73. pub parent_rect: Arc<SyncMutex<Option<Rectangle>>>,
  74. pub editor: Arc<AsyncMutex<Option<Editor>>>,
  75. pub content_height: AtomicF32,
  76. pub scroll: Arc<AtomicF32>,
  77. }
  78. impl MultiLine {
  79. /// Lazy-initializes the editor and returns a handle to it
  80. async fn lock_editor<'a>(&'a self) -> EditorHandle<'a> {
  81. EditorHandle { guard: self.editor.lock().await }
  82. }
  83. fn bounded_height(&self, height: f32) -> f32 {
  84. height.clamp(self.min_height.get(), self.max_height.get())
  85. }
  86. fn padding_top(&self) -> f32 {
  87. self.padding.get_f32(0).unwrap()
  88. }
  89. fn padding_bottom(&self) -> f32 {
  90. self.padding.get_f32(1).unwrap()
  91. }
  92. /// Gets the real cursor pos within the rect.
  93. async fn get_cursor_pos(&self) -> Point {
  94. // This is the position within the content.
  95. let cursor_pos = self.lock_editor().await.get_cursor_pos();
  96. // Apply the inner padding
  97. cursor_pos + self.inner_pos()
  98. }
  99. }
  100. #[async_trait]
  101. impl EditorBehavior for MultiLine {
  102. async fn eval_rect(&self, atom: &mut PropertyAtomicGuard) {
  103. let parent_rect = self.parent_rect.lock().clone().unwrap();
  104. // First we evaluate the width based off the parent dimensions
  105. self.rect
  106. .eval_with(
  107. atom,
  108. vec![2],
  109. vec![
  110. ("parent_w".to_string(), parent_rect.w),
  111. ("parent_h".to_string(), parent_rect.h),
  112. ],
  113. )
  114. .unwrap();
  115. let pad_right = self.padding.get_f32(1).unwrap();
  116. let pad_left = self.padding.get_f32(3).unwrap();
  117. // Use the width to adjust the height calcs
  118. let rect_w = self.rect.get_width() - pad_left - pad_right;
  119. let content_height = {
  120. let mut editor = self.lock_editor().await;
  121. editor.set_width(rect_w);
  122. editor.refresh().await;
  123. editor.height()
  124. };
  125. self.content_height.store(content_height, Ordering::Relaxed);
  126. let outer_height = content_height + self.padding_top() + self.padding_bottom();
  127. let rect_h = self.bounded_height(outer_height);
  128. self.rect.prop().set_f32(atom, Role::Internal, 3, rect_h).unwrap();
  129. // Finally calculate the position
  130. self.rect
  131. .eval_with(
  132. atom,
  133. vec![0, 1],
  134. vec![
  135. ("parent_w".to_string(), parent_rect.w),
  136. ("parent_h".to_string(), parent_rect.h),
  137. ("rect_w".to_string(), rect_w),
  138. ("rect_h".to_string(), rect_h),
  139. ],
  140. )
  141. .unwrap();
  142. }
  143. async fn apply_cursor_scroll(&self, atom: &mut PropertyAtomicGuard) {
  144. //let pad_top = self.padding_top();
  145. let pad_bot = self.padding_bottom();
  146. let mut scroll = self.scroll.load(Ordering::Relaxed);
  147. let rect_h = self.max_height.get() - pad_bot;
  148. let cursor_y0 = self.get_cursor_pos().await.y;
  149. let cursor_h = self.baseline.get() + self.cursor_descent.get();
  150. // The bottom
  151. let cursor_y1 = cursor_y0 + cursor_h;
  152. //t!("apply_cursor_scrolling() cursor = [{cursor_y0}, {cursor_y1}] rect_h={rect_h} scroll={scroll}");
  153. if cursor_y1 > rect_h + scroll {
  154. let max_scroll = self.max_scroll().await;
  155. //t!(" cursor bottom below rect");
  156. // We want cursor_y1 = rect_h + scroll by adjusting scroll
  157. scroll = (cursor_y1 - rect_h).clamp(0., max_scroll);
  158. self.scroll.store(scroll, Ordering::Release);
  159. } else if cursor_y0 < scroll {
  160. //t!(" cursor top above rect");
  161. scroll = cursor_y0.max(0.);
  162. assert!(scroll >= 0.);
  163. self.scroll.store(scroll, Ordering::Release);
  164. }
  165. }
  166. fn scroll(&self) -> Point {
  167. Point::new(0., -self.scroll.load(Ordering::Relaxed))
  168. }
  169. /// Maximum allowed scroll value
  170. /// * `content_height` measures the height of the actual content.
  171. /// * `outer_height` applies the inner padding.
  172. /// * `rect_h` then clips the `outer_height` to min/max values.
  173. /// We only allow scrolling when max clipping has been applied.
  174. async fn max_scroll(&self) -> f32 {
  175. let content_height = self.content_height.load(Ordering::Relaxed);
  176. let outer_height = content_height + self.padding_top() + self.padding_bottom();
  177. let rect_h = self.rect.get_height();
  178. //t!("max_scroll content_height={content_height}, rect_h={rect_h}");
  179. (outer_height - rect_h).max(0.)
  180. }
  181. /// Inner position used for rendering
  182. fn inner_pos(&self) -> Point {
  183. let pad_top = self.padding_top();
  184. let pad_bot = self.padding_bottom();
  185. let pad_left = self.padding.get_f32(3).unwrap();
  186. let content_height = self.content_height.load(Ordering::Relaxed);
  187. let outer_height = content_height + pad_top + pad_bot;
  188. let rect_h = self.rect.get_height();
  189. let mut inner_pos = Point::zero();
  190. inner_pos.x = pad_left;
  191. if outer_height < rect_h {
  192. // Min was applied to clip. Center content inside the rect.
  193. inner_pos.y = (rect_h - content_height) / 2.;
  194. } else {
  195. inner_pos.y = pad_top;
  196. }
  197. inner_pos
  198. }
  199. fn allow_endl(&self) -> bool {
  200. true
  201. }
  202. fn scroll_ctrl(&self) -> ScrollDir {
  203. ScrollDir::Vert
  204. }
  205. }
  206. pub(super) struct SingleLine {
  207. pub rect: PropertyRect,
  208. pub padding: PropertyPtr,
  209. pub cursor_width: PropertyFloat32,
  210. pub parent_rect: Arc<SyncMutex<Option<Rectangle>>>,
  211. pub editor: Arc<AsyncMutex<Option<Editor>>>,
  212. pub content_height: AtomicF32,
  213. pub scroll: Arc<AtomicF32>,
  214. }
  215. impl SingleLine {
  216. /// Lazy-initializes the editor and returns a handle to it
  217. async fn lock_editor<'a>(&'a self) -> EditorHandle<'a> {
  218. EditorHandle { guard: self.editor.lock().await }
  219. }
  220. }
  221. #[async_trait]
  222. impl EditorBehavior for SingleLine {
  223. async fn eval_rect(&self, atom: &mut PropertyAtomicGuard) {
  224. let content_height = {
  225. let mut editor = self.lock_editor().await;
  226. editor.refresh().await;
  227. editor.height()
  228. };
  229. self.content_height.store(content_height, Ordering::Relaxed);
  230. let parent_rect = self.parent_rect.lock().clone().unwrap();
  231. //self.rect.eval(atom, &parent_rect).unwrap();
  232. self.rect
  233. .eval_with(
  234. atom,
  235. (0..4).collect(),
  236. vec![
  237. ("parent_w".to_string(), parent_rect.w),
  238. ("parent_h".to_string(), parent_rect.h),
  239. ],
  240. )
  241. .unwrap();
  242. }
  243. async fn apply_cursor_scroll(&self, atom: &mut PropertyAtomicGuard) {
  244. let pad_right = self.padding.get_f32(1).unwrap();
  245. let pad_left = self.padding.get_f32(3).unwrap();
  246. let mut scroll = self.scroll.load(Ordering::Relaxed);
  247. let rect_w = self.rect.get_width() - pad_right;
  248. let cursor_x0 = self.lock_editor().await.get_cursor_pos().x + pad_left;
  249. let cursor_x1 = cursor_x0 + self.cursor_width.get();
  250. if cursor_x0 < scroll {
  251. assert!(cursor_x0 >= 0.);
  252. scroll = cursor_x0.max(0.);
  253. self.scroll.store(cursor_x0, Ordering::Release);
  254. } else if cursor_x1 > rect_w + scroll {
  255. let max_scroll = self.max_scroll().await;
  256. let scroll = (cursor_x1 - rect_w).clamp(0., max_scroll);
  257. self.scroll.store(scroll, Ordering::Release);
  258. }
  259. }
  260. fn scroll(&self) -> Point {
  261. Point::new(-self.scroll.load(Ordering::Relaxed), 0.)
  262. }
  263. async fn max_scroll(&self) -> f32 {
  264. let pad_right = self.padding.get_f32(1).unwrap();
  265. let pad_left = self.padding.get_f32(3).unwrap();
  266. let rect_w = self.rect.get_width();
  267. let content_w = self.lock_editor().await.width() + self.cursor_width.get();
  268. (pad_left + pad_right + content_w - rect_w).max(0.)
  269. }
  270. fn inner_pos(&self) -> Point {
  271. let pad_left = self.padding.get_f32(3).unwrap();
  272. let content_height = self.content_height.load(Ordering::Relaxed);
  273. let rect_h = self.rect.get_height();
  274. let mut inner_pos = Point::zero();
  275. inner_pos.x = pad_left;
  276. inner_pos.y = (rect_h - content_height) / 2.;
  277. inner_pos
  278. }
  279. fn allow_endl(&self) -> bool {
  280. false
  281. }
  282. fn scroll_ctrl(&self) -> ScrollDir {
  283. ScrollDir::Horiz
  284. }
  285. }