text.rs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  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 parking_lot::Mutex as SyncMutex;
  20. use rand::{rngs::OsRng, Rng};
  21. use std::sync::Arc;
  22. use tracing::instrument;
  23. use crate::{
  24. gfx::{gfxtag, DrawCall, DrawInstruction, EpochCache, Rectangle, RenderApi, Renderer},
  25. mesh::MeshBuilder,
  26. prop::{
  27. PropertyAtomicGuard, PropertyBool, PropertyColor, PropertyEnum, PropertyFloat32,
  28. PropertyRect, PropertyStr, PropertyUint32, Role,
  29. },
  30. scene::{Pimpl, SceneNodeWeak},
  31. text,
  32. util::i18n::I18nBabelFish,
  33. ExecutorPtr,
  34. };
  35. use super::{DrawUpdate, OnModify, RedrawTrigger, UIObject};
  36. pub type TextPtr = Arc<Text>;
  37. pub struct Text {
  38. node: SceneNodeWeak,
  39. renderer: Renderer,
  40. i18n_fish: I18nBabelFish,
  41. redraw: RedrawTrigger,
  42. tasks: SyncMutex<Vec<smol::Task<()>>>,
  43. dc_key: u64,
  44. rect: PropertyRect,
  45. height: PropertyFloat32,
  46. z_index: PropertyUint32,
  47. priority: PropertyUint32,
  48. text: PropertyStr,
  49. font_size: PropertyFloat32,
  50. text_color: PropertyColor,
  51. lineheight: PropertyFloat32,
  52. text_align: PropertyEnum,
  53. overflow_wrap: PropertyEnum,
  54. use_i18n: PropertyBool,
  55. debug: PropertyBool,
  56. window_scale: PropertyFloat32,
  57. /// Cached layout + rendered instrs. Empty means stale: recompute in
  58. /// the draw pass. Layout is the expensive part (shaping, line breaks).
  59. /// Entries from a dead UI epoch are evicted automatically.
  60. draw_cache: EpochCache<(text::TextLayout, Vec<DrawInstruction>)>,
  61. }
  62. impl Text {
  63. pub async fn new(
  64. node: SceneNodeWeak,
  65. window_scale: PropertyFloat32,
  66. renderer: Renderer,
  67. i18n_fish: I18nBabelFish,
  68. redraw: RedrawTrigger,
  69. ) -> Pimpl {
  70. let node_ref = &node.upgrade().unwrap();
  71. let rect = PropertyRect::wrap(node_ref, Role::Internal, "rect").unwrap();
  72. let height = PropertyFloat32::wrap(node_ref, Role::Internal, "height", 0).unwrap();
  73. let z_index = PropertyUint32::wrap(node_ref, Role::Internal, "z_index", 0).unwrap();
  74. let priority = PropertyUint32::wrap(node_ref, Role::Internal, "priority", 0).unwrap();
  75. let text = PropertyStr::wrap(node_ref, Role::Internal, "text", 0).unwrap();
  76. let font_size = PropertyFloat32::wrap(node_ref, Role::Internal, "font_size", 0).unwrap();
  77. let text_color = PropertyColor::wrap(node_ref, Role::Internal, "text_color").unwrap();
  78. let lineheight = PropertyFloat32::wrap(node_ref, Role::Internal, "lineheight", 0).unwrap();
  79. let text_align = PropertyEnum::wrap(node_ref, Role::Internal, "text_align", 0).unwrap();
  80. let overflow_wrap =
  81. PropertyEnum::wrap(node_ref, Role::Internal, "overflow_wrap", 0).unwrap();
  82. let use_i18n = PropertyBool::wrap(node_ref, Role::Internal, "use_i18n", 0).unwrap();
  83. let debug = PropertyBool::wrap(node_ref, Role::Internal, "debug", 0).unwrap();
  84. let draw_cache = EpochCache::new(&renderer);
  85. let self_ = Arc::new(Self {
  86. node,
  87. renderer,
  88. i18n_fish,
  89. redraw,
  90. tasks: SyncMutex::new(vec![]),
  91. dc_key: OsRng.gen(),
  92. rect,
  93. height,
  94. z_index,
  95. priority,
  96. text,
  97. font_size,
  98. text_color,
  99. lineheight,
  100. text_align,
  101. overflow_wrap,
  102. use_i18n,
  103. debug,
  104. window_scale,
  105. draw_cache,
  106. });
  107. Pimpl::Text(self_)
  108. }
  109. fn make_layout(&self) -> text::TextLayout {
  110. let text = self.text.get();
  111. let font_size = self.font_size.get();
  112. let lineheight = self.lineheight.get();
  113. let text_color = self.text_color.get();
  114. let window_scale = self.window_scale.get();
  115. let width = self.rect.get_width();
  116. let text_align = match self.text_align.get().as_str() {
  117. "end" => parley::Alignment::End,
  118. "left" => parley::Alignment::Left,
  119. "center" => parley::Alignment::Center,
  120. "right" => parley::Alignment::Right,
  121. "justify" => parley::Alignment::Justify,
  122. _ => parley::Alignment::Start,
  123. };
  124. let overflow_wrap = match self.overflow_wrap.get().as_str() {
  125. "anywhere" => parley::OverflowWrap::Anywhere,
  126. "break-word" => parley::OverflowWrap::BreakWord,
  127. _ => parley::OverflowWrap::Normal,
  128. };
  129. let text = if self.use_i18n.get() {
  130. if let Some(trans) = self.i18n_fish.tr(&text) {
  131. //t!("Translate '{text}' to '{trans}'");
  132. trans
  133. } else {
  134. format!("tr err: {}", text)
  135. }
  136. } else {
  137. text
  138. };
  139. text::make_layout2(
  140. &text,
  141. text_color,
  142. font_size,
  143. lineheight,
  144. window_scale,
  145. Some(width),
  146. &[],
  147. &[],
  148. text_align,
  149. overflow_wrap,
  150. )
  151. }
  152. fn regen_mesh(&self, layout: &text::TextLayout) -> Vec<DrawInstruction> {
  153. let mut debug_opts = text::DebugRenderOptions::OFF;
  154. if self.debug.get() {
  155. debug_opts |= text::DebugRenderOptions::BASELINE;
  156. }
  157. text::render_layout_with_opts(layout, debug_opts, &self.renderer, gfxtag!("text"))
  158. }
  159. fn get_draw_calls(
  160. &self,
  161. atom: &mut PropertyAtomicGuard,
  162. parent_rect: Rectangle,
  163. ) -> Option<DrawUpdate> {
  164. // Rect property is its own memo: compare before/after eval.
  165. let prev_rect = self.rect.get();
  166. self.rect.eval(atom, &parent_rect).ok()?;
  167. let rect = self.rect.get();
  168. let rect_changed = rect != prev_rect;
  169. // Draw-path re-evaluation of the expr-bound styling props:
  170. // populates the expression caches before the first read (they are
  171. // empty at startup, before any handler has fired) and self-heals
  172. // stale caches from edge cases such as dependency rewiring. Not
  173. // needed for switch atomicity — the batch guard owns that. Cache
  174. // writes are `Role::Internal` echoes, which the
  175. // when_change_external handlers skip.
  176. self.text_color.eval(atom).expect("text_color");
  177. self.font_size.eval(atom).expect("font_size");
  178. self.lineheight.eval(atom).expect("lineheight");
  179. // Layout depends on the width, so a rect change invalidates the
  180. // layout even if the text itself did not change. Compute under the
  181. // cache lock: the compute is synchronous, so concurrent invalidations
  182. // either land before (seen as None) or after (they clear our result).
  183. if rect_changed {
  184. self.draw_cache.clear();
  185. }
  186. let (layout, mut instrs) = self.draw_cache.get_or_insert_with(|| {
  187. let layout = self.make_layout();
  188. let mut instrs = vec![DrawInstruction::Move(rect.pos())];
  189. instrs.append(&mut self.regen_mesh(&layout));
  190. (layout, instrs)
  191. });
  192. // Height output for parents that depend on it.
  193. self.height.set(atom, layout.height());
  194. if self.debug.get() {
  195. let rect = self.rect.get().with_zero_pos();
  196. let mut mesh = MeshBuilder::new(gfxtag!("text_debug-rect"));
  197. mesh.draw_outline(&rect, [0., 1., 0., 0.7], 1.);
  198. let mesh = mesh.alloc(&self.renderer).draw_untextured();
  199. instrs.push(DrawInstruction::Draw(mesh));
  200. }
  201. Some(DrawUpdate {
  202. key: self.dc_key,
  203. draw_calls: vec![(
  204. self.dc_key,
  205. DrawCall::new(instrs, vec![], self.z_index.get(), "text"),
  206. )],
  207. })
  208. }
  209. }
  210. #[async_trait]
  211. impl UIObject for Text {
  212. fn priority(&self) -> u32 {
  213. self.priority.get()
  214. }
  215. async fn start(self: Arc<Self>, ex: ExecutorPtr) {
  216. let me = Arc::downgrade(&self);
  217. let mut on_modify = OnModify::new(ex, self.node.clone(), me.clone());
  218. // Invalidate the cache, then request a pass. Internal-role echoes
  219. // (the pass's own evals) are skipped.
  220. on_modify.when_change_external(self.rect.prop(), |self_, _| async move {
  221. self_.draw_cache.clear();
  222. self_.redraw.trigger();
  223. });
  224. on_modify.when_change_external(self.z_index.prop(), |self_, _| async move {
  225. self_.draw_cache.clear();
  226. self_.redraw.trigger();
  227. });
  228. on_modify.when_change_external(self.text.prop(), |self_, _| async move {
  229. self_.draw_cache.clear();
  230. self_.redraw.trigger();
  231. });
  232. on_modify.when_change_external(self.text_align.prop(), |self_, _| async move {
  233. self_.draw_cache.clear();
  234. self_.redraw.trigger();
  235. });
  236. on_modify.when_change_external(self.font_size.prop(), |self_, _| async move {
  237. self_.draw_cache.clear();
  238. self_.redraw.trigger();
  239. });
  240. on_modify.when_change_external(self.window_scale.prop(), |self_, _| async move {
  241. self_.draw_cache.clear();
  242. self_.redraw.trigger();
  243. });
  244. on_modify.when_change_external(self.text_color.prop(), |self_, _| async move {
  245. self_.draw_cache.clear();
  246. self_.redraw.trigger();
  247. });
  248. on_modify.when_change_external(self.debug.prop(), |self_, _| async move {
  249. self_.draw_cache.clear();
  250. self_.redraw.trigger();
  251. });
  252. *self.tasks.lock() = on_modify.tasks;
  253. }
  254. fn stop(&self) {
  255. self.tasks.lock().clear();
  256. self.draw_cache.clear();
  257. }
  258. #[instrument(target = "ui::text")]
  259. async fn draw(
  260. &self,
  261. parent_rect: Rectangle,
  262. atom: &mut PropertyAtomicGuard,
  263. ) -> Option<DrawUpdate> {
  264. self.get_draw_calls(atom, parent_rect)
  265. }
  266. fn set_i18n(&self, i18n_fish: &I18nBabelFish) {
  267. self.i18n_fish.set(i18n_fish);
  268. }
  269. }
  270. impl Drop for Text {
  271. fn drop(&mut self) {
  272. self.renderer.replace_draw_calls(vec![(self.dc_key, Default::default())]);
  273. }
  274. }
  275. impl std::fmt::Debug for Text {
  276. fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
  277. write!(f, "{:?}", self.node.upgrade().unwrap())
  278. }
  279. }