chat.rs 53 KB

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