Kaynağa Gözat

app: add the help popup for /me when typing

darkfi 2 gün önce
ebeveyn
işleme
08d8ccd2f4
2 değiştirilmiş dosya ile 205 ekleme ve 19 silme
  1. 201 19
      bin/app/src/app/schema/chat.rs
  2. 4 0
      bin/app/src/logger.rs

+ 201 - 19
bin/app/src/app/schema/chat.rs

@@ -1285,17 +1285,32 @@ pub async fn make(
     layer_node.link(node);
 
     // Commands help hint
-    let cmd_layer_node = create_layer("cmd_hint_layer");
+    let mut cmd_layer_node = create_layer("cmd_hint_layer");
+
+    // Number of visible command rows (0, 1, or 2). Drives the popup height
+    // so it shrinks/grows as rows are filtered by the typed text.
+    let mut prop = Property::new("vis_rows", PropertyType::Float32, PropertySubType::Null);
+    prop.set_defaults_f32(vec![0.]).unwrap();
+    cmd_layer_node.add_property(prop).unwrap();
+    let cmd_vis_rows_prop = cmd_layer_node.get_property("vis_rows").unwrap();
+
+    // Whether the /nick row is visible (1.0/0.0). Positions the /me row
+    // below it when both rows are shown.
+    let mut prop = Property::new("nick_row_vis", PropertyType::Float32, PropertySubType::Null);
+    prop.set_defaults_f32(vec![0.]).unwrap();
+    cmd_layer_node.add_property(prop).unwrap();
+    let nick_row_vis_prop = cmd_layer_node.get_property("nick_row_vis").unwrap();
+
     let prop = cmd_layer_node.get_property("rect").unwrap();
     prop.set_f32(atom, Role::App, 0, 5.).unwrap();
-    let code = cc.compile("editz_bg_top_y - CMD_HELP_HEIGHT - CMD_HELP_GAP").unwrap();
-    //let code = cc.compile("h - 60 - 80").unwrap();
-    //let code = cc.compile("h - 60 - 300").unwrap();
+    let code = cc.compile("editz_bg_top_y - CMD_HELP_HEIGHT * vis_rows - CMD_HELP_GAP").unwrap();
     prop.set_expr(atom, Role::App, 1, code).unwrap();
     let code = cc.compile("w - 2 * CMD_HELP_GAP").unwrap();
     prop.set_expr(atom, Role::App, 2, code).unwrap();
-    prop.set_f32(atom, Role::App, 3, CMD_HELP_HEIGHT).unwrap();
+    let code = cc.compile("CMD_HELP_HEIGHT * vis_rows").unwrap();
+    prop.set_expr(atom, Role::App, 3, code).unwrap();
     prop.add_depend(&editbox_bg_rect_prop, 1, "editz_bg_top_y");
+    prop.add_depend(&cmd_vis_rows_prop, 0, "vis_rows");
     cmd_layer_node.set_property_bool(atom, Role::App, "is_visible", false).unwrap();
     cmd_layer_node.set_property_u32(atom, Role::App, "z_index", 3).unwrap();
     let cmd_layer_node =
@@ -1305,6 +1320,39 @@ pub async fn make(
     let cmd_hint_is_visible =
         PropertyBool::wrap(&cmd_layer_node, Role::App, "is_visible", 0).unwrap();
 
+    // The /nick row sits at the top of the popup
+    let nick_row_node = create_layer("nick_row");
+    let prop = nick_row_node.get_property("rect").unwrap();
+    prop.set_f32(atom, Role::App, 0, 0.).unwrap();
+    prop.set_f32(atom, Role::App, 1, 0.).unwrap();
+    let code = cc.compile("w").unwrap();
+    prop.set_expr(atom, Role::App, 2, code).unwrap();
+    prop.set_f32(atom, Role::App, 3, CMD_HELP_HEIGHT).unwrap();
+    nick_row_node.set_property_bool(atom, Role::App, "is_visible", false).unwrap();
+    let nick_row_node =
+        nick_row_node.setup(|me| Layer::new(me, renderer.clone(), redraw.clone())).await;
+    cmd_layer_node.link(nick_row_node.clone());
+
+    let nick_row_is_visible =
+        PropertyBool::wrap(&nick_row_node, Role::App, "is_visible", 0).unwrap();
+
+    // The /me row sits below the /nick row, or at the top when /nick is filtered out
+    let me_row_node = create_layer("me_row");
+    let prop = me_row_node.get_property("rect").unwrap();
+    prop.set_f32(atom, Role::App, 0, 0.).unwrap();
+    let code = cc.compile("CMD_HELP_HEIGHT * nick_row_vis").unwrap();
+    prop.set_expr(atom, Role::App, 1, code).unwrap();
+    let code = cc.compile("w").unwrap();
+    prop.set_expr(atom, Role::App, 2, code).unwrap();
+    prop.set_f32(atom, Role::App, 3, CMD_HELP_HEIGHT).unwrap();
+    prop.add_depend(&nick_row_vis_prop, 0, "nick_row_vis");
+    me_row_node.set_property_bool(atom, Role::App, "is_visible", false).unwrap();
+    let me_row_node =
+        me_row_node.setup(|me| Layer::new(me, renderer.clone(), redraw.clone())).await;
+    cmd_layer_node.link(me_row_node.clone());
+
+    let me_row_is_visible = PropertyBool::wrap(&me_row_node, Role::App, "is_visible", 0).unwrap();
+
     // Make nick label clickable
     let node = create_button("nickcmd_btn");
     node.set_property_bool(atom, Role::App, "is_active", true).unwrap();
@@ -1330,9 +1378,36 @@ pub async fn make(
     layer_node.push_task(listen_click);
 
     let node = node.setup(|me| Button::new(me, renderer.clone(), redraw.clone())).await;
-    cmd_layer_node.link(node);
+    nick_row_node.link(node);
+
+    // Make /me label clickable
+    let node = create_button("mecmd_btn");
+    node.set_property_bool(atom, Role::App, "is_active", true).unwrap();
+    let prop = node.get_property("rect").unwrap();
+    prop.set_f32(atom, Role::App, 0, 0.).unwrap();
+    prop.set_f32(atom, Role::App, 1, 0.).unwrap();
+    prop.set_f32(atom, Role::App, 2, CMD_HELP_NICK_CMD_WIDTH).unwrap();
+    prop.set_f32(atom, Role::App, 3, CMD_HELP_HEIGHT).unwrap();
+
+    let (slot, recvr) = Slot::new("mecmd_clicked");
+    node.register("click", slot).unwrap();
+    let editz_text2 = editz_text.clone();
+    let redraw2 = redraw.clone();
+    let listen_click = ex.spawn(async move {
+        while let Ok(_) = recvr.recv().await {
+            info!(target: "app::chat", "clicked /me");
+            let atom = &mut redraw2.make_guard(gfxtag!("mecmd_clicked action"));
+            // This will autohide this popup due to ending in a space.
+            // Setting the property will retrigger the logic whether to show popup.
+            editz_text2.set(atom, "/me ");
+        }
+    });
+    layer_node.push_task(listen_click);
+
+    let node = node.setup(|me| Button::new(me, renderer.clone(), redraw.clone())).await;
+    me_row_node.link(node);
 
-    // Create the actionbar bg
+    // Create the /nick row actionbar bg
     let node = create_vector_art("cmd_hint_bg");
     let prop = node.get_property("rect").unwrap();
     prop.set_f32(atom, Role::App, 0, 0.).unwrap();
@@ -1373,7 +1448,7 @@ pub async fn make(
     );
 
     let node = node.setup(|me| VectorArt::new(me, shape, renderer.clone(), redraw.clone())).await;
-    cmd_layer_node.link(node);
+    nick_row_node.link(node);
 
     // Create some text
     let node = create_text("cmd_nick_label");
@@ -1398,7 +1473,7 @@ pub async fn make(
             Text::new(me, window_scale.clone(), renderer.clone(), i18n_fish.clone(), redraw.clone())
         })
         .await;
-    cmd_layer_node.link(node);
+    nick_row_node.link(node);
 
     // Create some text
     let node = create_text("cmd_nick_desc_label");
@@ -1423,7 +1498,96 @@ pub async fn make(
             Text::new(me, window_scale.clone(), renderer.clone(), i18n_fish.clone(), redraw.clone())
         })
         .await;
-    cmd_layer_node.link(node);
+    nick_row_node.link(node);
+
+    // Create the /me row actionbar bg
+    let node = create_vector_art("me_cmd_hint_bg");
+    let prop = node.get_property("rect").unwrap();
+    prop.set_f32(atom, Role::App, 0, 0.).unwrap();
+    prop.set_f32(atom, Role::App, 1, 0.).unwrap();
+    prop.set_expr(atom, Role::App, 2, expr::load_var("w")).unwrap();
+    prop.set_expr(atom, Role::App, 3, expr::load_var("h")).unwrap();
+    node.set_property_u32(atom, Role::App, "z_index", 0).unwrap();
+
+    let mut shape = VectorShape::new();
+    shape.add_filled_box(
+        expr::const_f32(CMD_HELP_GAP),
+        expr::const_f32(CMD_HELP_GAP),
+        cc.compile("w - CMD_HELP_GAP").unwrap(),
+        cc.compile("h - CMD_HELP_GAP").unwrap(),
+        [0., 0.11, 0.11, 0.4],
+    );
+    shape.add_filled_box(
+        expr::const_f32(CMD_HELP_GAP),
+        expr::const_f32(CMD_HELP_GAP),
+        expr::const_f32(CMD_HELP_NICK_CMD_WIDTH),
+        cc.compile("h - CMD_HELP_GAP").unwrap(),
+        [0., 0.3, 0.25, 1.],
+    );
+    shape.add_filled_box(
+        expr::const_f32(CMD_HELP_NICK_CMD_WIDTH),
+        expr::const_f32(CMD_HELP_GAP),
+        expr::const_f32(CMD_HELP_NICK_DESC_WIDTH),
+        cc.compile("h - CMD_HELP_GAP").unwrap(),
+        [0., 0.11, 0.11, 1.],
+    );
+    shape.add_outline(
+        expr::const_f32(0.),
+        expr::const_f32(0.),
+        expr::load_var("w"),
+        expr::load_var("h"),
+        1.,
+        [0.29, 0.51, 0.45, 1.],
+    );
+
+    let node = node.setup(|me| VectorArt::new(me, shape, renderer.clone(), redraw.clone())).await;
+    me_row_node.link(node);
+
+    // Create some text
+    let node = create_text("cmd_me_label");
+    let prop = node.get_property("rect").unwrap();
+    prop.set_f32(atom, Role::App, 0, CMD_HELP_CMD_LABEL_X_INSET).unwrap();
+    prop.set_f32(atom, Role::App, 1, CMD_HELP_LABEL_Y).unwrap();
+    prop.set_f32(atom, Role::App, 2, 1000.).unwrap();
+    prop.set_f32(atom, Role::App, 3, 1000.).unwrap();
+    node.set_property_f32(atom, Role::App, "font_size", CMD_HELP_CMD_FONTSIZE).unwrap();
+    node.set_property_str(atom, Role::App, "text", "/me").unwrap();
+    let prop = node.get_property("text_color").unwrap();
+    prop.set_f32(atom, Role::App, 0, 0.64).unwrap();
+    prop.set_f32(atom, Role::App, 1, 1.).unwrap();
+    prop.set_f32(atom, Role::App, 2, 0.83).unwrap();
+    prop.set_f32(atom, Role::App, 3, 1.).unwrap();
+    node.set_property_u32(atom, Role::App, "z_index", 1).unwrap();
+
+    let node = node
+        .setup(|me| {
+            Text::new(me, window_scale.clone(), renderer.clone(), i18n_fish.clone(), redraw.clone())
+        })
+        .await;
+    me_row_node.link(node);
+
+    // Create some text
+    let node = create_text("cmd_me_desc_label");
+    let prop = node.get_property("rect").unwrap();
+    prop.set_f32(atom, Role::App, 0, CMD_HELP_NICK_DESC_X).unwrap();
+    prop.set_f32(atom, Role::App, 1, CMD_HELP_LABEL_Y).unwrap();
+    prop.set_f32(atom, Role::App, 2, 1000.).unwrap();
+    prop.set_f32(atom, Role::App, 3, 1000.).unwrap();
+    node.set_property_f32(atom, Role::App, "font_size", FONTSIZE).unwrap();
+    node.set_property_str(atom, Role::App, "text", "Send an action message").unwrap();
+    let prop = node.get_property("text_color").unwrap();
+    prop.set_f32(atom, Role::App, 0, 0.).unwrap();
+    prop.set_f32(atom, Role::App, 1, 0.94).unwrap();
+    prop.set_f32(atom, Role::App, 2, 1.).unwrap();
+    prop.set_f32(atom, Role::App, 3, 1.).unwrap();
+    node.set_property_u32(atom, Role::App, "z_index", 1).unwrap();
+
+    let node = node
+        .setup(|me| {
+            Text::new(me, window_scale.clone(), renderer.clone(), i18n_fish.clone(), redraw.clone())
+        })
+        .await;
+    me_row_node.link(node);
 
     let editz_text_sub = editz_text.prop().subscribe_modify();
     let redraw = redraw.clone();
@@ -1436,15 +1600,33 @@ pub async fn make(
             // We want to avoid setting the property multiple times to the same value
             // because then it triggers unnecessary redraw work.
 
-            // Only show popup for "/ni", "/nick", but not for: "", "/nick ", "/nick foo"
-            if !text.is_empty() && "/nick".starts_with(&text) && text.len() <= "/nick".len() {
-                if !cmd_hint_is_visible.get() {
-                    cmd_hint_is_visible.set(atom, true);
-                }
-            } else {
-                if cmd_hint_is_visible.get() {
-                    cmd_hint_is_visible.set(atom, false);
-                }
+            // Show the /nick row for any prefix of "/nick", including the
+            // full command (it still needs an argument). Show the /me row
+            // only for strict prefixes ("/", "/m") — once "/me" is fully
+            // typed the popup hides.
+            let nick_vis = !text.is_empty() && "/nick".starts_with(&text);
+            let me_vis = !text.is_empty() && text != "/me" && "/me".starts_with(&text);
+            let popup_vis = nick_vis || me_vis;
+
+            if nick_vis != nick_row_is_visible.get() {
+                nick_row_is_visible.set(atom, nick_vis);
+            }
+            if me_vis != me_row_is_visible.get() {
+                me_row_is_visible.set(atom, me_vis);
+            }
+            if popup_vis != cmd_hint_is_visible.get() {
+                cmd_hint_is_visible.set(atom, popup_vis);
+            }
+
+            // Geometry inputs: the row count drives the popup height and
+            // the /me row sits below the /nick row when both are shown.
+            let vis_rows = (nick_vis as u8 + me_vis as u8) as f32;
+            if cmd_vis_rows_prop.get_f32(0).unwrap() != vis_rows {
+                cmd_vis_rows_prop.set_f32(atom, Role::App, 0, vis_rows).unwrap();
+            }
+            let nick_vis_f = nick_vis as u8 as f32;
+            if nick_row_vis_prop.get_f32(0).unwrap() != nick_vis_f {
+                nick_row_vis_prop.set_f32(atom, Role::App, 0, nick_vis_f).unwrap();
             }
         }
     });

+ 4 - 0
bin/app/src/logger.rs

@@ -57,6 +57,9 @@ static MUTED_TARGETS: &[&'static str] = &[
     "turso_sqlite",
     "walletdb",
     "rpc::client",
+    // Bridged `log` crate records (pulseaudio protocol trace spam)
+    "log",
+    "pulseaudio",
 ];
 #[cfg(not(target_os = "android"))]
 static ALLOW_TRACE: &[&'static str] = &["ui", "app", "gfx", "plugin", "app", "main"];
@@ -126,6 +129,7 @@ pub fn setup_logging() -> Option<WorkerGuard> {
                         "walletdb",
                         "rpc::client",
                     ])
+                    .targets_level(["log", "pulseaudio"], Level::Warn)
                     .default_level(Level::Trace),
             );