chat.rs 64 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2024 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 darkfi::system::msleep;
  19. use darkfi_serial::Encodable;
  20. use sled_overlay::sled;
  21. use std::time::UNIX_EPOCH;
  22. use crate::{
  23. app::{
  24. node::{
  25. create_button, create_chatedit, create_chatview, create_editbox, create_emoji_picker,
  26. create_image, create_layer, create_shortcut, create_text, create_vector_art,
  27. },
  28. populate_tree, App,
  29. },
  30. error::Error,
  31. expr::{self, Compiler, Op},
  32. gfx::{GraphicsEventPublisherPtr, Point, Rectangle, RenderApi, Vertex},
  33. mesh::{Color, MeshBuilder},
  34. plugin::darkirc,
  35. prop::{
  36. Property, PropertyBool, PropertyFloat32, PropertyStr, PropertySubType, PropertyType, Role,
  37. },
  38. scene::{SceneNodePtr, Slot},
  39. shape,
  40. text::TextShaperPtr,
  41. ui::{
  42. emoji_picker, Button, ChatEdit, ChatView, EditBox, EmojiPicker, Image, Layer, ShapeVertex,
  43. Shortcut, Text, VectorArt, VectorShape, Window,
  44. },
  45. util::unixtime,
  46. ExecutorPtr,
  47. };
  48. use super::{ColorScheme, COLOR_SCHEME};
  49. mod android_ui_consts {
  50. use crate::gfx::{Point, Rectangle};
  51. pub const CHANNEL_LABEL_BASELINE: f32 = 82.;
  52. pub const BACKARROW_SCALE: f32 = 30.;
  53. pub const BACKARROW_X: f32 = 50.;
  54. pub const BACKARROW_Y: f32 = 70.;
  55. pub const CHATEDIT_MIN_HEIGHT: f32 = 140.;
  56. pub const CHATEDIT_HEIGHT: f32 = 140.;
  57. pub const CHATEDIT_SINGLE_LINE_Y: f32 = 120.;
  58. pub const CHATEDIT_BOTTOM_PAD: f32 = 10.;
  59. pub const CHATEDIT_CURSOR_ASCENT: f32 = 50.;
  60. pub const CHATEDIT_CURSOR_DESCENT: f32 = 20.;
  61. pub const CHATEDIT_SELECT_ASCENT: f32 = 50.;
  62. pub const CHATEDIT_SELECT_DESCENT: f32 = 20.;
  63. pub const CHATEDIT_HANDLE_DESCENT: f32 = 10.;
  64. pub const CHATEDIT_LINESPACING: f32 = 70.;
  65. pub const CHATEDIT_NEG_W: f32 = 300.;
  66. pub const CHATEDIT_LHS_PAD: f32 = 150.;
  67. pub const TEXTBAR_BASELINE: f32 = 60.;
  68. pub const TEXT_DESCENT: f32 = 20.;
  69. pub const EMOJI_BTN_X: f32 = 60.;
  70. pub const EMOJI_BG_W: f32 = 120.;
  71. pub const EMOJI_SCALE: f32 = 40.;
  72. pub const EMOJI_NEG_Y: f32 = 85.;
  73. pub const EMOJIBTN_BOX: [f32; 4] = [20., 118., 80., 75.];
  74. pub const EMOJI_CLOSE_SCALE: f32 = 20.;
  75. pub const SENDARROW_NEG_X: f32 = 80.;
  76. pub const SENDARROW_NEG_Y: f32 = 80.;
  77. pub const SENDBTN_BOX: [f32; 4] = [116., 120., 80., 70.];
  78. pub const FONTSIZE: f32 = 40.;
  79. pub const TIMESTAMP_FONTSIZE: f32 = 30.;
  80. pub const TIMESTAMP_WIDTH: f32 = 135.;
  81. pub const MESSAGE_SPACING: f32 = 15.;
  82. pub const LINE_HEIGHT: f32 = 58.;
  83. pub const CHATVIEW_BASELINE: f32 = 36.;
  84. pub const CMD_HELP_HEIGHT: f32 = 110.;
  85. pub const CMD_HELP_GAP: f32 = 10.;
  86. pub const CMD_HELP_CMD_FONTSIZE: f32 = 48.;
  87. pub const CMD_HELP_BASELINE: f32 = 74.;
  88. pub const CMD_HELP_CMD_LABEL_X_INSET: f32 = 40.;
  89. pub const CMD_HELP_NICK_CMD_WIDTH: f32 = 280.;
  90. pub const CMD_HELP_NICK_DESC_WIDTH: f32 = 1000.;
  91. pub const CMD_HELP_NICK_DESC_X: f32 = 320.;
  92. pub const ACTION_POPUP_Y_OFF: f32 = 200.;
  93. pub const ACTION_COPY_RECT: Rectangle = Rectangle::new(0., 0., 200., 160.);
  94. pub const ACTION_PASTE_RECT: Rectangle = Rectangle::new(220., 0., 240., 160.);
  95. pub const ACTION_SELECT_ALL_RECT: Rectangle = Rectangle::new(480., 0., 400., 160.);
  96. pub const ACTION_LABEL_POS: Point = Point::new(40., 92.);
  97. }
  98. #[cfg(target_os = "android")]
  99. mod ui_consts {
  100. pub use super::android_ui_consts::*;
  101. }
  102. #[cfg(feature = "emulate-android")]
  103. mod ui_consts {
  104. pub use super::android_ui_consts::*;
  105. }
  106. #[cfg(all(
  107. any(target_os = "linux", target_os = "macos", target_os = "windows"),
  108. not(feature = "emulate-android")
  109. ))]
  110. mod ui_consts {
  111. use crate::gfx::{Point, Rectangle};
  112. // Chat UI
  113. pub const CHANNEL_LABEL_BASELINE: f32 = 37.;
  114. pub const BACKARROW_SCALE: f32 = 15.;
  115. pub const BACKARROW_X: f32 = 38.;
  116. pub const BACKARROW_Y: f32 = 26.;
  117. pub const CHATEDIT_MIN_HEIGHT: f32 = 60.;
  118. pub const CHATEDIT_HEIGHT: f32 = 60.;
  119. pub const CHATEDIT_SINGLE_LINE_Y: f32 = 58.;
  120. pub const CHATEDIT_BOTTOM_PAD: f32 = 5.;
  121. pub const CHATEDIT_CURSOR_ASCENT: f32 = 25.;
  122. pub const CHATEDIT_CURSOR_DESCENT: f32 = 8.;
  123. pub const CHATEDIT_SELECT_ASCENT: f32 = 30.;
  124. pub const CHATEDIT_SELECT_DESCENT: f32 = 10.;
  125. pub const CHATEDIT_HANDLE_DESCENT: f32 = 35.;
  126. pub const CHATEDIT_LINESPACING: f32 = 35.;
  127. pub const CHATEDIT_NEG_W: f32 = 190.;
  128. pub const CHATEDIT_LHS_PAD: f32 = 100.;
  129. pub const TEXTBAR_BASELINE: f32 = 34.;
  130. pub const TEXT_DESCENT: f32 = 10.;
  131. pub const EMOJI_BTN_X: f32 = 38.;
  132. pub const EMOJI_BG_W: f32 = 80.;
  133. pub const EMOJI_SCALE: f32 = 20.;
  134. pub const EMOJI_NEG_Y: f32 = 34.;
  135. pub const EMOJIBTN_BOX: [f32; 4] = [16., 50., 44., 36.];
  136. pub const EMOJI_CLOSE_SCALE: f32 = 10.;
  137. pub const SENDARROW_NEG_X: f32 = 50.;
  138. pub const SENDARROW_NEG_Y: f32 = 32.;
  139. pub const SENDBTN_BOX: [f32; 4] = [72., 50., 45., 34.];
  140. pub const FONTSIZE: f32 = 20.;
  141. pub const TIMESTAMP_FONTSIZE: f32 = 12.;
  142. pub const TIMESTAMP_WIDTH: f32 = 60.;
  143. pub const MESSAGE_SPACING: f32 = 5.;
  144. pub const LINE_HEIGHT: f32 = 30.;
  145. pub const CHATVIEW_BASELINE: f32 = 20.;
  146. pub const CMD_HELP_HEIGHT: f32 = 55.;
  147. pub const CMD_HELP_GAP: f32 = 5.;
  148. pub const CMD_HELP_CMD_FONTSIZE: f32 = 24.;
  149. pub const CMD_HELP_BASELINE: f32 = 37.;
  150. pub const CMD_HELP_CMD_LABEL_X_INSET: f32 = 20.;
  151. pub const CMD_HELP_NICK_CMD_WIDTH: f32 = 140.;
  152. pub const CMD_HELP_NICK_DESC_WIDTH: f32 = 500.;
  153. pub const CMD_HELP_NICK_DESC_X: f32 = 160.;
  154. pub const ACTION_POPUP_Y_OFF: f32 = 100.;
  155. pub const ACTION_COPY_RECT: Rectangle = Rectangle::new(0., 0., 100., 80.);
  156. pub const ACTION_PASTE_RECT: Rectangle = Rectangle::new(110., 0., 120., 80.);
  157. pub const ACTION_SELECT_ALL_RECT: Rectangle = Rectangle::new(240., 0., 200., 80.);
  158. pub const ACTION_LABEL_POS: Point = Point::new(20., 46.);
  159. }
  160. use super::EMOJI_PICKER_ICON_SIZE;
  161. use ui_consts::*;
  162. struct Clipboard {
  163. #[cfg(not(target_os = "android"))]
  164. clip: arboard::Clipboard,
  165. }
  166. impl Clipboard {
  167. fn new() -> Self {
  168. Self {
  169. #[cfg(not(target_os = "android"))]
  170. clip: arboard::Clipboard::new().unwrap(),
  171. }
  172. }
  173. fn get(&mut self) -> Option<String> {
  174. #[cfg(target_os = "android")]
  175. return miniquad::window::clipboard_get();
  176. #[cfg(not(target_os = "android"))]
  177. return self.clip.get_text().ok();
  178. }
  179. fn set(&mut self, data: &str) {
  180. #[cfg(target_os = "android")]
  181. return miniquad::window::clipboard_set(data);
  182. #[cfg(not(target_os = "android"))]
  183. return self.clip.set_text(data).unwrap();
  184. }
  185. }
  186. fn android_keyboard_height() -> f32 {
  187. #[cfg(target_os = "android")]
  188. return crate::android::get_keyboard_height() as f32;
  189. unreachable!()
  190. }
  191. pub async fn make(
  192. app: &App,
  193. window: SceneNodePtr,
  194. channel: &str,
  195. db: &sled::Db,
  196. emoji_meshes: emoji_picker::EmojiMeshesPtr,
  197. ) {
  198. let window_scale = PropertyFloat32::wrap(&window, Role::Internal, "scale", 0).unwrap();
  199. let mut cc = Compiler::new();
  200. cc.add_const_f32("CHATEDIT_HEIGHT", CHATEDIT_HEIGHT);
  201. cc.add_const_f32("CHATEDIT_SINGLE_LINE_Y", CHATEDIT_SINGLE_LINE_Y);
  202. cc.add_const_f32("CHATEDIT_BOTTOM_PAD", CHATEDIT_BOTTOM_PAD);
  203. cc.add_const_f32("CHATEDIT_NEG_W", CHATEDIT_NEG_W);
  204. cc.add_const_f32("CHATEDIT_MIN_HEIGHT", CHATEDIT_MIN_HEIGHT);
  205. cc.add_const_f32("SENDARROW_NEG_X", SENDARROW_NEG_X);
  206. cc.add_const_f32("SENDARROW_NEG_Y", SENDARROW_NEG_Y);
  207. cc.add_const_f32("EMOJI_NEG_Y", EMOJI_NEG_Y);
  208. cc.add_const_f32("EMOJIBTN_BOX_1", EMOJIBTN_BOX[1]);
  209. cc.add_const_f32("SENDBTN_BOX_0", SENDBTN_BOX[0]);
  210. cc.add_const_f32("SENDBTN_BOX_1", SENDBTN_BOX[1]);
  211. cc.add_const_f32("ACTION_POPUP_Y_OFF", ACTION_POPUP_Y_OFF);
  212. cc.add_const_f32("CMD_HELP_HEIGHT", CMD_HELP_HEIGHT);
  213. cc.add_const_f32("CMD_HELP_GAP", CMD_HELP_GAP);
  214. // Main view
  215. let layer_node = create_layer(&(channel.to_string() + "_chat_layer"));
  216. let prop = layer_node.get_property("rect").unwrap();
  217. prop.set_f32(Role::App, 0, 0.).unwrap();
  218. prop.set_f32(Role::App, 1, 0.).unwrap();
  219. prop.set_expr(Role::App, 2, expr::load_var("w")).unwrap();
  220. prop.set_expr(Role::App, 3, expr::load_var("h")).unwrap();
  221. layer_node.set_property_bool(Role::App, "is_visible", false).unwrap();
  222. layer_node.set_property_u32(Role::App, "z_index", 1).unwrap();
  223. let layer_node =
  224. layer_node.setup(|me| Layer::new(me, app.render_api.clone(), app.ex.clone())).await;
  225. window.link(layer_node.clone());
  226. // Create the toolbar bg
  227. let node = create_vector_art("toolbar_bg");
  228. let prop = node.get_property("rect").unwrap();
  229. prop.set_f32(Role::App, 0, 0.).unwrap();
  230. prop.set_f32(Role::App, 1, 0.).unwrap();
  231. prop.set_expr(Role::App, 2, expr::load_var("w")).unwrap();
  232. prop.set_f32(Role::App, 3, CHATEDIT_HEIGHT).unwrap();
  233. node.set_property_u32(Role::App, "z_index", 2).unwrap();
  234. let (bg_color, sep_color) = match COLOR_SCHEME {
  235. ColorScheme::DarkMode => ([0., 0.11, 0.11, 1.], [0.41, 0.6, 0.65, 1.]),
  236. ColorScheme::PaperLight => ([1., 1., 1., 1.], [0., 0.6, 0.65, 1.]),
  237. };
  238. let mut shape = VectorShape::new();
  239. shape.add_filled_box(
  240. expr::const_f32(0.),
  241. expr::const_f32(0.),
  242. expr::const_f32(EMOJI_BG_W),
  243. expr::load_var("h"),
  244. bg_color,
  245. );
  246. shape.add_filled_box(
  247. expr::const_f32(EMOJI_BG_W),
  248. expr::const_f32(0.),
  249. expr::const_f32(EMOJI_BG_W + 1.),
  250. expr::load_var("h"),
  251. sep_color,
  252. );
  253. shape.add_filled_box(
  254. expr::const_f32(0.),
  255. expr::load_var("h"),
  256. expr::load_var("w"),
  257. cc.compile("h + 1").unwrap(),
  258. sep_color,
  259. );
  260. let node =
  261. node.setup(|me| VectorArt::new(me, shape, app.render_api.clone(), app.ex.clone())).await;
  262. layer_node.clone().link(node);
  263. // Create the send button
  264. let node = create_vector_art("back_btn_bg");
  265. let prop = node.get_property("rect").unwrap();
  266. prop.set_f32(Role::App, 0, BACKARROW_X).unwrap();
  267. prop.set_f32(Role::App, 1, BACKARROW_Y).unwrap();
  268. prop.set_f32(Role::App, 2, 500.).unwrap();
  269. prop.set_f32(Role::App, 3, 500.).unwrap();
  270. node.set_property_u32(Role::App, "z_index", 3).unwrap();
  271. let shape = shape::create_back_arrow().scaled(BACKARROW_SCALE);
  272. let node =
  273. node.setup(|me| VectorArt::new(me, shape, app.render_api.clone(), app.ex.clone())).await;
  274. layer_node.clone().link(node);
  275. // Create the back button
  276. let node = create_button("back_btn");
  277. node.set_property_bool(Role::App, "is_active", true).unwrap();
  278. let prop = node.get_property("rect").unwrap();
  279. prop.set_f32(Role::App, 0, 0.).unwrap();
  280. prop.set_f32(Role::App, 1, 0.).unwrap();
  281. prop.set_f32(Role::App, 2, EMOJI_BG_W).unwrap();
  282. prop.set_f32(Role::App, 3, CHATEDIT_HEIGHT).unwrap();
  283. #[derive(Clone)]
  284. struct GoBack {
  285. sg_root: SceneNodePtr,
  286. chatview_is_visible: PropertyBool,
  287. }
  288. impl GoBack {
  289. fn go(&self) {
  290. info!(target: "app::chat", "clicked back");
  291. let menu_node = self.sg_root.clone().lookup_node("/window/menu_layer").unwrap();
  292. menu_node.set_property_bool(Role::App, "is_visible", true).unwrap();
  293. self.chatview_is_visible.set(false);
  294. }
  295. }
  296. let goback = GoBack {
  297. sg_root: app.sg_root.clone(),
  298. chatview_is_visible: PropertyBool::wrap(&layer_node, Role::App, "is_visible", 0).unwrap(),
  299. };
  300. let (slot, recvr) = Slot::new("back_clicked");
  301. node.register("click", slot).unwrap();
  302. let goback2 = goback.clone();
  303. // Menu doesn't exist yet ;)
  304. let listen_click = app.ex.spawn(async move {
  305. while let Ok(_) = recvr.recv().await {
  306. goback2.go();
  307. }
  308. });
  309. app.tasks.lock().unwrap().push(listen_click);
  310. let node = node.setup(|me| Button::new(me, app.ex.clone())).await;
  311. layer_node.clone().link(node);
  312. // Create shortcut to go back as well
  313. let node = create_shortcut("back_shortcut");
  314. #[cfg(target_os = "android")]
  315. node.set_property_str(Role::App, "key", "back").unwrap();
  316. #[cfg(not(target_os = "android"))]
  317. node.set_property_str(Role::App, "key", "alt+left").unwrap();
  318. let (slot, recvr) = Slot::new("back_pressed");
  319. node.register("shortcut", slot).unwrap();
  320. let listen_enter = app.ex.spawn(async move {
  321. while let Ok(_) = recvr.recv().await {
  322. goback.go();
  323. }
  324. });
  325. app.tasks.lock().unwrap().push(listen_enter);
  326. let node = node.setup(|me| Shortcut::new(me)).await;
  327. layer_node.clone().link(node);
  328. // Create some text
  329. let node = create_text("channel_label");
  330. let prop = node.get_property("rect").unwrap();
  331. prop.set_f32(Role::App, 0, CHATEDIT_LHS_PAD).unwrap();
  332. prop.set_f32(Role::App, 1, 0.).unwrap();
  333. prop.set_expr(Role::App, 2, expr::load_var("w")).unwrap();
  334. prop.set_f32(Role::App, 3, CHATEDIT_HEIGHT).unwrap();
  335. node.set_property_f32(Role::App, "baseline", CHANNEL_LABEL_BASELINE).unwrap();
  336. node.set_property_f32(Role::App, "font_size", FONTSIZE).unwrap();
  337. node.set_property_str(Role::App, "text", &("#".to_string() + channel)).unwrap();
  338. //node.set_property_bool(Role::App, "debug", true).unwrap();
  339. //node.set_property_str(Role::App, "text", "anon1").unwrap();
  340. let prop = node.get_property("text_color").unwrap();
  341. if COLOR_SCHEME == ColorScheme::DarkMode {
  342. prop.set_f32(Role::App, 0, 1.).unwrap();
  343. prop.set_f32(Role::App, 1, 1.).unwrap();
  344. prop.set_f32(Role::App, 2, 1.).unwrap();
  345. prop.set_f32(Role::App, 3, 1.).unwrap();
  346. } else if COLOR_SCHEME == ColorScheme::PaperLight {
  347. prop.set_f32(Role::App, 0, 0.).unwrap();
  348. prop.set_f32(Role::App, 1, 0.).unwrap();
  349. prop.set_f32(Role::App, 2, 0.).unwrap();
  350. prop.set_f32(Role::App, 3, 1.).unwrap();
  351. }
  352. node.set_property_u32(Role::App, "z_index", 3).unwrap();
  353. let node = node
  354. .setup(|me| {
  355. Text::new(
  356. me,
  357. window_scale.clone(),
  358. app.render_api.clone(),
  359. app.text_shaper.clone(),
  360. app.ex.clone(),
  361. )
  362. })
  363. .await;
  364. layer_node.clone().link(node);
  365. // Create the emoji picker
  366. let mut node = create_emoji_picker("emoji_picker");
  367. let prop = Property::new("dynamic_h", PropertyType::Float32, PropertySubType::Pixel);
  368. node.add_property(prop).unwrap();
  369. let emoji_dynamic_h_prop = node.get_property("dynamic_h").unwrap();
  370. //emoji_dynamic_h_prop.set_f32(Role::App, 0, 400.).unwrap();
  371. let prop = node.get_property("rect").unwrap();
  372. prop.set_f32(Role::App, 0, 0.).unwrap();
  373. let code = cc.compile("h - dynamic_h").unwrap();
  374. prop.set_expr(Role::App, 1, code).unwrap();
  375. prop.set_expr(Role::App, 2, expr::load_var("w")).unwrap();
  376. prop.set_expr(Role::App, 3, expr::load_var("dynamic_h")).unwrap();
  377. prop.add_depend(&emoji_dynamic_h_prop, 0, "dynamic_h");
  378. let emoji_h_prop = PropertyFloat32::wrap(&node, Role::App, "dynamic_h", 0).unwrap();
  379. //node.set_property_f32(Role::App, "baseline", CHANNEL_LABEL_BASELINE).unwrap();
  380. //node.set_property_f32(Role::App, "font_size", FONTSIZE).unwrap();
  381. node.set_property_f32(Role::App, "emoji_size", EMOJI_PICKER_ICON_SIZE).unwrap();
  382. node.set_property_u32(Role::App, "z_index", 2).unwrap();
  383. let node = node
  384. .setup(|me| {
  385. EmojiPicker::new(
  386. me,
  387. window_scale.clone(),
  388. app.render_api.clone(),
  389. emoji_meshes,
  390. app.ex.clone(),
  391. )
  392. })
  393. .await;
  394. let emoji_picker_node = node.clone();
  395. layer_node.clone().link(node);
  396. // Create the editbox bg
  397. let node = create_vector_art("emoji_picker_bg");
  398. let prop = node.get_property("rect").unwrap();
  399. prop.set_f32(Role::App, 0, 0.).unwrap();
  400. let code = cc.compile("h - dynamic_h").unwrap();
  401. prop.set_expr(Role::App, 1, code).unwrap();
  402. prop.set_expr(Role::App, 2, expr::load_var("w")).unwrap();
  403. prop.set_expr(Role::App, 3, expr::load_var("dynamic_h")).unwrap();
  404. prop.add_depend(&emoji_dynamic_h_prop, 0, "dynamic_h");
  405. node.set_property_u32(Role::App, "z_index", 2).unwrap();
  406. let mut shape = VectorShape::new();
  407. // Main bg
  408. shape.add_filled_box(
  409. expr::const_f32(0.),
  410. expr::const_f32(0.),
  411. expr::load_var("w"),
  412. expr::load_var("h"),
  413. [0., 0.11, 0.11, 1.],
  414. );
  415. // Top line
  416. shape.add_filled_box(
  417. expr::const_f32(EMOJI_BG_W),
  418. expr::const_f32(0.),
  419. expr::load_var("w"),
  420. expr::const_f32(1.),
  421. [0.41, 0.6, 0.65, 1.],
  422. );
  423. // Bottom line
  424. //shape.add_filled_box(
  425. // expr::const_f32(0.),
  426. // cc.compile("h - 1").unwrap(),
  427. // expr::load_var("w"),
  428. // expr::load_var("h"),
  429. // [0.41, 0.6, 0.65, 1.],
  430. //);
  431. let node =
  432. node.setup(|me| VectorArt::new(me, shape, app.render_api.clone(), app.ex.clone())).await;
  433. layer_node.clone().link(node);
  434. // Main content view
  435. let chat_layer_node = layer_node;
  436. let layer_node = create_layer("content");
  437. let prop = layer_node.get_property("rect").unwrap();
  438. prop.set_f32(Role::App, 0, 0.).unwrap();
  439. prop.set_f32(Role::App, 1, 0.).unwrap();
  440. prop.set_expr(Role::App, 2, expr::load_var("w")).unwrap();
  441. let code = cc.compile("h - emoji_h").unwrap();
  442. prop.set_expr(Role::App, 3, code).unwrap();
  443. prop.add_depend(&emoji_dynamic_h_prop, 0, "emoji_h");
  444. layer_node.set_property_bool(Role::App, "is_visible", true).unwrap();
  445. layer_node.set_property_u32(Role::App, "z_index", 1).unwrap();
  446. layer_node.set_property_u32(Role::App, "priority", 1).unwrap();
  447. let layer_node =
  448. layer_node.setup(|me| Layer::new(me, app.render_api.clone(), app.ex.clone())).await;
  449. chat_layer_node.clone().link(layer_node.clone());
  450. // ChatView
  451. let node = create_chatview("chatty");
  452. let prop = node.get_property("rect").unwrap();
  453. prop.set_f32(Role::App, 0, 10.).unwrap();
  454. prop.set_f32(Role::App, 1, CHATEDIT_HEIGHT).unwrap();
  455. let code = cc.compile("w - 30").unwrap();
  456. prop.set_expr(Role::App, 2, code).unwrap();
  457. let code = cc
  458. .compile(
  459. "
  460. height = if editz_h < CHATEDIT_MIN_HEIGHT {
  461. CHATEDIT_MIN_HEIGHT
  462. } else {
  463. editz_h
  464. };
  465. h - CHATEDIT_HEIGHT - height - 2 * CHATEDIT_BOTTOM_PAD
  466. ",
  467. )
  468. .unwrap();
  469. prop.set_expr(Role::App, 3, code).unwrap();
  470. let chatview_rect_prop = prop.clone();
  471. node.set_property_f32(Role::App, "font_size", FONTSIZE).unwrap();
  472. node.set_property_f32(Role::App, "timestamp_font_size", TIMESTAMP_FONTSIZE).unwrap();
  473. node.set_property_f32(Role::App, "timestamp_width", TIMESTAMP_WIDTH).unwrap();
  474. node.set_property_f32(Role::App, "line_height", LINE_HEIGHT).unwrap();
  475. node.set_property_f32(Role::App, "message_spacing", MESSAGE_SPACING).unwrap();
  476. node.set_property_f32(Role::App, "baseline", CHATVIEW_BASELINE).unwrap();
  477. node.set_property_u32(Role::App, "z_index", 2).unwrap();
  478. //node.set_property_bool(Role::App, "debug", true).unwrap();
  479. #[cfg(target_os = "android")]
  480. node.set_property_f32(Role::App, "scroll_start_accel", 40.).unwrap();
  481. #[cfg(target_os = "linux")]
  482. node.set_property_f32(Role::App, "scroll_start_accel", 15.).unwrap();
  483. node.set_property_f32(Role::App, "scroll_resist", 0.9).unwrap();
  484. let prop = node.get_property("timestamp_color").unwrap();
  485. prop.set_f32(Role::App, 0, 0.407).unwrap();
  486. prop.set_f32(Role::App, 1, 0.604).unwrap();
  487. prop.set_f32(Role::App, 2, 0.647).unwrap();
  488. prop.set_f32(Role::App, 3, 1.).unwrap();
  489. let prop = node.get_property("text_color").unwrap();
  490. if COLOR_SCHEME == ColorScheme::PaperLight {
  491. prop.set_f32(Role::App, 0, 0.).unwrap();
  492. prop.set_f32(Role::App, 1, 0.).unwrap();
  493. prop.set_f32(Role::App, 2, 0.).unwrap();
  494. prop.set_f32(Role::App, 3, 1.).unwrap();
  495. } else if COLOR_SCHEME == ColorScheme::DarkMode {
  496. prop.set_f32(Role::App, 0, 1.).unwrap();
  497. prop.set_f32(Role::App, 1, 1.).unwrap();
  498. prop.set_f32(Role::App, 2, 1.).unwrap();
  499. prop.set_f32(Role::App, 3, 1.).unwrap();
  500. }
  501. let prop = node.get_property("nick_colors").unwrap();
  502. #[rustfmt::skip]
  503. let nick_colors = [
  504. 0.00, 0.94, 1.00, 1.,
  505. 0.36, 1.00, 0.69, 1.,
  506. 0.29, 1.00, 0.45, 1.,
  507. 0.00, 0.73, 0.38, 1.,
  508. 0.21, 0.67, 0.67, 1.,
  509. 0.56, 0.61, 1.00, 1.,
  510. 0.84, 0.48, 1.00, 1.,
  511. 1.00, 0.61, 0.94, 1.,
  512. 1.00, 0.36, 0.48, 1.,
  513. 1.00, 0.30, 0.00, 1.
  514. ];
  515. for c in nick_colors {
  516. prop.push_f32(Role::App, c).unwrap();
  517. }
  518. let prop = node.get_property("hi_bg_color").unwrap();
  519. if COLOR_SCHEME == ColorScheme::PaperLight {
  520. prop.set_f32(Role::App, 0, 0.5).unwrap();
  521. prop.set_f32(Role::App, 1, 0.5).unwrap();
  522. prop.set_f32(Role::App, 2, 0.5).unwrap();
  523. prop.set_f32(Role::App, 3, 1.).unwrap();
  524. } else if COLOR_SCHEME == ColorScheme::DarkMode {
  525. prop.set_f32(Role::App, 0, 0.5).unwrap();
  526. prop.set_f32(Role::App, 1, 0.5).unwrap();
  527. prop.set_f32(Role::App, 2, 0.5).unwrap();
  528. prop.set_f32(Role::App, 3, 1.).unwrap();
  529. }
  530. let tree_name = channel.to_string() + "__chat_tree";
  531. let chat_tree = db.open_tree(tree_name.as_bytes()).unwrap();
  532. //if chat_tree.is_empty() {
  533. // populate_tree(&chat_tree);
  534. //}
  535. debug!(target: "app", "db has {} lines", chat_tree.len());
  536. let chatview_node = node
  537. .setup(|me| {
  538. ChatView::new(
  539. me,
  540. chat_tree,
  541. window_scale.clone(),
  542. app.render_api.clone(),
  543. app.text_shaper.clone(),
  544. app.ex.clone(),
  545. )
  546. })
  547. .await;
  548. layer_node.clone().link(chatview_node.clone());
  549. // Create the editbox bg
  550. let node = create_vector_art("editbox_bg");
  551. let prop = node.get_property("rect").unwrap();
  552. prop.set_f32(Role::App, 0, 0.).unwrap();
  553. let code = cc
  554. .compile(
  555. "
  556. height = if editz_h < CHATEDIT_MIN_HEIGHT {
  557. CHATEDIT_MIN_HEIGHT
  558. } else {
  559. editz_h
  560. };
  561. h - height - 2 * CHATEDIT_BOTTOM_PAD
  562. ",
  563. )
  564. .unwrap();
  565. prop.set_expr(Role::App, 1, code).unwrap();
  566. prop.set_expr(Role::App, 2, expr::load_var("w")).unwrap();
  567. let code = cc
  568. .compile(
  569. "
  570. height = if editz_h < CHATEDIT_MIN_HEIGHT {
  571. CHATEDIT_MIN_HEIGHT
  572. } else {
  573. editz_h
  574. };
  575. height + 2 * CHATEDIT_BOTTOM_PAD
  576. ",
  577. )
  578. .unwrap();
  579. prop.set_expr(Role::App, 3, code).unwrap();
  580. node.set_property_u32(Role::App, "z_index", 4).unwrap();
  581. node.set_property_u32(Role::App, "priority", 2).unwrap();
  582. let editbox_bg_rect_prop = prop.clone();
  583. let (bg_color, lhs_bg_color, line_color) = match COLOR_SCHEME {
  584. ColorScheme::DarkMode => {
  585. ([0., 0.13, 0.08, 1.], [0., 0.11, 0.11, 1.], [0.41, 0.6, 0.65, 1.])
  586. }
  587. ColorScheme::PaperLight => ([1., 1., 1., 1.], [1., 1., 1., 1.], [0., 0., 0., 1.]),
  588. };
  589. let mut shape = VectorShape::new();
  590. // Main green background
  591. shape.add_filled_box(
  592. expr::const_f32(0.),
  593. expr::const_f32(0.),
  594. expr::load_var("w"),
  595. expr::load_var("h"),
  596. bg_color,
  597. );
  598. // Left hand darker box
  599. shape.add_filled_box(
  600. expr::const_f32(0.),
  601. expr::const_f32(1.),
  602. expr::const_f32(EMOJI_BG_W),
  603. expr::load_var("h"),
  604. lhs_bg_color,
  605. );
  606. // Top line
  607. shape.add_filled_box(
  608. expr::const_f32(0.),
  609. expr::const_f32(0.),
  610. expr::load_var("w"),
  611. expr::const_f32(1.),
  612. line_color,
  613. );
  614. // Side line
  615. shape.add_filled_box(
  616. expr::const_f32(EMOJI_BG_W),
  617. expr::const_f32(0.),
  618. expr::const_f32(EMOJI_BG_W + 1.),
  619. expr::load_var("h"),
  620. line_color,
  621. );
  622. // Bottom line
  623. //shape.add_filled_box(
  624. // expr::const_f32(0.),
  625. // cc.compile("h - 1").unwrap(),
  626. // expr::load_var("w"),
  627. // expr::load_var("h"),
  628. // [0.41, 0.6, 0.65, 1.],
  629. //);
  630. let node =
  631. node.setup(|me| VectorArt::new(me, shape, app.render_api.clone(), app.ex.clone())).await;
  632. layer_node.clone().link(node);
  633. // Create the send button
  634. let node = create_vector_art("send_btn_bg");
  635. let prop = node.get_property("rect").unwrap();
  636. let code = cc.compile("w - SENDARROW_NEG_X").unwrap();
  637. prop.set_expr(Role::App, 0, code).unwrap();
  638. let code = cc.compile("h - SENDARROW_NEG_Y").unwrap();
  639. prop.set_expr(Role::App, 1, code).unwrap();
  640. prop.set_f32(Role::App, 2, 500.).unwrap();
  641. prop.set_f32(Role::App, 3, 500.).unwrap();
  642. node.set_property_u32(Role::App, "z_index", 5).unwrap();
  643. let shape = shape::create_send_arrow().scaled(EMOJI_SCALE);
  644. let node =
  645. node.setup(|me| VectorArt::new(me, shape, app.render_api.clone(), app.ex.clone())).await;
  646. layer_node.clone().link(node);
  647. // Create the emoji button
  648. let node = create_vector_art("emoji_btn_bg");
  649. let emoji_btn_is_visible = PropertyBool::wrap(&node, Role::App, "is_visible", 0).unwrap();
  650. let prop = node.get_property("rect").unwrap();
  651. prop.set_f32(Role::App, 0, EMOJI_BTN_X).unwrap();
  652. let code = cc.compile("h - EMOJI_NEG_Y").unwrap();
  653. prop.set_expr(Role::App, 1, code).unwrap();
  654. prop.set_f32(Role::App, 2, 500.).unwrap();
  655. prop.set_f32(Role::App, 3, 500.).unwrap();
  656. node.set_property_u32(Role::App, "z_index", 5).unwrap();
  657. let color = match COLOR_SCHEME {
  658. ColorScheme::DarkMode => [0., 1., 1., 1.],
  659. ColorScheme::PaperLight => [0., 0., 0., 1.],
  660. };
  661. let shape = shape::create_emoji_selector(color).scaled(EMOJI_SCALE);
  662. let node =
  663. node.setup(|me| VectorArt::new(me, shape, app.render_api.clone(), app.ex.clone())).await;
  664. layer_node.clone().link(node);
  665. // Create the emoji button
  666. let node = create_vector_art("emoji_close_btn_bg");
  667. node.set_property_bool(Role::App, "is_visible", false).unwrap();
  668. let prop = node.get_property("rect").unwrap();
  669. let emoji_close_is_visible = PropertyBool::wrap(&node, Role::App, "is_visible", 0).unwrap();
  670. prop.set_f32(Role::App, 0, EMOJI_BTN_X).unwrap();
  671. let code = cc.compile("h - EMOJI_NEG_Y").unwrap();
  672. prop.set_expr(Role::App, 1, code).unwrap();
  673. prop.set_f32(Role::App, 2, 500.).unwrap();
  674. prop.set_f32(Role::App, 3, 500.).unwrap();
  675. node.set_property_u32(Role::App, "z_index", 5).unwrap();
  676. let shape = shape::create_close_icon().scaled(EMOJI_CLOSE_SCALE);
  677. let node =
  678. node.setup(|me| VectorArt::new(me, shape, app.render_api.clone(), app.ex.clone())).await;
  679. layer_node.clone().link(node);
  680. // Text edit
  681. let node = create_chatedit("editz");
  682. node.set_property_bool(Role::App, "is_active", true).unwrap();
  683. node.set_property_bool(Role::App, "is_focused", true).unwrap();
  684. node.set_property_f32(Role::App, "max_height", 600.).unwrap();
  685. let prop = node.get_property("rect").unwrap();
  686. prop.set_f32(Role::App, 0, CHATEDIT_LHS_PAD).unwrap();
  687. let code = cc
  688. .compile(
  689. "
  690. height = if rect_h < CHATEDIT_MIN_HEIGHT {
  691. CHATEDIT_SINGLE_LINE_Y
  692. } else {
  693. rect_h
  694. };
  695. parent_h - (height + CHATEDIT_BOTTOM_PAD)
  696. ",
  697. )
  698. .unwrap();
  699. prop.set_expr(Role::App, 1, code).unwrap();
  700. let code = cc.compile("parent_w - CHATEDIT_NEG_W").unwrap();
  701. prop.set_expr(Role::App, 2, code).unwrap();
  702. prop.set_f32(Role::App, 3, CHATEDIT_HEIGHT).unwrap();
  703. chatview_rect_prop.add_depend(&prop, 3, "editz_h");
  704. editbox_bg_rect_prop.add_depend(&prop, 3, "editz_h");
  705. let prop = node.get_property("padding").unwrap();
  706. prop.set_f32(Role::App, 0, 0.).unwrap();
  707. prop.set_f32(Role::App, 1, TEXTBAR_BASELINE / 2.).unwrap();
  708. node.set_property_f32(Role::App, "baseline", TEXTBAR_BASELINE).unwrap();
  709. node.set_property_f32(Role::App, "linespacing", CHATEDIT_LINESPACING).unwrap();
  710. node.set_property_f32(Role::App, "descent", TEXT_DESCENT).unwrap();
  711. node.set_property_f32(Role::App, "font_size", FONTSIZE).unwrap();
  712. //node.set_property_str(Role::App, "text", "hello king!😁🍆jelly 🍆1234").unwrap();
  713. let prop = node.get_property("text_color").unwrap();
  714. if COLOR_SCHEME == ColorScheme::PaperLight {
  715. prop.set_f32(Role::App, 0, 0.).unwrap();
  716. prop.set_f32(Role::App, 1, 0.).unwrap();
  717. prop.set_f32(Role::App, 2, 0.).unwrap();
  718. prop.set_f32(Role::App, 3, 1.).unwrap();
  719. } else if COLOR_SCHEME == ColorScheme::DarkMode {
  720. prop.set_f32(Role::App, 0, 1.).unwrap();
  721. prop.set_f32(Role::App, 1, 1.).unwrap();
  722. prop.set_f32(Role::App, 2, 1.).unwrap();
  723. prop.set_f32(Role::App, 3, 1.).unwrap();
  724. }
  725. let prop = node.get_property("text_hi_color").unwrap();
  726. prop.set_f32(Role::App, 0, 0.44).unwrap();
  727. prop.set_f32(Role::App, 1, 0.96).unwrap();
  728. prop.set_f32(Role::App, 2, 1.).unwrap();
  729. prop.set_f32(Role::App, 3, 1.).unwrap();
  730. let prop = node.get_property("text_cmd_color").unwrap();
  731. prop.set_f32(Role::App, 0, 0.64).unwrap();
  732. prop.set_f32(Role::App, 1, 1.).unwrap();
  733. prop.set_f32(Role::App, 2, 0.83).unwrap();
  734. prop.set_f32(Role::App, 3, 1.).unwrap();
  735. let prop = node.get_property("cursor_color").unwrap();
  736. prop.set_f32(Role::App, 0, 0.816).unwrap();
  737. prop.set_f32(Role::App, 1, 0.627).unwrap();
  738. prop.set_f32(Role::App, 2, 1.).unwrap();
  739. prop.set_f32(Role::App, 3, 1.).unwrap();
  740. node.set_property_f32(Role::App, "cursor_ascent", CHATEDIT_CURSOR_ASCENT).unwrap();
  741. node.set_property_f32(Role::App, "cursor_descent", CHATEDIT_CURSOR_DESCENT).unwrap();
  742. node.set_property_f32(Role::App, "select_ascent", CHATEDIT_SELECT_ASCENT).unwrap();
  743. node.set_property_f32(Role::App, "select_descent", CHATEDIT_SELECT_DESCENT).unwrap();
  744. node.set_property_f32(Role::App, "handle_descent", CHATEDIT_HANDLE_DESCENT).unwrap();
  745. let prop = node.get_property("hi_bg_color").unwrap();
  746. if COLOR_SCHEME == ColorScheme::PaperLight {
  747. prop.set_f32(Role::App, 0, 0.5).unwrap();
  748. prop.set_f32(Role::App, 1, 0.5).unwrap();
  749. prop.set_f32(Role::App, 2, 0.5).unwrap();
  750. prop.set_f32(Role::App, 3, 1.).unwrap();
  751. } else if COLOR_SCHEME == ColorScheme::DarkMode {
  752. prop.set_f32(Role::App, 0, 0.).unwrap();
  753. prop.set_f32(Role::App, 1, 0.27).unwrap();
  754. prop.set_f32(Role::App, 2, 0.22).unwrap();
  755. prop.set_f32(Role::App, 3, 1.).unwrap();
  756. }
  757. let prop = node.get_property("cmd_bg_color").unwrap();
  758. if COLOR_SCHEME == ColorScheme::PaperLight {
  759. prop.set_f32(Role::App, 0, 0.5).unwrap();
  760. prop.set_f32(Role::App, 1, 0.5).unwrap();
  761. prop.set_f32(Role::App, 2, 0.5).unwrap();
  762. prop.set_f32(Role::App, 3, 1.).unwrap();
  763. } else if COLOR_SCHEME == ColorScheme::DarkMode {
  764. prop.set_f32(Role::App, 0, 0.).unwrap();
  765. prop.set_f32(Role::App, 1, 0.30).unwrap();
  766. prop.set_f32(Role::App, 2, 0.25).unwrap();
  767. prop.set_f32(Role::App, 3, 1.).unwrap();
  768. }
  769. node.set_property_u32(Role::App, "z_index", 6).unwrap();
  770. node.set_property_u32(Role::App, "priority", 3).unwrap();
  771. //node.set_property_bool(Role::App, "debug", true).unwrap();
  772. let editz_text = PropertyStr::wrap(&node, Role::App, "text", 0).unwrap();
  773. let editz_select_text = node.get_property("select_text").unwrap();
  774. let editz_cursor_pos = node.get_property("cursor_pos").unwrap();
  775. //let editbox_focus = PropertyBool::wrap(node, Role::App, "is_focused", 0).unwrap();
  776. //let darkirc_backend = app.darkirc_backend.clone();
  777. //let task = app.ex.spawn(async move {
  778. // while let Ok(_) = btn_click_recvr.recv().await {
  779. // let text = editbox_text.get();
  780. // editbox_text.prop().unset(Role::App, 0).unwrap();
  781. // // Clicking outside the editbox makes it lose focus
  782. // // So lets focus it again
  783. // editbox_focus.set(true);
  784. // debug!(target: "app", "sending text {text}");
  785. // let privmsg =
  786. // Privmsg { channel: "#random".to_string(), nick: "king".to_string(), msg: text };
  787. // darkirc_backend.send(privmsg).await;
  788. // }
  789. //});
  790. //tasks.push(task);
  791. let node = node
  792. .setup(|me| {
  793. ChatEdit::new(
  794. me,
  795. window_scale.clone(),
  796. app.render_api.clone(),
  797. app.text_shaper.clone(),
  798. app.ex.clone(),
  799. )
  800. })
  801. .await;
  802. let chatedit_node = node.clone();
  803. layer_node.clone().link(node);
  804. let (slot, recvr) = Slot::new("emoji_selected");
  805. emoji_picker_node.register("emoji_select", slot).unwrap();
  806. let chatedit_node2 = chatedit_node.clone();
  807. let listen_click = app.ex.spawn(async move {
  808. while let Ok(data) = recvr.recv().await {
  809. // No need to decode the data. Just pass it straight along
  810. chatedit_node2.call_method("insert_text", data).await.unwrap();
  811. }
  812. });
  813. app.tasks.lock().unwrap().push(listen_click);
  814. // No way to get the top of the editbox until eval has been called.
  815. // But this is on top so it happens before the eval. So the value is 0.
  816. // Not sure the best way to fix this.
  817. /*
  818. // Create the editbox fg shadow
  819. let node = create_vector_art("editbox_fg");
  820. let prop = node.get_property("rect").unwrap();
  821. prop.set_f32(Role::App, 0, EMOJI_BG_W).unwrap();
  822. let code = cc.compile("editz_bg_top_y").unwrap();
  823. prop.set_expr(Role::App, 1, code).unwrap();
  824. prop.set_expr(Role::App, 2, expr::load_var("w")).unwrap();
  825. prop.set_f32(Role::App, 3, 300.).unwrap();
  826. //prop.set_expr(Role::App, 2, expr::load_var("w")).unwrap();
  827. //prop.set_expr(Role::App, 3, expr::load_var("editz_bg_h")).unwrap();
  828. node.set_property_u32(Role::App, "z_index", 4).unwrap();
  829. prop.add_depend(&editbox_bg_rect_prop, 1, "editz_bg_top_y");
  830. prop.add_depend(&editbox_bg_rect_prop, 3, "editz_bg_h");
  831. let mut shape = VectorShape::new();
  832. // Left hand darker box
  833. shape.add_gradient_box(
  834. expr::const_f32(0.),
  835. expr::const_f32(0.),
  836. expr::load_var("w"),
  837. expr::const_f32(40.),
  838. [
  839. [0., 0., 0., 1.],
  840. [0., 0., 0., 1.],
  841. [0., 0., 0., 0.],
  842. [0., 0., 0., 0.],
  843. ]
  844. );
  845. let node =
  846. node.setup(|me| VectorArt::new(me, shape, app.render_api.clone(), app.ex.clone())).await;
  847. layer_node.clone().link(node);
  848. */
  849. // Create the send button
  850. let node = create_button("send_btn");
  851. node.set_property_bool(Role::App, "is_active", true).unwrap();
  852. let prop = node.get_property("rect").unwrap();
  853. let code = cc.compile("w - SENDBTN_BOX_0").unwrap();
  854. prop.set_expr(Role::App, 0, code).unwrap();
  855. let code = cc.compile("h - SENDBTN_BOX_1").unwrap();
  856. prop.set_expr(Role::App, 1, code).unwrap();
  857. prop.set_f32(Role::App, 2, SENDBTN_BOX[2]).unwrap();
  858. prop.set_f32(Role::App, 3, SENDBTN_BOX[3]).unwrap();
  859. #[derive(Clone)]
  860. struct SendMsg {
  861. channel: String,
  862. editz_text: PropertyStr,
  863. sg_root: SceneNodePtr,
  864. chatview_node: SceneNodePtr,
  865. }
  866. impl SendMsg {
  867. async fn send(&self) {
  868. let text = self.editz_text.get();
  869. info!(target: "app::chat", "Send '{text}' to channel: #{}", self.channel);
  870. self.editz_text.set("");
  871. let Some(darkirc) = self.sg_root.clone().lookup_node("/plugin/darkirc") else {
  872. error!(target: "app::chat", "DarkIrc plugin has not been loaded");
  873. return
  874. };
  875. if text.starts_with("/nick") {
  876. let nick = text.split_whitespace().nth(1).unwrap_or("anon");
  877. info!(target: "app::chat", "Setting nick to: {nick}");
  878. darkirc.set_property_str(Role::App, "nick", nick);
  879. let msg = format!("You are now known as <{nick}>");
  880. let id: [u8; 32] = rand::random();
  881. let mut data = vec![];
  882. unixtime().encode(&mut data).unwrap();
  883. id.encode(&mut data).unwrap();
  884. "NOTICE".encode(&mut data).unwrap();
  885. msg.encode(&mut data).unwrap();
  886. self.chatview_node.call_method("insert_line", data).await.unwrap();
  887. return
  888. }
  889. let timest = UNIX_EPOCH.elapsed().unwrap().as_millis() as u64;
  890. let nick = darkirc.get_property_str("nick").unwrap();
  891. let msg = darkirc::Privmsg::new(self.channel.clone(), nick, text);
  892. //let mut data = vec![];
  893. //timest.encode(&mut data).unwrap();
  894. //msg.msg_id(timest).encode(&mut data).unwrap();
  895. //msg.nick.encode(&mut data).unwrap();
  896. //msg.msg.encode(&mut data).unwrap();
  897. //chatview_node.call_method("insert_unconf_line", data).await.unwrap();
  898. let mut data = vec![];
  899. timest.encode(&mut data).unwrap();
  900. msg.channel.encode(&mut data).unwrap();
  901. msg.msg.encode(&mut data).unwrap();
  902. darkirc.call_method("send", data).await.unwrap();
  903. }
  904. }
  905. let sendmsg = SendMsg {
  906. editz_text: editz_text.clone(),
  907. channel: format!("#{channel}"),
  908. sg_root: app.sg_root.clone(),
  909. chatview_node,
  910. };
  911. let (slot, recvr) = Slot::new("send_clicked");
  912. node.register("click", slot).unwrap();
  913. let sendmsg2 = sendmsg.clone();
  914. let listen_click = app.ex.spawn(async move {
  915. while let Ok(_) = recvr.recv().await {
  916. sendmsg2.send().await;
  917. }
  918. });
  919. app.tasks.lock().unwrap().push(listen_click);
  920. let node = node.setup(|me| Button::new(me, app.ex.clone())).await;
  921. layer_node.clone().link(node);
  922. // Create shortcut to send as well
  923. let node = create_shortcut("send_shortcut");
  924. node.set_property_str(Role::App, "key", "enter").unwrap();
  925. let (slot, recvr) = Slot::new("enter_pressed");
  926. node.register("shortcut", slot).unwrap();
  927. let listen_enter = app.ex.spawn(async move {
  928. while let Ok(_) = recvr.recv().await {
  929. sendmsg.send().await;
  930. }
  931. });
  932. app.tasks.lock().unwrap().push(listen_enter);
  933. let node = node.setup(|me| Shortcut::new(me)).await;
  934. layer_node.clone().link(node);
  935. // Create the emoji button
  936. let node = create_button("emoji_btn");
  937. node.set_property_bool(Role::App, "is_active", true).unwrap();
  938. let prop = node.get_property("rect").unwrap();
  939. prop.set_f32(Role::App, 0, EMOJIBTN_BOX[0]).unwrap();
  940. let code = cc.compile("h - EMOJIBTN_BOX_1").unwrap();
  941. prop.set_expr(Role::App, 1, code).unwrap();
  942. prop.set_f32(Role::App, 2, EMOJIBTN_BOX[2]).unwrap();
  943. prop.set_f32(Role::App, 3, EMOJIBTN_BOX[3]).unwrap();
  944. let (slot, recvr) = Slot::new("reqkeyb");
  945. chatedit_node.register("keyboard_request", slot).unwrap();
  946. let emoji_btn_is_visible2 = emoji_btn_is_visible.clone();
  947. let listen_click = app.ex.spawn(async move {
  948. while let Ok(_) = recvr.recv().await {
  949. if emoji_btn_is_visible2.get() {
  950. debug!(target: "app::chat", "Emoji picker not visible so showing keyboard");
  951. miniquad::window::show_keyboard(true);
  952. }
  953. }
  954. });
  955. app.tasks.lock().unwrap().push(listen_click);
  956. let (slot, recvr) = Slot::new("emoji_clicked");
  957. node.register("click", slot).unwrap();
  958. let listen_click = app.ex.spawn(async move {
  959. let mut panel_height = if cfg!(target_os = "android") {
  960. let keyb_height = android_keyboard_height();
  961. if keyb_height > 0. {
  962. keyb_height
  963. } else {
  964. 600.
  965. }
  966. } else {
  967. 400.
  968. };
  969. while let Ok(_) = recvr.recv().await {
  970. info!(target: "app::chat", "clicked emoji");
  971. if cfg!(target_os = "android") {
  972. let keyb_height = android_keyboard_height();
  973. if keyb_height > panel_height {
  974. panel_height = keyb_height
  975. }
  976. }
  977. if emoji_btn_is_visible.get() {
  978. miniquad::window::show_keyboard(false);
  979. assert!(!emoji_close_is_visible.get());
  980. assert!(emoji_h_prop.get() < 0.001);
  981. emoji_btn_is_visible.set(false);
  982. emoji_close_is_visible.set(true);
  983. emoji_h_prop.set(panel_height as f32);
  984. //for i in 1..=20 {
  985. // emoji_h_prop.set((20 * i) as f32);
  986. // msleep(10).await;
  987. //}
  988. } else {
  989. miniquad::window::show_keyboard(true);
  990. assert!(emoji_close_is_visible.get());
  991. assert!(emoji_h_prop.get() > 0.);
  992. emoji_btn_is_visible.set(true);
  993. emoji_close_is_visible.set(false);
  994. emoji_h_prop.set(0. as f32);
  995. //for i in 1..=20 {
  996. // emoji_h_prop.set((400 - 20 * i) as f32);
  997. // msleep(10).await;
  998. //}
  999. }
  1000. }
  1001. });
  1002. app.tasks.lock().unwrap().push(listen_click);
  1003. let node = node.setup(|me| Button::new(me, app.ex.clone())).await;
  1004. layer_node.clone().link(node);
  1005. // Commands help hint
  1006. let cmd_layer_node = create_layer("cmd_hint_layer");
  1007. let prop = cmd_layer_node.get_property("rect").unwrap();
  1008. prop.set_f32(Role::App, 0, 5.).unwrap();
  1009. let code = cc.compile("editz_bg_top_y - CMD_HELP_HEIGHT - CMD_HELP_GAP").unwrap();
  1010. //let code = cc.compile("h - 60 - 80").unwrap();
  1011. //let code = cc.compile("h - 60 - 300").unwrap();
  1012. prop.set_expr(Role::App, 1, code).unwrap();
  1013. let code = cc.compile("w - 2 * CMD_HELP_GAP").unwrap();
  1014. prop.set_expr(Role::App, 2, code).unwrap();
  1015. prop.set_f32(Role::App, 3, CMD_HELP_HEIGHT).unwrap();
  1016. prop.add_depend(&editbox_bg_rect_prop, 1, "editz_bg_top_y");
  1017. cmd_layer_node.set_property_bool(Role::App, "is_visible", false).unwrap();
  1018. cmd_layer_node.set_property_u32(Role::App, "z_index", 3).unwrap();
  1019. let cmd_layer_node =
  1020. cmd_layer_node.setup(|me| Layer::new(me, app.render_api.clone(), app.ex.clone())).await;
  1021. layer_node.clone().link(cmd_layer_node.clone());
  1022. let cmd_hint_is_visible =
  1023. PropertyBool::wrap(&cmd_layer_node, Role::App, "is_visible", 0).unwrap();
  1024. // Create the actionbar bg
  1025. let node = create_vector_art("cmd_hint_bg");
  1026. let prop = node.get_property("rect").unwrap();
  1027. prop.set_f32(Role::App, 0, 0.).unwrap();
  1028. prop.set_f32(Role::App, 1, 0.).unwrap();
  1029. prop.set_expr(Role::App, 2, expr::load_var("w")).unwrap();
  1030. prop.set_expr(Role::App, 3, expr::load_var("h")).unwrap();
  1031. node.set_property_u32(Role::App, "z_index", 0).unwrap();
  1032. let mut shape = VectorShape::new();
  1033. shape.add_filled_box(
  1034. expr::const_f32(CMD_HELP_GAP),
  1035. expr::const_f32(CMD_HELP_GAP),
  1036. cc.compile("w - CMD_HELP_GAP").unwrap(),
  1037. cc.compile("h - CMD_HELP_GAP").unwrap(),
  1038. [0., 0.11, 0.11, 0.4],
  1039. );
  1040. shape.add_filled_box(
  1041. expr::const_f32(CMD_HELP_GAP),
  1042. expr::const_f32(CMD_HELP_GAP),
  1043. expr::const_f32(CMD_HELP_NICK_CMD_WIDTH),
  1044. cc.compile("h - CMD_HELP_GAP").unwrap(),
  1045. [0., 0.3, 0.25, 1.],
  1046. );
  1047. shape.add_filled_box(
  1048. expr::const_f32(CMD_HELP_NICK_CMD_WIDTH),
  1049. expr::const_f32(CMD_HELP_GAP),
  1050. expr::const_f32(CMD_HELP_NICK_DESC_WIDTH),
  1051. cc.compile("h - CMD_HELP_GAP").unwrap(),
  1052. [0., 0.11, 0.11, 1.],
  1053. );
  1054. shape.add_outline(
  1055. expr::const_f32(0.),
  1056. expr::const_f32(0.),
  1057. expr::load_var("w"),
  1058. expr::load_var("h"),
  1059. 1.,
  1060. [0.29, 0.51, 0.45, 1.],
  1061. );
  1062. let node =
  1063. node.setup(|me| VectorArt::new(me, shape, app.render_api.clone(), app.ex.clone())).await;
  1064. cmd_layer_node.clone().link(node);
  1065. // Create some text
  1066. let node = create_text("cmd_nick_label");
  1067. let prop = node.get_property("rect").unwrap();
  1068. prop.set_f32(Role::App, 0, CMD_HELP_CMD_LABEL_X_INSET).unwrap();
  1069. prop.set_f32(Role::App, 1, 0.).unwrap();
  1070. prop.set_f32(Role::App, 2, 1000.).unwrap();
  1071. prop.set_f32(Role::App, 3, 1000.).unwrap();
  1072. node.set_property_f32(Role::App, "baseline", CMD_HELP_BASELINE).unwrap();
  1073. node.set_property_f32(Role::App, "font_size", CMD_HELP_CMD_FONTSIZE).unwrap();
  1074. node.set_property_str(Role::App, "text", "/nick").unwrap();
  1075. //node.set_property_bool(Role::App, "debug", true).unwrap();
  1076. //node.set_property_str(Role::App, "text", "anon1").unwrap();
  1077. let prop = node.get_property("text_color").unwrap();
  1078. prop.set_f32(Role::App, 0, 0.64).unwrap();
  1079. prop.set_f32(Role::App, 1, 1.).unwrap();
  1080. prop.set_f32(Role::App, 2, 0.83).unwrap();
  1081. prop.set_f32(Role::App, 3, 1.).unwrap();
  1082. node.set_property_u32(Role::App, "z_index", 1).unwrap();
  1083. let node = node
  1084. .setup(|me| {
  1085. Text::new(
  1086. me,
  1087. window_scale.clone(),
  1088. app.render_api.clone(),
  1089. app.text_shaper.clone(),
  1090. app.ex.clone(),
  1091. )
  1092. })
  1093. .await;
  1094. cmd_layer_node.clone().link(node);
  1095. // Create some text
  1096. let node = create_text("cmd_nick_desc_label");
  1097. let prop = node.get_property("rect").unwrap();
  1098. prop.set_f32(Role::App, 0, CMD_HELP_NICK_DESC_X).unwrap();
  1099. prop.set_f32(Role::App, 1, 0.).unwrap();
  1100. prop.set_f32(Role::App, 2, 1000.).unwrap();
  1101. prop.set_f32(Role::App, 3, 1000.).unwrap();
  1102. node.set_property_f32(Role::App, "baseline", CMD_HELP_BASELINE).unwrap();
  1103. node.set_property_f32(Role::App, "font_size", FONTSIZE).unwrap();
  1104. node.set_property_str(Role::App, "text", "Change your nickname").unwrap();
  1105. //node.set_property_bool(Role::App, "debug", true).unwrap();
  1106. //node.set_property_str(Role::App, "text", "anon1").unwrap();
  1107. let prop = node.get_property("text_color").unwrap();
  1108. prop.set_f32(Role::App, 0, 0.).unwrap();
  1109. prop.set_f32(Role::App, 1, 0.94).unwrap();
  1110. prop.set_f32(Role::App, 2, 1.).unwrap();
  1111. prop.set_f32(Role::App, 3, 1.).unwrap();
  1112. node.set_property_u32(Role::App, "z_index", 1).unwrap();
  1113. let node = node
  1114. .setup(|me| {
  1115. Text::new(
  1116. me,
  1117. window_scale.clone(),
  1118. app.render_api.clone(),
  1119. app.text_shaper.clone(),
  1120. app.ex.clone(),
  1121. )
  1122. })
  1123. .await;
  1124. cmd_layer_node.clone().link(node);
  1125. // Create debug box
  1126. /*
  1127. let mut node = create_vector_art("debugtool");
  1128. let mut prop = Property::new("hoff", PropertyType::Float32, PropertySubType::Pixel);
  1129. node.add_property(prop).unwrap();
  1130. node.set_property_f32(Role::App, "hoff", 40.).unwrap();
  1131. let prop = node.get_property("rect").unwrap();
  1132. prop.set_f32(Role::App, 0, EMOJIBTN_BOX[0]).unwrap();
  1133. let code = cc.compile("h - EMOJIBTN_BOX_1").unwrap();
  1134. prop.set_expr(Role::App, 1, code).unwrap();
  1135. prop.set_f32(Role::App, 2, EMOJIBTN_BOX[2]).unwrap();
  1136. prop.set_f32(Role::App, 3, EMOJIBTN_BOX[3]).unwrap();
  1137. let hoff_prop = node.get_property("hoff").unwrap();
  1138. prop.add_depend(&hoff_prop, 0, "hoff");
  1139. node.set_property_u32(Role::App, "z_index", 6).unwrap();
  1140. let mut shape = VectorShape::new();
  1141. shape.add_outline(
  1142. expr::const_f32(0.),
  1143. expr::const_f32(0.),
  1144. expr::load_var("w"),
  1145. expr::load_var("h"),
  1146. 2.,
  1147. [1., 0., 0., 1.],
  1148. );
  1149. let node =
  1150. node.setup(|me| VectorArt::new(me, shape, app.render_api.clone(), app.ex.clone())).await;
  1151. layer_node.clone().link(node);
  1152. */
  1153. // Overlay popup
  1154. let content_layer_node = layer_node.clone();
  1155. let layer_node = create_layer("overlay_layer");
  1156. let prop = layer_node.get_property("rect").unwrap();
  1157. prop.set_f32(Role::App, 0, 40.).unwrap();
  1158. let code = cc.compile("editz_bg_top_y - ACTION_POPUP_Y_OFF").unwrap();
  1159. //let code = cc.compile("h - 60 - 80").unwrap();
  1160. //let code = cc.compile("h - 60 - 300").unwrap();
  1161. prop.set_expr(Role::App, 1, code).unwrap();
  1162. prop.set_f32(Role::App, 2, ACTION_SELECT_ALL_RECT.rhs()).unwrap();
  1163. prop.set_f32(Role::App, 3, ACTION_SELECT_ALL_RECT.h).unwrap();
  1164. prop.add_depend(&editbox_bg_rect_prop, 1, "editz_bg_top_y");
  1165. layer_node.set_property_bool(Role::App, "is_visible", false).unwrap();
  1166. layer_node.set_property_u32(Role::App, "z_index", 8).unwrap();
  1167. // Priority higher than chatview but lower than chatedit
  1168. layer_node.set_property_u32(Role::App, "priority", 1).unwrap();
  1169. let layer_node =
  1170. layer_node.setup(|me| Layer::new(me, app.render_api.clone(), app.ex.clone())).await;
  1171. content_layer_node.clone().link(layer_node.clone());
  1172. let actions_is_visible = PropertyBool::wrap(&layer_node, Role::App, "is_visible", 0).unwrap();
  1173. // Create the actionbar bg
  1174. let node = create_vector_art("actionbar_bg");
  1175. let prop = node.get_property("rect").unwrap();
  1176. prop.set_f32(Role::App, 0, 0.).unwrap();
  1177. prop.set_f32(Role::App, 1, 0.).unwrap();
  1178. prop.set_f32(Role::App, 2, ACTION_SELECT_ALL_RECT.rhs()).unwrap();
  1179. prop.set_f32(Role::App, 3, ACTION_SELECT_ALL_RECT.h).unwrap();
  1180. node.set_property_u32(Role::App, "z_index", 0).unwrap();
  1181. let mut shape = VectorShape::new();
  1182. let color1 = [0., 0.13, 0.08, 0.75];
  1183. let color2 = [0., 0., 0., 1.];
  1184. let gradient = [color1.clone(), color1, color2.clone(), color2];
  1185. let hicolor = [0., 0.94, 1., 1.];
  1186. // Copy box
  1187. shape.add_gradient_box(
  1188. expr::const_f32(ACTION_COPY_RECT.x),
  1189. expr::const_f32(ACTION_COPY_RECT.y),
  1190. expr::const_f32(ACTION_COPY_RECT.rhs()),
  1191. expr::const_f32(ACTION_COPY_RECT.bhs()),
  1192. gradient.clone(),
  1193. );
  1194. // Copy outline
  1195. shape.add_outline(
  1196. expr::const_f32(ACTION_COPY_RECT.x),
  1197. expr::const_f32(ACTION_COPY_RECT.y),
  1198. expr::const_f32(ACTION_COPY_RECT.rhs()),
  1199. expr::const_f32(ACTION_COPY_RECT.bhs()),
  1200. 1.,
  1201. hicolor.clone(),
  1202. );
  1203. // Paste box
  1204. shape.add_gradient_box(
  1205. expr::const_f32(ACTION_PASTE_RECT.x),
  1206. expr::const_f32(ACTION_PASTE_RECT.y),
  1207. expr::const_f32(ACTION_PASTE_RECT.rhs()),
  1208. expr::const_f32(ACTION_PASTE_RECT.bhs()),
  1209. gradient.clone(),
  1210. );
  1211. // Paste outline
  1212. shape.add_outline(
  1213. expr::const_f32(ACTION_PASTE_RECT.x),
  1214. expr::const_f32(ACTION_PASTE_RECT.y),
  1215. expr::const_f32(ACTION_PASTE_RECT.rhs()),
  1216. expr::const_f32(ACTION_PASTE_RECT.bhs()),
  1217. 1.,
  1218. hicolor.clone(),
  1219. );
  1220. // Select all box
  1221. shape.add_gradient_box(
  1222. expr::const_f32(ACTION_SELECT_ALL_RECT.x),
  1223. expr::const_f32(ACTION_SELECT_ALL_RECT.y),
  1224. expr::const_f32(ACTION_SELECT_ALL_RECT.rhs()),
  1225. expr::const_f32(ACTION_SELECT_ALL_RECT.bhs()),
  1226. gradient,
  1227. );
  1228. // Select all outline
  1229. shape.add_outline(
  1230. expr::const_f32(ACTION_SELECT_ALL_RECT.x),
  1231. expr::const_f32(ACTION_SELECT_ALL_RECT.y),
  1232. expr::const_f32(ACTION_SELECT_ALL_RECT.rhs()),
  1233. expr::const_f32(ACTION_SELECT_ALL_RECT.bhs()),
  1234. 1.,
  1235. hicolor,
  1236. );
  1237. let node =
  1238. node.setup(|me| VectorArt::new(me, shape, app.render_api.clone(), app.ex.clone())).await;
  1239. layer_node.clone().link(node);
  1240. // Create some text
  1241. let node = create_text("actions_label");
  1242. let prop = node.get_property("rect").unwrap();
  1243. prop.set_f32(Role::App, 0, ACTION_LABEL_POS.x).unwrap();
  1244. prop.set_f32(Role::App, 1, ACTION_LABEL_POS.y).unwrap();
  1245. prop.set_f32(Role::App, 2, ACTION_SELECT_ALL_RECT.rhs()).unwrap();
  1246. prop.set_f32(Role::App, 3, ACTION_SELECT_ALL_RECT.h).unwrap();
  1247. node.set_property_f32(Role::App, "baseline", 0.).unwrap();
  1248. node.set_property_f32(Role::App, "font_size", FONTSIZE).unwrap();
  1249. node.set_property_str(Role::App, "text", "copy paste select all").unwrap();
  1250. //node.set_property_bool(Role::App, "debug", true).unwrap();
  1251. //node.set_property_str(Role::App, "text", "anon1").unwrap();
  1252. let prop = node.get_property("text_color").unwrap();
  1253. prop.set_f32(Role::App, 0, 0.).unwrap();
  1254. prop.set_f32(Role::App, 1, 0.94).unwrap();
  1255. prop.set_f32(Role::App, 2, 1.).unwrap();
  1256. prop.set_f32(Role::App, 3, 1.).unwrap();
  1257. node.set_property_u32(Role::App, "z_index", 1).unwrap();
  1258. let node = node
  1259. .setup(|me| {
  1260. Text::new(
  1261. me,
  1262. window_scale.clone(),
  1263. app.render_api.clone(),
  1264. app.text_shaper.clone(),
  1265. app.ex.clone(),
  1266. )
  1267. })
  1268. .await;
  1269. layer_node.clone().link(node);
  1270. // Copy button
  1271. let node = create_button("copy_btn");
  1272. node.set_property_bool(Role::App, "is_active", true).unwrap();
  1273. let prop = node.get_property("rect").unwrap();
  1274. prop.set_f32(Role::App, 0, ACTION_COPY_RECT.x).unwrap();
  1275. prop.set_f32(Role::App, 1, ACTION_COPY_RECT.y).unwrap();
  1276. prop.set_f32(Role::App, 2, ACTION_COPY_RECT.w).unwrap();
  1277. prop.set_f32(Role::App, 3, ACTION_COPY_RECT.h).unwrap();
  1278. let (slot, recvr) = Slot::new("copy_clicked");
  1279. node.register("click", slot).unwrap();
  1280. let actions_is_visible2 = actions_is_visible.clone();
  1281. let editz_select_text2 = editz_select_text.clone();
  1282. let listen_click = app.ex.spawn(async move {
  1283. let mut clip = Clipboard::new();
  1284. while let Ok(_) = recvr.recv().await {
  1285. info!(target: "app::chat", "clicked copy");
  1286. actions_is_visible2.set(false);
  1287. let select_text = editz_select_text2.get_str(0).unwrap();
  1288. clip.set(&select_text);
  1289. }
  1290. });
  1291. app.tasks.lock().unwrap().push(listen_click);
  1292. let node = node.setup(|me| Button::new(me, app.ex.clone())).await;
  1293. layer_node.clone().link(node);
  1294. // Paste button
  1295. let node = create_button("paste_btn");
  1296. node.set_property_bool(Role::App, "is_active", true).unwrap();
  1297. let prop = node.get_property("rect").unwrap();
  1298. prop.set_f32(Role::App, 0, ACTION_PASTE_RECT.x).unwrap();
  1299. prop.set_f32(Role::App, 1, ACTION_PASTE_RECT.y).unwrap();
  1300. prop.set_f32(Role::App, 2, ACTION_PASTE_RECT.w).unwrap();
  1301. prop.set_f32(Role::App, 3, ACTION_PASTE_RECT.h).unwrap();
  1302. let (slot, recvr) = Slot::new("paste_clicked");
  1303. node.register("click", slot).unwrap();
  1304. let actions_is_visible2 = actions_is_visible.clone();
  1305. let chatedit_node2 = chatedit_node.clone();
  1306. let listen_click = app.ex.spawn(async move {
  1307. let mut clip = Clipboard::new();
  1308. while let Ok(_) = recvr.recv().await {
  1309. if let Some(text) = clip.get() {
  1310. info!(target: "app::chat", "clicked paste: {text}");
  1311. let mut data = vec![];
  1312. text.encode(&mut data).unwrap();
  1313. chatedit_node2.call_method("insert_text", data).await.unwrap();
  1314. } else {
  1315. info!(target: "app::chat", "clicked paste but clip is empty");
  1316. }
  1317. actions_is_visible2.set(false);
  1318. }
  1319. });
  1320. app.tasks.lock().unwrap().push(listen_click);
  1321. let node = node.setup(|me| Button::new(me, app.ex.clone())).await;
  1322. layer_node.clone().link(node);
  1323. // Select all button
  1324. let node = create_button("select_all_btn");
  1325. node.set_property_bool(Role::App, "is_active", true).unwrap();
  1326. let prop = node.get_property("rect").unwrap();
  1327. prop.set_f32(Role::App, 0, ACTION_SELECT_ALL_RECT.x).unwrap();
  1328. prop.set_f32(Role::App, 1, ACTION_SELECT_ALL_RECT.y).unwrap();
  1329. prop.set_f32(Role::App, 2, ACTION_SELECT_ALL_RECT.w).unwrap();
  1330. prop.set_f32(Role::App, 3, ACTION_SELECT_ALL_RECT.h).unwrap();
  1331. let (slot, recvr) = Slot::new("select_all_clicked");
  1332. node.register("click", slot).unwrap();
  1333. let actions_is_visible2 = actions_is_visible.clone();
  1334. let listen_click = app.ex.spawn(async move {
  1335. while let Ok(_) = recvr.recv().await {
  1336. info!(target: "app::chat", "clicked select_all");
  1337. }
  1338. });
  1339. app.tasks.lock().unwrap().push(listen_click);
  1340. let node = node.setup(|me| Button::new(me, app.ex.clone())).await;
  1341. layer_node.clone().link(node);
  1342. // Paste overlay popup
  1343. let layer_node = create_layer("paste_layer");
  1344. let prop = layer_node.get_property("rect").unwrap();
  1345. prop.set_f32(Role::App, 0, 40.).unwrap();
  1346. let code = cc.compile("editz_bg_top_y - ACTION_POPUP_Y_OFF").unwrap();
  1347. //let code = cc.compile("h - 60 - 80").unwrap();
  1348. //let code = cc.compile("h - 60 - 300").unwrap();
  1349. prop.set_expr(Role::App, 1, code).unwrap();
  1350. prop.set_f32(Role::App, 2, ACTION_SELECT_ALL_RECT.rhs()).unwrap();
  1351. prop.set_f32(Role::App, 3, ACTION_SELECT_ALL_RECT.h).unwrap();
  1352. prop.add_depend(&editbox_bg_rect_prop, 1, "editz_bg_top_y");
  1353. layer_node.set_property_bool(Role::App, "is_visible", false).unwrap();
  1354. layer_node.set_property_u32(Role::App, "z_index", 8).unwrap();
  1355. // Priority higher than chatview but lower than chatedit
  1356. layer_node.set_property_u32(Role::App, "priority", 1).unwrap();
  1357. let layer_node =
  1358. layer_node.setup(|me| Layer::new(me, app.render_api.clone(), app.ex.clone())).await;
  1359. content_layer_node.link(layer_node.clone());
  1360. let pasta_is_visible = PropertyBool::wrap(&layer_node, Role::App, "is_visible", 0).unwrap();
  1361. let (slot, recvr) = Slot::new("reqpasta");
  1362. chatedit_node.register("paste_request", slot).unwrap();
  1363. let pasta_is_visible2 = pasta_is_visible.clone();
  1364. let listen_click = app.ex.spawn(async move {
  1365. while let Ok(_) = recvr.recv().await {
  1366. pasta_is_visible2.set(true);
  1367. }
  1368. });
  1369. app.tasks.lock().unwrap().push(listen_click);
  1370. // Create the paste bg
  1371. let node = create_vector_art("paste_bg");
  1372. let prop = node.get_property("rect").unwrap();
  1373. prop.set_f32(Role::App, 0, 0.).unwrap();
  1374. prop.set_f32(Role::App, 1, 0.).unwrap();
  1375. prop.set_f32(Role::App, 2, ACTION_SELECT_ALL_RECT.rhs()).unwrap();
  1376. prop.set_f32(Role::App, 3, ACTION_SELECT_ALL_RECT.h).unwrap();
  1377. node.set_property_u32(Role::App, "z_index", 0).unwrap();
  1378. let mut shape = VectorShape::new();
  1379. let color1 = [0., 0.13, 0.08, 0.75];
  1380. let color2 = [0., 0., 0., 1.];
  1381. let gradient = [color1.clone(), color1, color2.clone(), color2];
  1382. let hicolor = [0., 0.94, 1., 1.];
  1383. // Paste box
  1384. shape.add_gradient_box(
  1385. expr::const_f32(0.),
  1386. expr::const_f32(0.),
  1387. expr::const_f32(ACTION_PASTE_RECT.w),
  1388. expr::const_f32(ACTION_PASTE_RECT.h),
  1389. gradient.clone(),
  1390. );
  1391. // Paste outline
  1392. shape.add_outline(
  1393. expr::const_f32(0.),
  1394. expr::const_f32(0.),
  1395. expr::const_f32(ACTION_PASTE_RECT.w),
  1396. expr::const_f32(ACTION_PASTE_RECT.h),
  1397. 1.,
  1398. hicolor.clone(),
  1399. );
  1400. let node =
  1401. node.setup(|me| VectorArt::new(me, shape, app.render_api.clone(), app.ex.clone())).await;
  1402. layer_node.clone().link(node);
  1403. // Create some text
  1404. let node = create_text("paste_label");
  1405. let prop = node.get_property("rect").unwrap();
  1406. prop.set_f32(Role::App, 0, ACTION_LABEL_POS.x).unwrap();
  1407. prop.set_f32(Role::App, 1, ACTION_LABEL_POS.y).unwrap();
  1408. prop.set_f32(Role::App, 2, ACTION_SELECT_ALL_RECT.rhs()).unwrap();
  1409. prop.set_f32(Role::App, 3, ACTION_SELECT_ALL_RECT.h).unwrap();
  1410. node.set_property_f32(Role::App, "baseline", 0.).unwrap();
  1411. node.set_property_f32(Role::App, "font_size", FONTSIZE).unwrap();
  1412. node.set_property_str(Role::App, "text", "paste").unwrap();
  1413. //node.set_property_bool(Role::App, "debug", true).unwrap();
  1414. //node.set_property_str(Role::App, "text", "anon1").unwrap();
  1415. let prop = node.get_property("text_color").unwrap();
  1416. prop.set_f32(Role::App, 0, 0.).unwrap();
  1417. prop.set_f32(Role::App, 1, 0.94).unwrap();
  1418. prop.set_f32(Role::App, 2, 1.).unwrap();
  1419. prop.set_f32(Role::App, 3, 1.).unwrap();
  1420. node.set_property_u32(Role::App, "z_index", 1).unwrap();
  1421. let node = node
  1422. .setup(|me| {
  1423. Text::new(
  1424. me,
  1425. window_scale.clone(),
  1426. app.render_api.clone(),
  1427. app.text_shaper.clone(),
  1428. app.ex.clone(),
  1429. )
  1430. })
  1431. .await;
  1432. layer_node.clone().link(node);
  1433. // Paste button
  1434. let node = create_button("paste_btn");
  1435. node.set_property_bool(Role::App, "is_active", true).unwrap();
  1436. let prop = node.get_property("rect").unwrap();
  1437. prop.set_f32(Role::App, 0, 0.).unwrap();
  1438. prop.set_f32(Role::App, 1, 0.).unwrap();
  1439. prop.set_f32(Role::App, 2, ACTION_PASTE_RECT.w).unwrap();
  1440. prop.set_f32(Role::App, 3, ACTION_PASTE_RECT.h).unwrap();
  1441. let node = node.setup(|me| Button::new(me, app.ex.clone())).await;
  1442. layer_node.clone().link(node.clone());
  1443. let (slot, recvr) = Slot::new("paste_clicked");
  1444. node.register("click", slot).unwrap();
  1445. let chatedit_node2 = chatedit_node.clone();
  1446. let pasta_is_visible2 = pasta_is_visible.clone();
  1447. let listen_click = app.ex.spawn(async move {
  1448. let mut clip = Clipboard::new();
  1449. while let Ok(_) = recvr.recv().await {
  1450. if let Some(text) = clip.get() {
  1451. info!(target: "app::chat", "clicked paste: {text}");
  1452. let mut data = vec![];
  1453. text.encode(&mut data).unwrap();
  1454. chatedit_node2.call_method("insert_text", data).await.unwrap();
  1455. } else {
  1456. info!(target: "app::chat", "clicked paste but clip is empty");
  1457. }
  1458. pasta_is_visible2.set(false);
  1459. }
  1460. });
  1461. app.tasks.lock().unwrap().push(listen_click);
  1462. let editz_cpos_sub = editz_cursor_pos.subscribe_modify();
  1463. let pasta_is_visible2 = pasta_is_visible.clone();
  1464. let editz_cpos_task = app.ex.spawn(async move {
  1465. while let Ok(_) = editz_cpos_sub.receive().await {
  1466. pasta_is_visible2.set(false);
  1467. }
  1468. });
  1469. app.tasks.lock().unwrap().push(editz_cpos_task);
  1470. let editz_select_sub = editz_select_text.subscribe_modify();
  1471. let pasta_is_visible2 = pasta_is_visible.clone();
  1472. let editz_select_task = app.ex.spawn(async move {
  1473. while let Ok(_) = editz_select_sub.receive().await {
  1474. if editz_select_text.is_null(0).unwrap() {
  1475. info!(target: "app::chat", "selection changed: null");
  1476. actions_is_visible.set(false);
  1477. pasta_is_visible2.set(false);
  1478. } else {
  1479. let select_text = editz_select_text.get_str(0).unwrap();
  1480. info!(target: "app::chat", "selection changed: {select_text}");
  1481. actions_is_visible.set(true);
  1482. pasta_is_visible2.set(false);
  1483. }
  1484. }
  1485. });
  1486. app.tasks.lock().unwrap().push(editz_select_task);
  1487. let editz_text_sub = editz_text.prop().subscribe_modify();
  1488. let editz_text_task = app.ex.spawn(async move {
  1489. while let Ok(_) = editz_text_sub.receive().await {
  1490. pasta_is_visible.set(false);
  1491. let text = editz_text.get();
  1492. info!(target: "app::chat", "text changed: {text}");
  1493. // We want to avoid setting the property multiple times to the same value
  1494. // because then it triggers unnecessary redraw work.
  1495. if let Some(first_char) = text.chars().next() {
  1496. if first_char == '/' {
  1497. if !cmd_hint_is_visible.get() {
  1498. cmd_hint_is_visible.set(true);
  1499. }
  1500. } else {
  1501. if cmd_hint_is_visible.get() {
  1502. cmd_hint_is_visible.set(false);
  1503. }
  1504. }
  1505. } else {
  1506. if cmd_hint_is_visible.get() {
  1507. cmd_hint_is_visible.set(false);
  1508. }
  1509. }
  1510. }
  1511. });
  1512. app.tasks.lock().unwrap().push(editz_text_task);
  1513. }