diff --git a/bin/darkwallet/insert_line.py b/bin/darkwallet/insert_line.py new file mode 100644 index 000000000..dccd9fc85 --- /dev/null +++ b/bin/darkwallet/insert_line.py @@ -0,0 +1,11 @@ +from gui import * + +node_id = api.lookup_node_id("/window/view/chatty") + +arg_data = bytearray() +serial.write_u32(arg_data, 110) +serial.encode_str(arg_data, "nick") +serial.encode_str(arg_data, "hello1234") + +api.call_method(node_id, "insert_line", arg_data) + diff --git a/bin/darkwallet/pydrk/api.py b/bin/darkwallet/pydrk/api.py index a8a506307..f0208a2f8 100644 --- a/bin/darkwallet/pydrk/api.py +++ b/bin/darkwallet/pydrk/api.py @@ -152,6 +152,7 @@ class ErrorCode: PY_EVAL_ERR = 30 SEXPR_EMPTY = 31 SEXPR_GLOBAL_NOT_FOUND = 32 + CHANNEL_CLOSED = 36 @staticmethod def to_str(errc): @@ -218,6 +219,8 @@ class ErrorCode: return "sexpr_empty" case ErrorCode.SEXPR_GLOBAL_NOT_FOUND: return "sexpr_global_not_found" + case ErrorCode.CHANNEL_CLOSED: + return "channel_closed" def vertex(x, y, r, g, b, a, u, v): buf = bytearray() @@ -319,6 +322,8 @@ class Api: raise exc.SExprEmpty case 32: raise exc.SExprGlobalNotFound + case 36: + raise exc.ChannelClosed return cursor def hello(self): @@ -475,7 +480,7 @@ class Api: serial.encode_str(req, node_path) try: cur = self._make_request(Command.LOOKUP_NODE_ID, req) - except exc.RequestNodeNotFound: + except exc.NodeNotFound: return None return serial.read_u32(cur) diff --git a/bin/darkwallet/src/app.rs b/bin/darkwallet/src/app.rs index 1fed8bacb..079b276ac 100644 --- a/bin/darkwallet/src/app.rs +++ b/bin/darkwallet/src/app.rs @@ -22,10 +22,11 @@ use futures::{stream::FuturesUnordered, StreamExt}; use std::{sync::Arc, thread}; use crate::{ + error::Error, expr::Op, gfx2::{GraphicsEventPublisherPtr, RenderApiPtr, Vertex}, prop::{Property, PropertySubType, PropertyType}, - scene::{Pimpl, SceneGraph, SceneGraphPtr2, SceneNodeId, SceneNodeType}, + scene::{MethodResponseFn, Pimpl, SceneGraph, SceneGraphPtr2, SceneNodeId, SceneNodeType}, text2::TextShaperPtr, ui::{chatview, Button, ChatView, EditBox, Image, Mesh, RenderLayer, Stoppable, Text, Window}, }; @@ -635,7 +636,7 @@ impl App { sg.link(node_id, layer_node_id).unwrap(); // ChatView - let node_id = create_chatview(&mut sg, "chatty"); + let (node_id, recvr) = create_chatview(&mut sg, "chatty"); let node = sg.get_node(node_id).unwrap(); let prop = node.get_property("rect").unwrap(); prop.set_f32(0, 0.).unwrap(); @@ -708,6 +709,7 @@ impl App { self.event_pub.clone(), self.text_shaper.clone(), chat_tree, + recvr, ) .await; let mut sg = self.sg.lock().await; @@ -916,7 +918,10 @@ fn create_editbox(sg: &mut SceneGraph, name: &str) -> SceneNodeId { node.id } -fn create_chatview(sg: &mut SceneGraph, name: &str) -> SceneNodeId { +fn create_chatview( + sg: &mut SceneGraph, + name: &str, +) -> (SceneNodeId, async_channel::Receiver>) { debug!(target: "app", "create_chatview({name})"); let node = sg.add_node(name, SceneNodeType::ChatView); @@ -959,5 +964,25 @@ fn create_chatview(sg: &mut SceneGraph, name: &str) -> SceneNodeId { let mut prop = Property::new("debug", PropertyType::Bool, PropertySubType::Null); node.add_property(prop).unwrap(); - node.id + let (sender, recvr) = async_channel::unbounded::>(); + let method = move |data: Vec, response_fn: MethodResponseFn| { + if sender.try_send(data).is_err() { + response_fn(Err(Error::ChannelClosed)); + } else { + response_fn(Ok(vec![])); + } + }; + node.add_method( + "insert_line", + vec![ + ("timestamp", "Timestamp", PropertyType::Uint32), + ("nick", "Nickname", PropertyType::Str), + ("text", "Text", PropertyType::Str), + ], + vec![], + Box::new(method), + ) + .unwrap(); + + (node.id, recvr) } diff --git a/bin/darkwallet/src/error.rs b/bin/darkwallet/src/error.rs index 22ac0b254..cb41d2388 100644 --- a/bin/darkwallet/src/error.rs +++ b/bin/darkwallet/src/error.rs @@ -125,4 +125,7 @@ pub enum Error { #[error("Empty atlas")] AtlasIsEmpty = 35, + + #[error("Channel closed")] + ChannelClosed = 36, } diff --git a/bin/darkwallet/src/net.rs b/bin/darkwallet/src/net.rs index 9183163a2..41963c702 100644 --- a/bin/darkwallet/src/net.rs +++ b/bin/darkwallet/src/net.rs @@ -526,7 +526,7 @@ impl ZeroMQAdapter { let node = scene_graph.get_node_mut(node_id).ok_or(Error::NodeNotFound)?; let method_name2 = method_name.clone(); - let (tx, rx) = mpsc::sync_channel::>>(0); + let (tx, rx) = mpsc::sync_channel::>>(1); let response_fn = Box::new(move |result| { debug!(target: "req", "processing callmethod for {}:'{}'", node_id, method_name2); tx.send(result).unwrap(); diff --git a/bin/darkwallet/src/ui/button.rs b/bin/darkwallet/src/ui/button.rs index 0937a1fe3..225803c61 100644 --- a/bin/darkwallet/src/ui/button.rs +++ b/bin/darkwallet/src/ui/button.rs @@ -66,27 +66,18 @@ impl Button { let self_ = Arc::new_cyclic(|me: &Weak| { let ev_sub = event_pub.subscribe_mouse_btn_down(); let me2 = me.clone(); - let mouse_btn_down_task = ex.spawn(async move { - loop { - Self::process_mouse_btn_down(&me2, &ev_sub).await; - } - }); + let mouse_btn_down_task = + ex.spawn(async move { while Self::process_mouse_btn_down(&me2, &ev_sub).await {} }); let ev_sub = event_pub.subscribe_mouse_btn_up(); let me2 = me.clone(); - let mouse_btn_up_task = ex.spawn(async move { - loop { - Self::process_mouse_btn_up(&me2, &ev_sub).await; - } - }); + let mouse_btn_up_task = + ex.spawn(async move { while Self::process_mouse_btn_up(&me2, &ev_sub).await {} }); let ev_sub = event_pub.subscribe_touch(); let me2 = me.clone(); - let touch_task = ex.spawn(async move { - loop { - Self::process_touch(&me2, &ev_sub).await; - } - }); + let touch_task = + ex.spawn(async move { while Self::process_touch(&me2, &ev_sub).await {} }); let tasks = vec![mouse_btn_down_task, mouse_btn_up_task, touch_task]; @@ -99,10 +90,10 @@ impl Button { async fn process_mouse_btn_down( me: &Weak, ev_sub: &Subscription<(MouseButton, f32, f32)>, - ) { + ) -> bool { let Ok((btn, mouse_x, mouse_y)) = ev_sub.receive().await else { debug!(target: "ui::button", "Event relayer closed"); - return + return false }; let Some(self_) = me.upgrade() else { @@ -111,16 +102,20 @@ impl Button { }; if !self_.is_active.get() { - return + return true } self_.handle_mouse_btn_down(btn, mouse_x, mouse_y); + true } - async fn process_mouse_btn_up(me: &Weak, ev_sub: &Subscription<(MouseButton, f32, f32)>) { + async fn process_mouse_btn_up( + me: &Weak, + ev_sub: &Subscription<(MouseButton, f32, f32)>, + ) -> bool { let Ok((btn, mouse_x, mouse_y)) = ev_sub.receive().await else { debug!(target: "ui::button", "Event relayer closed"); - return + return false }; let Some(self_) = me.upgrade() else { @@ -129,16 +124,20 @@ impl Button { }; if !self_.is_active.get() { - return + return true } self_.handle_mouse_btn_up(btn, mouse_x, mouse_y).await; + true } - async fn process_touch(me: &Weak, ev_sub: &Subscription<(TouchPhase, u64, f32, f32)>) { + async fn process_touch( + me: &Weak, + ev_sub: &Subscription<(TouchPhase, u64, f32, f32)>, + ) -> bool { let Ok((phase, id, touch_x, touch_y)) = ev_sub.receive().await else { debug!(target: "ui::button", "Event relayer closed"); - return + return false }; let Some(self_) = me.upgrade() else { @@ -147,10 +146,11 @@ impl Button { }; if !self_.is_active.get() { - return + return true } self_.handle_touch(phase, id, touch_x, touch_y).await; + true } fn handle_mouse_btn_down(&self, btn: MouseButton, mouse_x: f32, mouse_y: f32) { diff --git a/bin/darkwallet/src/ui/chatview.rs b/bin/darkwallet/src/ui/chatview.rs index 881e7c8dc..a16a44d01 100644 --- a/bin/darkwallet/src/ui/chatview.rs +++ b/bin/darkwallet/src/ui/chatview.rs @@ -22,6 +22,7 @@ use rand::{rngs::OsRng, Rng}; use std::{ collections::BTreeMap, hash::{DefaultHasher, Hash, Hasher}, + io::Cursor, sync::{Arc, Mutex as SyncMutex, Weak}, }; @@ -244,6 +245,7 @@ impl ChatView { event_pub: GraphicsEventPublisherPtr, text_shaper: TextShaperPtr, tree: sled::Tree, + recvr: async_channel::Receiver>, ) -> Pimpl { debug!(target: "ui::chatview", "ChatView::new()"); let scene_graph = sg.lock().await; @@ -265,27 +267,24 @@ impl ChatView { let self_ = Arc::new_cyclic(|me: &Weak| { let ev_sub = event_pub.subscribe_mouse_wheel(); let me2 = me.clone(); - let mouse_wheel_task = ex.spawn(async move { - loop { - Self::process_mouse_wheel(&me2, &ev_sub).await; - } - }); + let mouse_wheel_task = + ex.spawn(async move { while Self::process_mouse_wheel(&me2, &ev_sub).await {} }); let ev_sub = event_pub.subscribe_mouse_move(); let me2 = me.clone(); - let mouse_move_task = ex.spawn(async move { - loop { - Self::process_mouse_move(&me2, &ev_sub).await; - } - }); + let mouse_move_task = + ex.spawn(async move { while Self::process_mouse_move(&me2, &ev_sub).await {} }); let ev_sub = event_pub.subscribe_touch(); let me2 = me.clone(); - let touch_task = ex.spawn(async move { - loop { - Self::process_touch(&me2, &ev_sub).await; - } - }); + let touch_task = + ex.spawn(async move { while Self::process_touch(&me2, &ev_sub).await {} }); + + let me2 = me.clone(); + let insert_line_method_task = + ex.spawn( + async move { while Self::process_insert_line_method(&me2, &recvr).await {} }, + ); let mut on_modify = OnModify::new(ex, node_name, node_id, me.clone()); @@ -296,7 +295,8 @@ impl ChatView { } on_modify.when_change(rect.clone(), redraw); - let mut tasks = vec![mouse_wheel_task, mouse_move_task, touch_task]; + let mut tasks = + vec![mouse_wheel_task, mouse_move_task, touch_task, insert_line_method_task]; tasks.append(&mut on_modify.tasks); Self { @@ -334,10 +334,10 @@ impl ChatView { Pimpl::ChatView(self_) } - async fn process_mouse_wheel(me: &Weak, ev_sub: &Subscription<(f32, f32)>) { + async fn process_mouse_wheel(me: &Weak, ev_sub: &Subscription<(f32, f32)>) -> bool { let Ok((wheel_x, wheel_y)) = ev_sub.receive().await else { debug!(target: "ui::chatview", "Event relayer closed"); - return + return false }; let Some(self_) = me.upgrade() else { @@ -346,12 +346,13 @@ impl ChatView { }; self_.handle_mouse_wheel(wheel_x, wheel_y).await; + true } - async fn process_mouse_move(me: &Weak, ev_sub: &Subscription<(f32, f32)>) { + async fn process_mouse_move(me: &Weak, ev_sub: &Subscription<(f32, f32)>) -> bool { let Ok((mouse_x, mouse_y)) = ev_sub.receive().await else { debug!(target: "ui::chatview", "Event relayer closed"); - return + return false }; let Some(self_) = me.upgrade() else { @@ -360,12 +361,16 @@ impl ChatView { }; self_.handle_mouse_move(mouse_x, mouse_y).await; + true } - async fn process_touch(me: &Weak, ev_sub: &Subscription<(TouchPhase, u64, f32, f32)>) { + async fn process_touch( + me: &Weak, + ev_sub: &Subscription<(TouchPhase, u64, f32, f32)>, + ) -> bool { let Ok((phase, id, touch_x, touch_y)) = ev_sub.receive().await else { debug!(target: "ui::chatview", "Event relayer closed"); - return + return false }; let Some(self_) = me.upgrade() else { @@ -374,6 +379,38 @@ impl ChatView { }; self_.handle_touch(phase, id, touch_x, touch_y).await; + true + } + + async fn process_insert_line_method( + me: &Weak, + recvr: &async_channel::Receiver>, + ) -> bool { + let Ok(data) = recvr.recv().await else { + debug!(target: "ui::chatview", "Event relayer closed"); + return false + }; + + fn decode_data(data: &[u8]) -> std::io::Result<(u32, String, String)> { + let mut cur = Cursor::new(&data); + let timestamp = u32::decode(&mut cur)?; + let nick = String::decode(&mut cur)?; + let text = String::decode(&mut cur)?; + Ok((timestamp, nick, text)) + } + + let Ok((timestamp, nick, text)) = decode_data(&data) else { + error!(target: "ui::chatview", "insert_line() method invalid arg data"); + return true + }; + + let Some(self_) = me.upgrade() else { + // Should not happen + panic!("self destroyed before touch_task was stopped!"); + }; + + self_.handle_insert_line(timestamp, nick, text).await; + true } async fn handle_mouse_wheel(&self, wheel_x: f32, wheel_y: f32) { @@ -435,6 +472,48 @@ impl ChatView { } } + async fn handle_insert_line(&self, timest: u32, nick: String, text: String) { + debug!(target: "ui::chatview", "handle_insert_line({timest}, {nick}, {text})"); + + /* + let chatmsg = ChatMsg { nick, text }; + + let timestr = timest.to_string(); + // left pad with zeros + let mut timestr = format!("{:0>4}", timestr); + timestr.insert(2, ':'); + + let text = format!("{} {} {}", timestr, chatmsg.nick, chatmsg.text); + let glyphs = self.text_shaper.shape(text, self.font_size.get()).await; + + // Now add message to page + + let mut pages = self.pages2.lock().unwrap(); + let mut idx = None; + for (i, page) in pages.iter_mut().enumerate() { + let first_timest = page.msgs.first().unwrap().timest; + let last_timest = page.msgs.last().unwrap().timest; + + if first_timest <= timest && timest <= last_timest { + idx = Some(i); + } + } + if idx.is_none() { + idx = Some(pages.len() - 1); + } + let idx = idx.unwrap(); + + let mut msgs = pages[idx].msgs.clone(); + + msgs.push(Message { timest, chatmsg, glyphs }); + msgs.sort_unstable_by_key(|msg| msg.timest); + + let new_page = Page2::new(msgs, &self.render_api).await; + let _ = std::mem::replace(&mut pages[idx], new_page); + drop(pages); + */ + } + /// Descent = line height - baseline fn descent(&self) -> f32 { self.line_height.get() - self.baseline.get() diff --git a/bin/darkwallet/src/ui/editbox.rs b/bin/darkwallet/src/ui/editbox.rs index 6b077db2a..2d62f9bc0 100644 --- a/bin/darkwallet/src/ui/editbox.rs +++ b/bin/darkwallet/src/ui/editbox.rs @@ -204,19 +204,13 @@ impl EditBox { // Start a task monitoring for key down events let ev_sub = event_pub.subscribe_char(); let me2 = me.clone(); - let char_task = ex.spawn(async move { - loop { - Self::process_char(&me2, &ev_sub).await; - } - }); + let char_task = + ex.spawn(async move { while Self::process_char(&me2, &ev_sub).await {} }); let ev_sub = event_pub.subscribe_key_down(); let me2 = me.clone(); - let key_down_task = ex.spawn(async move { - loop { - Self::process_key_down(&me2, &ev_sub).await; - } - }); + let key_down_task = + ex.spawn(async move { while Self::process_key_down(&me2, &ev_sub).await {} }); /* let ev_sub = event_pub.subscribe_key_up(); @@ -242,35 +236,23 @@ impl EditBox { let ev_sub = event_pub.subscribe_mouse_btn_down(); let me2 = me.clone(); - let mouse_btn_down_task = ex.spawn(async move { - loop { - Self::process_mouse_btn_down(&me2, &ev_sub).await; - } - }); + let mouse_btn_down_task = + ex.spawn(async move { while Self::process_mouse_btn_down(&me2, &ev_sub).await {} }); let ev_sub = event_pub.subscribe_mouse_btn_up(); let me2 = me.clone(); - let mouse_btn_up_task = ex.spawn(async move { - loop { - Self::process_mouse_btn_up(&me2, &ev_sub).await; - } - }); + let mouse_btn_up_task = + ex.spawn(async move { while Self::process_mouse_btn_up(&me2, &ev_sub).await {} }); let ev_sub = event_pub.subscribe_mouse_move(); let me2 = me.clone(); - let mouse_move_task = ex.spawn(async move { - loop { - Self::process_mouse_move(&me2, &ev_sub).await; - } - }); + let mouse_move_task = + ex.spawn(async move { while Self::process_mouse_move(&me2, &ev_sub).await {} }); let ev_sub = event_pub.subscribe_touch(); let me2 = me.clone(); - let touch_task = ex.spawn(async move { - loop { - Self::process_touch(&me2, &ev_sub).await; - } - }); + let touch_task = + ex.spawn(async move { while Self::process_touch(&me2, &ev_sub).await {} }); let mut on_modify = OnModify::new(ex, node_name, node_id, me.clone()); on_modify.when_change(is_focused.prop(), Self::change_focus); @@ -479,15 +461,15 @@ impl EditBox { } } - async fn process_char(me: &Weak, ev_sub: &Subscription<(char, KeyMods, bool)>) { + async fn process_char(me: &Weak, ev_sub: &Subscription<(char, KeyMods, bool)>) -> bool { let Ok((key, mods, repeat)) = ev_sub.receive().await else { debug!(target: "ui::editbox", "Event relayer closed"); - return + return false }; // First filter for only single digit keys if DISALLOWED_CHARS.contains(&key) { - return + return true } let Some(self_) = me.upgrade() else { @@ -496,15 +478,15 @@ impl EditBox { }; if !self_.is_focused.get() { - return + return true } if mods.ctrl || mods.alt { if repeat { - return + return true } self_.handle_shortcut(key, &mods).await; - return + return true } let actions = { @@ -515,18 +497,22 @@ impl EditBox { for _ in 0..actions { self_.insert_char(key).await; } + true } - async fn process_key_down(me: &Weak, ev_sub: &Subscription<(KeyCode, KeyMods, bool)>) { + async fn process_key_down( + me: &Weak, + ev_sub: &Subscription<(KeyCode, KeyMods, bool)>, + ) -> bool { let Ok((key, mods, repeat)) = ev_sub.receive().await else { debug!(target: "ui::editbox", "Event relayer closed"); - return + return false }; // First filter for only single digit keys // Avoid processing events handled by insert_char() if !ALLOWED_KEYCODES.contains(&key) { - return + return true } let Some(self_) = me.upgrade() else { @@ -535,7 +521,7 @@ impl EditBox { }; if !self_.is_focused.get() { - return + return true } let actions = { @@ -549,15 +535,16 @@ impl EditBox { for _ in 0..actions { self_.handle_key(&key, &mods).await; } + true } async fn process_mouse_btn_down( me: &Weak, ev_sub: &Subscription<(MouseButton, f32, f32)>, - ) { + ) -> bool { let Ok((btn, mouse_x, mouse_y)) = ev_sub.receive().await else { debug!(target: "ui::editbox", "Event relayer closed"); - return + return false }; let Some(self_) = me.upgrade() else { @@ -566,16 +553,20 @@ impl EditBox { }; if !self_.is_active.get() { - return + return true } self_.handle_mouse_btn_down(btn, mouse_x, mouse_y).await; + true } - async fn process_mouse_btn_up(me: &Weak, ev_sub: &Subscription<(MouseButton, f32, f32)>) { + async fn process_mouse_btn_up( + me: &Weak, + ev_sub: &Subscription<(MouseButton, f32, f32)>, + ) -> bool { let Ok((btn, mouse_x, mouse_y)) = ev_sub.receive().await else { debug!(target: "ui::editbox", "Event relayer closed"); - return + return false }; let Some(self_) = me.upgrade() else { @@ -584,16 +575,17 @@ impl EditBox { }; if !self_.is_active.get() { - return + return true } self_.handle_mouse_btn_up(btn, mouse_x, mouse_y); + true } - async fn process_mouse_move(me: &Weak, ev_sub: &Subscription<(f32, f32)>) { + async fn process_mouse_move(me: &Weak, ev_sub: &Subscription<(f32, f32)>) -> bool { let Ok((mouse_x, mouse_y)) = ev_sub.receive().await else { debug!(target: "ui::editbox", "Event relayer closed"); - return + return false }; let Some(self_) = me.upgrade() else { @@ -602,16 +594,20 @@ impl EditBox { }; if !self_.is_active.get() { - return + return true } self_.handle_mouse_move(mouse_x, mouse_y).await; + true } - async fn process_touch(me: &Weak, ev_sub: &Subscription<(TouchPhase, u64, f32, f32)>) { + async fn process_touch( + me: &Weak, + ev_sub: &Subscription<(TouchPhase, u64, f32, f32)>, + ) -> bool { let Ok((phase, id, touch_x, touch_y)) = ev_sub.receive().await else { debug!(target: "ui::editbox", "Event relayer closed"); - return + return false }; let Some(self_) = me.upgrade() else { @@ -620,10 +616,11 @@ impl EditBox { }; if !self_.is_active.get() { - return + return true } self_.handle_touch(phase, id, touch_x, touch_y).await; + true } async fn change_focus(self: Arc) {