Przeglądaj źródła

app/wallet/receive: show tooltip when the receive address is copied

epiphany 2 tygodni temu
rodzic
commit
41ed7fb1c4

+ 6 - 0
bin/app/src/app/schema/wallet/data.rs

@@ -52,6 +52,9 @@ mod android_ui_consts {
     pub const CHAT_BTN_SIZE: f32 = 200.;
     pub const CHAT_BTN_MARGIN: f32 = 20.;
 
+    pub const TOOLTIP_PADDING_X: f32 = 20.;
+    pub const TOOLTIP_PADDING_Y: f32 = 15.;
+
     pub const NETSTATUS_ICON_SIZE: f32 = 140.;
     pub const SETTINGS_ICON_SIZE: f32 = 140.;
     pub const NETLOGO_SCALE: f32 = 7.;
@@ -103,6 +106,9 @@ mod ui_consts {
     pub const CHAT_BTN_SIZE: f32 = 100.;
     pub const CHAT_BTN_MARGIN: f32 = 20.;
 
+    pub const TOOLTIP_PADDING_X: f32 = 10.;
+    pub const TOOLTIP_PADDING_Y: f32 = 6.;
+
     pub const NETSTATUS_ICON_SIZE: f32 = 60.;
     pub const SETTINGS_ICON_SIZE: f32 = 60.;
     pub const NETLOGO_SCALE: f32 = 3.5;

+ 25 - 0
bin/app/src/app/schema/wallet/receive.rs

@@ -169,12 +169,18 @@ pub async fn make(
 
     let (slot, recvr) = Slot::new("receive_copy_clicked");
     node.register("click", slot).unwrap();
+    let sg_root = app.sg_root.clone();
     let receive_address_text2 = receive_address_text.clone();
     let listen_click = app.ex.spawn(async move {
         while let Ok(_) = recvr.recv().await {
             let addr = receive_address_text2.get();
             info!(target: "app", "Copy receive address: {addr}");
             clipboard::set(&addr);
+
+            // Show tooltip
+            if let Some(tooltip) = sg_root.lookup_node("/window/content/wallet/receive_layer/receive_copy_tooltip") {
+                let _ = tooltip.call_method("show", vec![]).await;
+            }
         }
     });
     app.tasks.lock().unwrap().push(listen_click);
@@ -182,6 +188,25 @@ pub async fn make(
     let node = node.setup(|me| Button::new(me, app.renderer.clone())).await;
     receive_layer.link(node);
 
+    // Create tooltip
+    let (tooltip, tooltip_width) = create_tooltip(
+        "receive_copy_tooltip",
+        app,
+        &receive_layer,
+        "copied",
+        atom,
+        window_scale.clone(),
+        i18n_fish,
+    ).await;
+
+    // Position tooltip
+    let tooltip_y_expr = cc.compile(format!("{y} + PADDING_Y * 3 + addr_height + 1")).unwrap();
+    let tooltip_x_expr = cc.compile(format!("(w - {tooltip_width}) / 2")).unwrap();
+    let tooltip_rect = tooltip.get_property("rect").unwrap();
+    tooltip_rect.set_expr(atom, Role::App, 0, tooltip_x_expr).unwrap();
+    tooltip_rect.set_expr(atom, Role::App, 1, tooltip_y_expr).unwrap();
+    tooltip_rect.add_depend(&addr_h_prop, 0, "addr_height");
+
     let sep = create_separator_expr(app, atom, &receive_layer, "receive_address_separator", &mut cc, &format!("{y} + PADDING_Y * 2 + addr_height + 1")).await;
     let prop = sep.get_property("rect").unwrap();
     prop.add_depend(&addr_h_prop, 0, "addr_height");

+ 147 - 3
bin/app/src/app/schema/wallet/util.rs

@@ -20,17 +20,21 @@ use darkfi::util::parse::encode_base10;
 use darkfi_money_contract::model::TokenId;
 use darkfi_serial::Decodable;
 
+use futures::FutureExt;
+use smol::channel::unbounded;
+
 use crate::{
     app::App,
-    app::node::{create_button, create_text, create_vector_art},
+    app::node::{create_button, create_layer, create_text, create_vector_art},
     app::schema::COLOR_SCHEME,
     expr::{self, Compiler},
-    gfx::Renderer,
+    gfx::{gfxtag, Renderer},
     mesh::{COLOR_TEAL, COLOR_CYAN},
     prop::{PropertyAtomicGuard, PropertyFloat32, Role},
     scene::SceneNodePtr,
     scene::Pimpl,
-    ui::{Button, Text, VectorArt, VectorShape},
+    text,
+    ui::{Button, Layer, Text, VectorArt, VectorShape},
     util::i18n::I18nBabelFish,
 };
 
@@ -504,3 +508,143 @@ pub async fn create_bottom_button_with_states(
 
     (btn, bg_valid, bg_invalid, label_node)
 }
+
+/// Create tooltip component (with auto-hide)
+pub async fn create_tooltip(
+    name: &str,
+    app: &App,
+    parent_layer: &SceneNodePtr,
+    text: &str,
+    atom: &mut PropertyAtomicGuard,
+    window_scale: PropertyFloat32,
+    i18n_fish: &I18nBabelFish,
+) -> (SceneNodePtr, f32) {
+    let text_color = COLOR_CYAN;
+
+    // Get actual text dimensions using make_layout
+    let scale = window_scale.get();
+    let layout = text::make_layout(
+        text,
+        text_color,
+        HINT_FONTSIZE,
+        1.0,
+        scale,
+        None,
+        &[],
+    );
+    let text_width = layout.width();
+    let text_height = HINT_FONTSIZE * 1.2;
+
+    let width = text_width + TOOLTIP_PADDING_X * 2.;
+    let height = text_height + TOOLTIP_PADDING_Y * 2.;
+
+    let mut cc = Compiler::new();
+    cc.add_const_f32("TOOLTIP_WIDTH", width);
+    cc.add_const_f32("TOOLTIP_HEIGHT", height);
+
+    // Create tooltip layer
+    let mut tooltip_layer = create_layer(name);
+    let prop = tooltip_layer.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, width).unwrap();
+    prop.set_f32(atom, Role::App, 3, height).unwrap();
+    tooltip_layer.set_property_bool(atom, Role::App, "is_visible", false).unwrap();
+    tooltip_layer.set_property_u32(atom, Role::App, "z_index", 3).unwrap();
+
+    tooltip_layer.add_method("show", vec![], None).unwrap();
+
+    let tooltip_layer = tooltip_layer.setup(|me| Layer::new(me, app.renderer.clone())).await;
+    parent_layer.link(tooltip_layer.clone());
+
+    // Create box
+    let node = create_vector_art("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();
+    let code = cc.compile("TOOLTIP_WIDTH").unwrap();
+    prop.set_expr(atom, Role::App, 2, code).unwrap();
+    let code = cc.compile("TOOLTIP_HEIGHT").unwrap();
+    prop.set_expr(atom, Role::App, 3, code).unwrap();
+
+    let mut shape = VectorShape::new();
+    shape.add_outline(
+        expr::const_f32(0.),
+        expr::const_f32(0.),
+        expr::const_f32(width),
+        expr::const_f32(height),
+        1.,
+        text_color,
+    );
+    let node = node.setup(|me| VectorArt::new(me, shape, app.renderer.clone())).await;
+    tooltip_layer.link(node);
+
+    // Create text
+    let node = create_text("text");
+    node.set_property_str(atom, Role::App, "text", text).unwrap();
+    let prop = node.get_property("rect").unwrap();
+    prop.set_f32(atom, Role::App, 0, TOOLTIP_PADDING_X).unwrap();
+    prop.set_f32(atom, Role::App, 1, TOOLTIP_PADDING_Y).unwrap();
+    prop.set_f32(atom, Role::App, 2, text_width).unwrap();
+    prop.set_f32(atom, Role::App, 3, text_height).unwrap();
+    node.set_property_f32(atom, Role::App, "font_size", HINT_FONTSIZE).unwrap();
+
+    let prop = node.get_property("text_color").unwrap();
+    prop.set_f32(atom, Role::App, 0, text_color[0]).unwrap();
+    prop.set_f32(atom, Role::App, 1, text_color[1]).unwrap();
+    prop.set_f32(atom, Role::App, 2, text_color[2]).unwrap();
+    prop.set_f32(atom, Role::App, 3, text_color[3]).unwrap();
+    node.set_property_u32(atom, Role::App, "z_index", 3).unwrap();
+    let node = node.setup(|me| Text::new(me, window_scale.clone(), app.renderer.clone(), i18n_fish.clone())).await;
+    tooltip_layer.link(node);
+
+    // Subscribe to show method for auto-hide behavior
+    let show_method_sub = tooltip_layer.subscribe_method_call("show").unwrap();
+    let tooltip_clone = tooltip_layer.clone();
+    let renderer2 = app.renderer.clone();
+    let (reset_sender, reset_receiver) = unbounded::<()>();
+
+    app.tasks.lock().unwrap().push(app.ex.spawn(async move {
+        loop {
+            // Wait for show() call
+            let _ = show_method_sub.receive().await;
+
+            // Show the tooltip
+            let atom = &mut renderer2.make_guard(gfxtag!("tooltip show"));
+            tooltip_clone.set_property_bool(atom, Role::App, "is_visible", true).unwrap();
+
+            // Send reset signal to hide timer
+            let _ = reset_sender.send(()).await;
+        }
+    }));
+
+    // Hide timer task
+    let tooltip2 = tooltip_layer.clone();
+    let renderer2 = app.renderer.clone();
+    app.tasks.lock().unwrap().push(app.ex.spawn(async move {
+        loop {
+            // Wait for show signal
+            let _ = reset_receiver.recv().await;
+
+            // Sleep 2 seconds, but reset if we get another signal
+            loop {
+                futures::select! {
+                    _ = darkfi::system::sleep(2).fuse() => {
+                        // Timeout completed, hide and break
+                        break;
+                    }
+                    _ = reset_receiver.recv().fuse() => {
+                        // Got another show signal, restart timer
+                        continue;
+                    }
+                }
+            }
+
+            // Hide the tooltip
+            let atom = &mut renderer2.make_guard(gfxtag!("tooltip hide"));
+            tooltip2.set_property_bool(atom, Role::App, "is_visible", false).unwrap();
+        }
+    }));
+
+    (tooltip_layer, width)
+}