Преглед изворни кода

wallet: migrate to using handle_char() through the tree

darkfi пре 1 година
родитељ
комит
c9a9726c93

+ 1 - 0
bin/darkwallet/Cargo.toml

@@ -42,6 +42,7 @@ url = "2.5.2"
 semver = "1.0.23"
 chrono = "0.4.38"
 async-gen = "0.2"
+async-trait = "0.1.82"
 
 [patch.crates-io]
 freetype-rs = { git = "https://github.com/narodnik/freetype-rs" }

+ 6 - 0
bin/darkwallet/src/app/node.rs

@@ -44,6 +44,9 @@ pub fn create_layer(sg: &mut SceneGraph, name: &str) -> SceneNodeId {
     prop.allow_exprs();
     node.add_property(prop).unwrap();
 
+    let prop = Property::new("z_index", PropertyType::Uint32, PropertySubType::Null);
+    node.add_property(prop).unwrap();
+
     node.id
 }
 
@@ -75,6 +78,9 @@ pub fn create_button(sg: &mut SceneGraph, name: &str) -> SceneNodeId {
     prop.allow_exprs();
     node.add_property(prop).unwrap();
 
+    let prop = Property::new("z_index", PropertyType::Uint32, PropertySubType::Null);
+    node.add_property(prop).unwrap();
+
     node.add_signal("click", "Button clicked event", vec![]).unwrap();
 
     node.id

+ 14 - 3
bin/darkwallet/src/ui/button.rs

@@ -24,13 +24,13 @@ use std::sync::{
 
 use crate::{
     gfx::{GraphicsEventPublisherPtr, Point, Rectangle},
-    prop::{PropertyBool, PropertyPtr, Role},
+    prop::{PropertyBool, PropertyPtr, Role, PropertyUint32},
     pubsub::Subscription,
     scene::{Pimpl, SceneGraphPtr2, SceneNodeId},
     ExecutorPtr,
 };
 
-use super::{eval_rect, read_rect};
+use super::{eval_rect, read_rect, UIObject};
 
 pub type ButtonPtr = Arc<Button>;
 
@@ -42,6 +42,7 @@ pub struct Button {
 
     is_active: PropertyBool,
     rect: PropertyPtr,
+    z_index: PropertyUint32,
 
     mouse_btn_held: AtomicBool,
 }
@@ -58,6 +59,7 @@ impl Button {
         //let node_name = node.name.clone();
         let is_active = PropertyBool::wrap(node, Role::Internal, "is_active", 0).unwrap();
         let rect = node.get_property("rect").expect("Button::rect");
+        let z_index = PropertyUint32::wrap(node, Role::Internal, "z_index", 0).unwrap();
         //let sig = node.get_signal("click").expect("Button::click");
         drop(scene_graph);
 
@@ -79,7 +81,9 @@ impl Button {
 
             let tasks = vec![mouse_btn_down_task, mouse_btn_up_task, touch_task];
 
-            Self { node_id, tasks, sg, is_active, rect, mouse_btn_held: AtomicBool::new(false) }
+            Self { node_id, tasks, sg, is_active, rect, 
+                z_index,
+                mouse_btn_held: AtomicBool::new(false) }
         });
 
         Pimpl::Button(self_)
@@ -229,3 +233,10 @@ impl Button {
         }
     }
 }
+
+impl UIObject for Button {
+    fn z_index(&self) -> u32 {
+        self.z_index.get()
+    }
+}
+

+ 7 - 1
bin/darkwallet/src/ui/chatview/mod.rs

@@ -54,7 +54,7 @@ use crate::{
     ExecutorPtr,
 };
 
-use super::{eval_rect, get_parent_rect, read_rect, DrawUpdate, OnModify};
+use super::{eval_rect, get_parent_rect, read_rect, DrawUpdate, OnModify, UIObject};
 
 const EPSILON: f32 = 0.001;
 const BIG_EPSILON: f32 = 0.05;
@@ -1029,3 +1029,9 @@ impl ChatView {
         // ... todo fin
     }
 }
+
+impl UIObject for ChatView {
+    fn z_index(&self) -> u32 {
+        self.z_index.get()
+    }
+}

+ 67 - 90
bin/darkwallet/src/ui/editbox.rs

@@ -16,6 +16,7 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
+use async_trait::async_trait;
 use miniquad::{window, KeyCode, KeyMods, MouseButton, TouchPhase};
 use rand::{rngs::OsRng, Rng};
 use std::{
@@ -45,7 +46,7 @@ use crate::{
     ExecutorPtr,
 };
 
-use super::{eval_rect, get_parent_rect, read_rect, DrawUpdate, OnModify, Stoppable};
+use super::{eval_rect, get_parent_rect, read_rect, DrawUpdate, OnModify, Stoppable, UIObject};
 
 // Pixel width of the cursor
 const CURSOR_WIDTH: f32 = 2.;
@@ -176,6 +177,10 @@ pub struct EditBox {
     debug: PropertyBool,
 
     mouse_btn_held: AtomicBool,
+
+    // I mean this is wrong since it's using the parent instead of the screen
+    // But keep it for now...
+    parent_rect: SyncMutex<Option<Rectangle>>,
 }
 
 impl EditBox {
@@ -213,11 +218,6 @@ impl EditBox {
 
         let self_ = Arc::new_cyclic(|me: &Weak<Self>| {
             // 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 { while Self::process_char(&me2, &ev_sub).await {} });
-
             let ev_sub = event_pub.subscribe_key_down();
             let me2 = me.clone();
             let key_down_task =
@@ -300,7 +300,6 @@ impl EditBox {
 
             // on modify tasks too
             let mut tasks = vec![
-                char_task,
                 key_down_task,
                 mouse_btn_down_task,
                 mouse_btn_up_task,
@@ -337,6 +336,8 @@ impl EditBox {
                 debug,
 
                 mouse_btn_held: AtomicBool::new(false),
+
+                parent_rect: SyncMutex::new(None),
             }
         });
 
@@ -477,45 +478,6 @@ impl EditBox {
         Ok(())
     }
 
-    async fn process_char(me: &Weak<Self>, 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 false
-        };
-
-        // First filter for only single digit keys
-        if DISALLOWED_CHARS.contains(&key) {
-            return true
-        }
-
-        let Some(self_) = me.upgrade() else {
-            // Should not happen
-            panic!("self destroyed before char_task was stopped!");
-        };
-
-        if !self_.is_focused.get() {
-            return true
-        }
-
-        if mods.ctrl || mods.alt {
-            if repeat {
-                return true
-            }
-            self_.handle_shortcut(key, &mods).await;
-            return true
-        }
-
-        let actions = {
-            let mut repeater = self_.key_repeat.lock().unwrap();
-            repeater.key_down(PressedKey::Char(key), repeat)
-        };
-        debug!(target: "ui::editbox", "Key {:?} has {} actions", key, actions);
-        for _ in 0..actions {
-            self_.insert_char(key).await;
-        }
-        true
-    }
-
     async fn process_key_down(
         me: &Weak<Self>,
         ev_sub: &Subscription<(KeyCode, KeyMods, bool)>,
@@ -656,7 +618,7 @@ impl EditBox {
 
         let mouse_pos = Point::from([mouse_x, mouse_y]);
 
-        let Some(rect) = self.get_cached_world_rect().await else { return };
+        let Some(rect) = self.cached_rect() else { return };
 
         // clicking inside box will:
         // 1. make it active
@@ -713,7 +675,7 @@ impl EditBox {
         // just scroll to the end
         // also set cursor_pos too
 
-        let Some(rect) = self.get_cached_world_rect().await else { return };
+        let Some(rect) = self.cached_rect() else { return };
         let cpos = self.find_closest_glyph_idx(mouse_x, &rect);
 
         self.cursor_pos.set(cpos);
@@ -1115,30 +1077,6 @@ impl EditBox {
         };
         Some(rect)
     }
-    async fn get_parent_rect(&self) -> Option<Rectangle> {
-        let sg = self.sg.lock().await;
-        let node = sg.get_node(self.node_id).unwrap();
-        let Some(parent_rect) = get_parent_rect(&sg, node) else {
-            return None;
-        };
-        drop(sg);
-        Some(parent_rect)
-    }
-    async fn get_cached_world_rect(&self) -> Option<Rectangle> {
-        // NBD if it's slightly wrong
-        let mut rect = self.cached_rect()?;
-
-        // If layers can be nested and we use offsets for (x, y)
-        // then this will be incorrect for nested layers.
-        // For now we don't allow nesting of layers.
-        let parent_rect = self.get_parent_rect().await?;
-
-        // Offset rect which is now in world coords
-        rect.x += parent_rect.x;
-        rect.y += parent_rect.y;
-
-        Some(rect)
-    }
 
     /// Whenever the cursor property is modified this MUST be called
     /// to recalculate the scroll x property.
@@ -1193,17 +1131,11 @@ impl EditBox {
     }
 
     async fn redraw(&self) {
-        let sg = self.sg.lock().await;
-        let node = sg.get_node(self.node_id).unwrap();
-
-        let Some(parent_rect) = get_parent_rect(&sg, node) else {
+        let Some(draw_update) = self.draw_cached() else {
+            error!(target: "ui::editbox", "Text failed to draw");
             return;
         };
 
-        let Some(draw_update) = self.draw(&sg, &parent_rect) else {
-            error!(target: "ui::editbox", "Text {:?} failed to draw", node);
-            return;
-        };
         self.render_api.replace_draw_calls(draw_update.draw_calls);
         //debug!(target: "ui::editbox", "replace draw calls done");
         for buffer_id in draw_update.freed_buffers {
@@ -1214,17 +1146,9 @@ impl EditBox {
         }
     }
 
-    pub fn draw(&self, sg: &SceneGraph, parent_rect: &Rectangle) -> Option<DrawUpdate> {
-        //debug!(target: "ui::editbox", "EditBox::draw()");
-        // Only used for debug messages
-        let node = sg.get_node(self.node_id).unwrap();
-
-        if let Err(err) = eval_rect(self.rect.clone(), parent_rect) {
-            panic!("Node {:?} bad rect property: {}", node, err);
-        }
-
+    fn draw_cached(&self) -> Option<DrawUpdate> {
         let Ok(rect) = read_rect(self.rect.clone()) else {
-            panic!("Node {:?} bad rect property", node);
+            panic!("Node bad rect property")
         };
 
         // draw will recalc this when it's None
@@ -1248,6 +1172,10 @@ impl EditBox {
             num_elements: render_info.mesh.num_elements,
         };
 
+        let Some(parent_rect) = self.parent_rect.lock().unwrap().clone() else {
+            return None
+        };
+
         let off_x = rect.x / parent_rect.w;
         let off_y = rect.y / parent_rect.h;
         let scale_x = 1. / parent_rect.w;
@@ -1273,6 +1201,19 @@ impl EditBox {
         })
     }
 
+    pub fn draw(&self, sg: &SceneGraph, parent_rect: &Rectangle) -> Option<DrawUpdate> {
+        *self.parent_rect.lock().unwrap() = Some(parent_rect.clone());
+        //debug!(target: "ui::editbox", "EditBox::draw()");
+        // Only used for debug messages
+        let node = sg.get_node(self.node_id).unwrap();
+
+        if let Err(err) = eval_rect(self.rect.clone(), parent_rect) {
+            panic!("Node {:?} bad rect property: {}", node, err);
+        }
+
+        self.draw_cached()
+    }
+
     async fn send_event(&self) {
         let text = self.text.get();
         debug!(target: "ui::editbox", "sending text {}", text);
@@ -1307,6 +1248,42 @@ impl Stoppable for EditBox {
     }
 }
 
+#[async_trait]
+impl UIObject for EditBox {
+    fn z_index(&self) -> u32 {
+        self.z_index.get()
+    }
+
+    async fn handle_char(&self, sg: &SceneGraph, key: char, mods: KeyMods, repeat: bool) -> bool {
+        // First filter for only single digit keys
+        if DISALLOWED_CHARS.contains(&key) {
+            return false
+        }
+
+        if !self.is_focused.get() {
+            return false
+        }
+
+        if mods.ctrl || mods.alt {
+            if repeat {
+                return false
+            }
+            self.handle_shortcut(key, &mods).await;
+            return true
+        }
+
+        let actions = {
+            let mut repeater = self.key_repeat.lock().unwrap();
+            repeater.key_down(PressedKey::Char(key), repeat)
+        };
+        debug!(target: "ui::editbox", "Key {:?} has {} actions", key, actions);
+        for _ in 0..actions {
+            self.insert_char(key).await;
+        }
+        true
+    }
+}
+
 /// Filter these char events from being handled since we handle them
 /// using the key_up/key_down events.
 /// Avoids duplicate processing of keyboard input events.

+ 8 - 1
bin/darkwallet/src/ui/image.rs

@@ -31,7 +31,7 @@ use crate::{
     ExecutorPtr,
 };
 
-use super::{eval_rect, get_parent_rect, read_rect, DrawUpdate, OnModify};
+use super::{eval_rect, get_parent_rect, read_rect, DrawUpdate, OnModify, UIObject};
 
 pub type ImagePtr = Arc<Image>;
 
@@ -229,3 +229,10 @@ impl Drop for Image {
         self.render_api.delete_texture(texture_id);
     }
 }
+
+impl UIObject for Image {
+    fn z_index(&self) -> u32 {
+        self.z_index.get()
+    }
+}
+

+ 30 - 2
bin/darkwallet/src/ui/layer.rs

@@ -16,18 +16,20 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
+use async_trait::async_trait;
 use async_recursion::async_recursion;
+use miniquad::KeyMods;
 use rand::{rngs::OsRng, Rng};
 use std::sync::{Arc, Weak};
 
 use crate::{
     gfx::{GfxDrawCall, GfxDrawInstruction, Rectangle, RenderApiPtr},
-    prop::{PropertyBool, PropertyPtr, Role},
+    prop::{PropertyBool, PropertyPtr, Role, PropertyUint32},
     scene::{Pimpl, SceneGraph, SceneGraphPtr2, SceneNodeId},
     ExecutorPtr,
 };
 
-use super::{eval_rect, get_parent_rect, read_rect, DrawUpdate, OnModify, Stoppable};
+use super::{eval_rect, get_parent_rect, read_rect, DrawUpdate, OnModify, Stoppable, UIObject, get_child_nodes_ordered, get_ui_object};
 
 pub type RenderLayerPtr = Arc<RenderLayer>;
 
@@ -43,6 +45,7 @@ pub struct RenderLayer {
 
     is_visible: PropertyBool,
     rect: PropertyPtr,
+    z_index: PropertyUint32,
 }
 
 impl RenderLayer {
@@ -59,6 +62,7 @@ impl RenderLayer {
         let is_visible = PropertyBool::wrap(node, Role::Internal, "is_visible", 0)
             .expect("RenderLayer::is_visible");
         let rect = node.get_property("rect").expect("RenderLayer::rect");
+        let z_index = PropertyUint32::wrap(node, Role::Internal, "z_index", 0).unwrap();
         drop(sg);
 
         let self_ = Arc::new_cyclic(|me: &Weak<Self>| {
@@ -73,12 +77,17 @@ impl RenderLayer {
                 dc_key: OsRng.gen(),
                 is_visible,
                 rect,
+                z_index
             }
         });
 
         Pimpl::RenderLayer(self_)
     }
 
+    pub async fn handle_char(&self, sg: &SceneGraph, key: char, mods: KeyMods, repeat: bool) -> bool {
+        false
+    }
+
     async fn redraw(self: Arc<Self>) {
         let sg = self.sg.lock().await;
         let node = sg.get_node(self.node_id).unwrap();
@@ -174,3 +183,22 @@ impl RenderLayer {
 impl Stoppable for RenderLayer {
     async fn stop(&self) {}
 }
+
+#[async_trait]
+impl UIObject for RenderLayer {
+    fn z_index(&self) -> u32 {
+        self.z_index.get()
+    }
+
+    async fn handle_char(&self, sg: &SceneGraph, key: char, mods: KeyMods, repeat: bool) -> bool {
+        for child_id in get_child_nodes_ordered(&sg, self.node_id) {
+            let node = sg.get_node(child_id).unwrap();
+            let obj = get_ui_object(node);
+            if obj.handle_char(&sg, key, mods, repeat).await {
+                return true
+            }
+        }
+        false
+    }
+}
+

+ 39 - 1
bin/darkwallet/src/ui/mod.rs

@@ -16,14 +16,16 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
+use async_trait::async_trait;
 use std::sync::{Arc, Weak};
+use miniquad::KeyMods;
 
 use crate::{
     error::{Error, Result},
     expr::{SExprMachine, SExprVal},
     gfx::{GfxBufferId, GfxDrawCall, GfxTextureId, Rectangle},
     prop::{PropertyPtr, Role},
-    scene::{SceneGraph, SceneNode, SceneNodeId, SceneNodeType},
+    scene::{SceneGraph, SceneNode, SceneNodeId, SceneNodeType, Pimpl},
     ExecutorPtr,
 };
 
@@ -51,6 +53,13 @@ pub trait Stoppable {
     async fn stop(&self);
 }
 
+#[async_trait]
+pub trait UIObject: Sync {
+    fn z_index(&self) -> u32;
+
+    async fn handle_char(&self, sg: &SceneGraph, key: char, mods: KeyMods, repeat: bool) -> bool { false }
+}
+
 pub struct DrawUpdate {
     pub key: u64,
     pub draw_calls: Vec<(u64, GfxDrawCall)>,
@@ -212,3 +221,32 @@ pub fn get_parent_rect(sg: &SceneGraph, node: &SceneNode) -> Option<Rectangle> {
     };
     Some(parent_rect)
 }
+
+pub fn get_ui_object<'a>(node: &'a SceneNode) -> &'a dyn UIObject {
+        match &node.pimpl {
+            Pimpl::RenderLayer(layer) => layer.as_ref(),
+            Pimpl::VectorArt(svg) => svg.as_ref(),
+            Pimpl::Text(txt) => txt.as_ref(),
+            Pimpl::EditBox(editb) => editb.as_ref(),
+            Pimpl::ChatView(chat) => chat.as_ref(),
+            Pimpl::Image(img) => img.as_ref(),
+            Pimpl::Button(btn) => btn.as_ref(),
+            _ => panic!("unhandled type for get_ui_object"),
+        }
+}
+
+pub fn get_child_nodes_ordered(sg: &SceneGraph, node_id: SceneNodeId) -> Vec<SceneNodeId> {
+    let mut child_nodes = vec![];
+    let self_node = sg.get_node(node_id).unwrap();
+    for child_inf in self_node.get_children2() {
+        let node = sg.get_node(child_inf.id).unwrap();
+        let obj = get_ui_object(node);
+        let z_index = obj.z_index();
+        child_nodes.push((node.id, z_index));
+    }
+    child_nodes.sort_unstable_by_key(|(node_id, _)| *node_id);
+
+    let nodes = child_nodes.into_iter().rev().map(|(node_id, _)| node_id).collect();
+    nodes
+}
+

+ 8 - 1
bin/darkwallet/src/ui/text.rs

@@ -34,7 +34,7 @@ use crate::{
     ExecutorPtr,
 };
 
-use super::{eval_rect, get_parent_rect, read_rect, DrawUpdate, OnModify, Stoppable};
+use super::{eval_rect, get_parent_rect, read_rect, DrawUpdate, OnModify, Stoppable, UIObject};
 
 pub type TextPtr = Arc<Text>;
 
@@ -258,3 +258,10 @@ impl Stoppable for Text {
         self.render_api.delete_texture(texture_id);
     }
 }
+
+impl UIObject for Text {
+    fn z_index(&self) -> u32 {
+        self.z_index.get()
+    }
+}
+

+ 8 - 1
bin/darkwallet/src/ui/vector_art/mod.rs

@@ -32,7 +32,7 @@ use crate::{
     ExecutorPtr,
 };
 
-use super::{eval_rect, get_parent_rect, read_rect, DrawUpdate, OnModify, Stoppable};
+use super::{eval_rect, get_parent_rect, read_rect, DrawUpdate, OnModify, Stoppable, UIObject};
 
 pub mod shape;
 use shape::VectorShape;
@@ -179,3 +179,10 @@ impl Stoppable for VectorArt {
         }
     }
 }
+
+impl UIObject for VectorArt {
+    fn z_index(&self) -> u32 {
+        self.z_index.get()
+    }
+}
+

+ 77 - 5
bin/darkwallet/src/ui/win.rs

@@ -17,20 +17,24 @@
  */
 
 use std::sync::{Arc, Weak};
+use miniquad::KeyMods;
 
 use crate::{
     gfx::{GfxDrawCall, GraphicsEventPublisherPtr, Rectangle, RenderApiPtr},
     prop::{PropertyPtr, Role},
+    pubsub::Subscription,
     scene::{Pimpl, SceneGraph, SceneGraphPtr2, SceneNodeId},
     ExecutorPtr,
 };
 
-use super::{OnModify, Stoppable};
+use super::{OnModify, Stoppable, get_child_nodes_ordered, get_ui_object};
 
 pub type WindowPtr = Arc<Window>;
 
 pub struct Window {
     node_id: SceneNodeId,
+    sg: SceneGraphPtr2,
+
     // Task is dropped at the end of the scope for Window, hence ending it
     #[allow(dead_code)]
     tasks: Vec<smol::Task<()>>,
@@ -84,6 +88,38 @@ impl Window {
                 }
             });
 
+            let ev_sub = event_pub.subscribe_char();
+            let me2 = me.clone();
+            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 { while Self::process_key_down(&me2, &ev_sub).await {} });
+
+            let ev_sub = event_pub.subscribe_key_up();
+            let me2 = me.clone();
+            let key_up_task =
+                ex.spawn(async move { while Self::process_key_up(&me2, &ev_sub).await {} });
+
+            let ev_sub = event_pub.subscribe_mouse_btn_down();
+            let me2 = me.clone();
+            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 { 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 { while Self::process_touch(&me2, &ev_sub).await {} });
+            */
+
             let sg2 = sg.clone();
             let redraw_fn = move |self_: Arc<Self>| {
                 let sg = sg2.clone();
@@ -96,15 +132,51 @@ impl Window {
             let mut on_modify = OnModify::new(ex.clone(), node_name, node_id, me.clone());
             on_modify.when_change(scale_prop, redraw_fn);
 
-            let mut tasks = on_modify.tasks;
-            tasks.push(resize_task);
-
-            Self { node_id, tasks, screen_size_prop, render_api }
+            let mut tasks = vec![
+                resize_task,
+                char_task,
+                //key_down_task,
+                //key_up_task,
+                //mouse_btn_down_task,
+                //mouse_btn_up_task,
+                //mouse_move_task,
+                //touch_task,
+            ];
+            tasks.append(&mut on_modify.tasks);
+
+            Self { node_id, sg, tasks, screen_size_prop, render_api }
         });
 
         Pimpl::Window(self_)
     }
 
+    async fn process_char(me: &Weak<Self>, ev_sub: &Subscription<(char, KeyMods, bool)>) -> bool {
+        let Ok((key, mods, repeat)) = ev_sub.receive().await else {
+            debug!(target: "ui::win", "Event relayer closed");
+            return false
+        };
+
+        let Some(self_) = me.upgrade() else {
+            // Should not happen
+            panic!("self destroyed before char_task was stopped!");
+        };
+
+        self_.handle_char(key, mods, repeat).await;
+        true
+    }
+
+    async fn handle_char(&self, key: char, mods: KeyMods, repeat: bool) {
+        let sg = self.sg.lock().await;
+
+        for child_id in get_child_nodes_ordered(&sg, self.node_id) {
+            let node = sg.get_node(child_id).unwrap();
+            let obj = get_ui_object(node);
+            if obj.handle_char(&sg, key, mods, repeat).await {
+                return
+            }
+        }
+    }
+
     pub async fn draw(&self, sg: &SceneGraph) {
         let screen_width = self.screen_size_prop.get_f32(0).unwrap();
         let screen_height = self.screen_size_prop.get_f32(1).unwrap();