darkfi 1 год назад
Родитель
Сommit
d5ba88e099

+ 14 - 0
bin/darkwallet/src/app/schema.rs

@@ -795,4 +795,18 @@ pub(super) async fn make(app: &App, window: SceneNodePtr) {
         })
         .await;
     layer_node.link(node);
+
+    // Create the send button
+    let node = create_button("send_btn");
+    node.set_property_bool(Role::App, "is_active", true).unwrap();
+    let prop = node.get_property("rect").unwrap();
+    let code = cc.compile("w - SENDLABEL_WIDTH").unwrap();
+    prop.set_expr(Role::App, 0, code).unwrap();
+    let code = cc.compile("h - EDITCHAT_HEIGHT").unwrap();
+    prop.set_expr(Role::App, 1, code).unwrap();
+    prop.set_f32(Role::App, 2, SENDLABEL_WIDTH).unwrap();
+    prop.set_f32(Role::App, 3, EDITCHAT_HEIGHT).unwrap();
+
+    let node = node.setup(|me| Button::new(me, app.ex.clone())).await;
+    layer_node.link(node);
 }

+ 52 - 11
bin/darkwallet/src/darkirc2.rs

@@ -38,13 +38,19 @@ use smol::{
     fs,
     io::{ReadHalf, WriteHalf},
 };
-use std::sync::{
-    atomic::{AtomicBool, Ordering},
-    Arc, Mutex as SyncMutex, Weak,
+use std::{
+    sync::{
+        atomic::{AtomicBool, Ordering},
+        Arc, Mutex as SyncMutex, Weak,
+    },
+    time::UNIX_EPOCH,
 };
 use url::Url;
 
-use crate::scene::SceneNodePtr;
+use crate::{
+    prop::{PropertyStr, Role},
+    scene::{SceneNodePtr, Slot},
+};
 
 #[cfg(target_os = "android")]
 const EVGRDB_PATH: &str = "/data/data/darkfi.darkwallet/evgr/";
@@ -74,14 +80,20 @@ pub struct LocalDarkIRC {
     writer: AsyncMutex<Option<WriteHalf<Box<dyn PtStream>>>>,
 
     evgr: LocalEventGraphPtr,
-    receive_task: SyncMutex<Option<smol::Task<()>>>,
+    tasks: SyncMutex<Vec<smol::Task<()>>>,
 
     chatview_node: SceneNodePtr,
+    sendbtn_node: SceneNodePtr,
+    editbox_text: PropertyStr,
 }
 
 impl LocalDarkIRC {
     pub async fn new(sg_root: SceneNodePtr, ex: ExecutorPtr) -> Result<Arc<Self>> {
-        let chatview_node = sg_root.lookup_node("/window/view/chatty").unwrap();
+        let chatview_node = sg_root.clone().lookup_node("/window/view/chatty").unwrap();
+        let sendbtn_node = sg_root.clone().lookup_node("/window/view/send_btn").unwrap();
+
+        let editbox_node = sg_root.lookup_node("/window/view/editz").unwrap();
+        let editbox_text = PropertyStr::wrap(&editbox_node, Role::App, "text", 0).unwrap();
 
         info!(target: "darkirc", "Instantiating DarkIRC event DAG");
         let datastore = expand_path(EVGRDB_PATH)?;
@@ -96,9 +108,11 @@ impl LocalDarkIRC {
             writer: AsyncMutex::new(None),
 
             evgr,
-            receive_task: SyncMutex::new(None),
+            tasks: SyncMutex::new(vec![]),
 
             chatview_node,
+            sendbtn_node,
+            editbox_text,
         }))
     }
 
@@ -125,16 +139,30 @@ impl LocalDarkIRC {
         self.version_exchange().await?;
 
         let me = Arc::downgrade(&self);
-        let task = ex.spawn(async move {
+        let recv_task = ex.spawn(async move {
             while let Some(self_) = me.upgrade() {
                 self_.receive_msg().await.unwrap();
             }
             error!(target: "darkirc", "Closing DarkIRC receive loop");
         });
 
-        let mut receive_task = self.receive_task.lock().unwrap();
-        assert!(receive_task.is_none());
-        *receive_task = Some(task);
+        let (slot_click, click_recvr) = Slot::new("send_button_clicked");
+        self.sendbtn_node.register("click", slot_click).unwrap();
+
+        let me = Arc::downgrade(&self);
+        let send_task = ex.spawn(async move {
+            while let Some(self_) = me.upgrade() {
+                let Ok(_) = click_recvr.recv().await else {
+                    error!(target: "ui::win", "Button click recvr closed");
+                    break
+                };
+                self_.handle_send().await;
+            }
+        });
+
+        let mut tasks = self.tasks.lock().unwrap();
+        assert!(tasks.is_empty());
+        *tasks = vec![recv_task, send_task];
 
         self.is_connected.store(true, Ordering::Relaxed);
 
@@ -236,4 +264,17 @@ impl LocalDarkIRC {
 
         Ok(())
     }
+
+    async fn handle_send(&self) {
+        // Get text from editbox
+        let text = self.editbox_text.get();
+        // Clear editbox
+        self.editbox_text.set("");
+
+        // Send text to channel
+        debug!(target: "darkirc", "Sending privmsg: {text}");
+        let msg = Privmsg::new("#random".to_string(), "anon".to_string(), text);
+        let timestamp = UNIX_EPOCH.elapsed().unwrap().as_millis() as u64;
+        self.send_msg(timestamp, msg).await.unwrap();
+    }
 }

+ 57 - 11
bin/darkwallet/src/scene.rs

@@ -16,12 +16,13 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-use async_channel::Sender;
+use async_channel::{Receiver, Sender};
 use async_lock::RwLock as AsyncRwLock;
 use async_trait::async_trait;
 use darkfi_serial::{FutAsyncWriteExt, SerialDecodable, SerialEncodable};
+use futures::{stream::FuturesUnordered, StreamExt};
 use std::{
-    collections::VecDeque,
+    collections::{HashMap, VecDeque},
     fmt,
     future::Future,
     str::FromStr,
@@ -120,7 +121,7 @@ pub struct SceneNode {
     parent: SyncRwLock<Option<Weak<Self>>>,
     children: SyncRwLock<Vec<SceneNodePtr>>,
     pub props: Vec<PropertyPtr>,
-    pub sigs: Vec<Signal>,
+    pub sigs: SyncRwLock<Vec<SignalPtr>>,
     pub methods: Vec<Method>,
     pub pimpl: Pimpl,
 }
@@ -138,7 +139,7 @@ impl SceneNode {
             parent: SyncRwLock::new(None),
             children: SyncRwLock::new(vec![]),
             props: vec![],
-            sigs: vec![],
+            sigs: SyncRwLock::new(vec![]),
             methods: vec![],
             pimpl: Pimpl::Null,
         }
@@ -254,18 +255,53 @@ impl SceneNode {
             .into_iter()
             .map(|(n, d, t)| CallArg { name: n.into(), desc: d.into(), typ: t })
             .collect();
-        self.sigs.push(Signal {
+        let mut sigs = self.sigs.write().unwrap();
+        sigs.push(Arc::new(Signal {
             name: name.into(),
             desc: desc.into(),
             fmt,
-            slots: vec![],
-            freed: vec![],
-        });
+            slots: SyncRwLock::new(HashMap::new()),
+        }));
         Ok(())
     }
 
     fn has_signal(&self, name: &str) -> bool {
-        self.sigs.iter().any(|sig| sig.name == name)
+        let sigs = self.sigs.read().unwrap();
+        sigs.iter().any(|sig| sig.name == name)
+    }
+    pub fn get_signal(&self, name: &str) -> Option<SignalPtr> {
+        let sigs = self.sigs.read().unwrap();
+        sigs.iter().find(|sig| sig.name == name).cloned()
+    }
+
+    pub fn register(&self, sig_name: &str, slot: Slot) -> Result<SlotId> {
+        let slot_id = rand::random();
+        let sig = self.get_signal(sig_name).ok_or(Error::SignalNotFound)?;
+        let mut slots = sig.slots.write().unwrap();
+        slots.insert(slot_id, slot);
+        Ok(slot_id)
+    }
+    pub fn unregister(&self, sig_name: &str, slot_id: SlotId) -> Result<()> {
+        let sig = self.get_signal(sig_name).ok_or(Error::SignalNotFound)?;
+        let mut slots = sig.slots.write().unwrap();
+        slots.remove(&slot_id).ok_or(Error::SlotNotFound)?;
+        Ok(())
+    }
+
+    pub async fn trigger(&self, sig_name: &str, data: Vec<u8>) -> Result<()> {
+        let sig = self.get_signal(sig_name).ok_or(Error::SignalNotFound)?;
+        let futures = FuturesUnordered::new();
+        let slots: Vec<_> = sig.slots.read().unwrap().values().cloned().collect();
+        // TODO: autoremove failed slots
+        for slot in slots {
+            debug!(target: "scene", "triggering {}", slot.name);
+            // Trigger the slot
+            let data = data.clone();
+            futures.push(async move { slot.notify.send(data).await.is_ok() });
+        }
+        let success: Vec<_> = futures.collect().await;
+        debug!(target: "scene", "trigger success: {success:?}");
+        Ok(())
     }
 
     pub fn add_method<S: Into<String>>(
@@ -341,19 +377,29 @@ pub type CallData = Vec<u8>;
 
 pub type SlotId = u32;
 
+#[derive(Clone)]
 pub struct Slot {
     pub name: String,
     pub notify: Sender<CallData>,
 }
 
+impl Slot {
+    pub fn new<S: Into<String>>(name: S) -> (Self, Receiver<CallData>) {
+        let (notify, recvr) = async_channel::unbounded();
+        let self_ = Self { name: name.into(), notify };
+        (self_, recvr)
+    }
+}
+
+type SignalPtr = Arc<Signal>;
+
 pub struct Signal {
     pub name: String,
     #[allow(dead_code)]
     pub desc: String,
     #[allow(dead_code)]
     pub fmt: Vec<CallArg>,
-    slots: Vec<Slot>,
-    freed: Vec<SlotId>,
+    slots: SyncRwLock<HashMap<SlotId, Slot>>,
 }
 
 #[derive(Clone, Debug)]

+ 2 - 2
bin/darkwallet/src/ui/button.rs

@@ -116,9 +116,9 @@ impl UIObject for Button {
             return false
         }
 
-        debug!(target: "ui::button", "Mouse button clicked!");
+        debug!(target: "ui::button", "Button clicked!");
         let node = self.node.upgrade().unwrap();
-        //node.trigger("click", vec![]).await.unwrap();
+        node.trigger("click", vec![]).await.unwrap();
 
         true
     }

+ 29 - 32
bin/darkwallet/src/ui/editbox.rs

@@ -446,39 +446,39 @@ impl EditBox {
         // clicking inside box will:
         // 1. make it active
         // 2. begin selection
-        if rect.contains(mouse_pos) {
-            window::show_keyboard(true);
-
+        if !rect.contains(mouse_pos) {
             if self.is_focused.get() {
-                debug!(target: "ui::editbox", "EditBox clicked");
-            } else {
-                debug!(target: "ui::editbox", "EditBox focused");
-                self.is_focused.set(true);
-            }
+                debug!(target: "ui::editbox", "EditBox unfocused");
+                self.is_focused.set(false);
+                self.selected.set_null(Role::Internal, 0).unwrap();
+                self.selected.set_null(Role::Internal, 1).unwrap();
 
-            let cpos = self.find_closest_glyph_idx(mouse_pos.x, &rect);
-
-            // set cursor pos
-            self.cursor_pos.set(cpos);
-            self.apply_cursor_scrolling();
+                self.redraw().await;
+            }
+            return false
+        }
 
-            // begin selection
-            self.selected.set_u32(Role::Internal, 0, cpos).unwrap();
-            self.selected.set_u32(Role::Internal, 1, cpos).unwrap();
+        window::show_keyboard(true);
 
-            self.mouse_btn_held.store(true, Ordering::Relaxed);
-        // click outside the box will make it unfocused
-        } else if self.is_focused.get() {
-            debug!(target: "ui::editbox", "EditBox unfocused");
-            self.is_focused.set(false);
-            self.selected.set_null(Role::Internal, 0).unwrap();
-            self.selected.set_null(Role::Internal, 1).unwrap();
+        if self.is_focused.get() {
+            debug!(target: "ui::editbox", "EditBox clicked");
         } else {
-            // Do nothing. Click was outside editbox, and editbox wasn't focused
-            return false
+            debug!(target: "ui::editbox", "EditBox focused");
+            self.is_focused.set(true);
         }
 
-        self.redraw().await;
+        let cpos = self.find_closest_glyph_idx(mouse_pos.x, &rect);
+
+        // set cursor pos
+        self.cursor_pos.set(cpos);
+        self.apply_cursor_scrolling();
+
+        // begin selection
+        self.selected.set_u32(Role::Internal, 0, cpos).unwrap();
+        self.selected.set_u32(Role::Internal, 1, cpos).unwrap();
+
+        self.mouse_btn_held.store(true, Ordering::Relaxed);
+
         true
     }
     fn handle_click_up(&self, btn: MouseButton, pos: Point) -> bool {
@@ -1096,8 +1096,7 @@ impl UIObject for EditBox {
             return true
         }
 
-        self.handle_click_down(btn, mouse_pos).await;
-        true
+        self.handle_click_down(btn, mouse_pos).await
     }
 
     async fn handle_mouse_btn_up(&self, btn: MouseButton, mouse_pos: Point) -> bool {
@@ -1105,8 +1104,7 @@ impl UIObject for EditBox {
             return true
         }
 
-        self.handle_click_up(btn, mouse_pos);
-        true
+        self.handle_click_up(btn, mouse_pos)
     }
 
     async fn handle_mouse_move(&self, mouse_pos: Point) -> bool {
@@ -1114,8 +1112,7 @@ impl UIObject for EditBox {
             return false
         }
 
-        self.handle_cursor_move(mouse_pos).await;
-        false
+        self.handle_cursor_move(mouse_pos).await
     }
 
     async fn handle_touch(&self, phase: TouchPhase, id: u64, touch_pos: Point) -> bool {

+ 12 - 1
script/evgrd/Makefile

@@ -1,3 +1,14 @@
+.POSIX:
+
+# Cargo binary
+CARGO = cargo +nightly
+
+BIN = evgrd
+
+all: $(BIN)
+
 evgrd:
-	LOG_TARGETS=evgrd cargo run --bin evgrd --features=build-daemon
+	LOG_TARGETS=evgrd $(CARGO) run --bin evgrd --features=build-daemon
+
+.PHONY: all