diff --git a/src/gui/views/goblin/data.rs b/src/gui/views/goblin/data.rs index ec2d14d7..8014c349 100644 --- a/src/gui/views/goblin/data.rs +++ b/src/gui/views/goblin/data.rs @@ -236,6 +236,16 @@ pub fn short_npub(hex: &str) -> String { format!("{}…", &hex[..8.min(hex.len())]) } +/// Full bech32 npub (no truncation), for the recipient picker's grey subtitle +/// where showing the complete key is more useful than repeating the truncation. +pub fn full_npub(hex: &str) -> String { + use nostr_sdk::{PublicKey, ToBech32}; + PublicKey::from_hex(hex) + .ok() + .and_then(|pk| pk.to_bech32().ok()) + .unwrap_or_else(|| hex.to_string()) +} + /// Build the activity feed for a wallet, newest first. pub fn activity_items(wallet: &Wallet) -> Vec { let data = match wallet.get_data() { diff --git a/src/gui/views/goblin/send.rs b/src/gui/views/goblin/send.rs index c69b755e..4653dca4 100644 --- a/src/gui/views/goblin/send.rs +++ b/src/gui/views/goblin/send.rs @@ -206,8 +206,8 @@ impl SendFlow { t.bg }, inner_margin: egui::Margin { - left: (View::far_left_inset_margin(ui) + 20.0) as i8, - right: (View::get_right_inset() + 20.0) as i8, + left: (View::far_left_inset_margin(ui) + 12.0) as i8, + right: (View::get_right_inset() + 12.0) as i8, top: (View::get_top_inset() + 12.0) as i8, bottom: (View::get_bottom_inset() + 12.0) as i8, }, @@ -383,7 +383,7 @@ impl SendFlow { if w::activity_row( ui, &name, - &short_npub(&npub), + &data::full_npub(&npub), hue, "", false, @@ -936,16 +936,11 @@ impl SendFlow { } else { format!("You're sending {}", recipient.name) }; - let galley = ui.painter().layout_no_wrap( - label.clone(), - FontId::new(14.0, fonts::regular()), - t.text_dim, - ); - let row_w = 36.0 + 8.0 + galley.size().x; - ui.horizontal(|ui| { - ui.add_space(((ui.available_width() - row_w) / 2.0).max(0.0)); - w::avatar_any(ui, &recipient.name, 36.0, recipient.hue, hero_tex.as_ref()); - ui.add_space(8.0); + // Centered avatar + caption. A long counterparty (a bare npub) wraps + // and stays centered instead of overflowing the card. + ui.vertical_centered(|ui| { + w::avatar_any(ui, &recipient.name, 40.0, recipient.hue, hero_tex.as_ref()); + ui.add_space(6.0); ui.label( RichText::new(label) .font(FontId::new(14.0, fonts::regular())) @@ -1157,7 +1152,7 @@ impl SendFlow { let (rect, _) = ui.allocate_exact_size(Vec2::splat(120.0), Sense::hover()); ui.painter() .circle_filled(rect.center(), 60.0, t.accent_ink); - let img = egui::Image::new(egui::include_image!("../../../../img/goblin-mask-128.png")) + let img = egui::Image::new(egui::include_image!("../../../../img/goblin-logo2.svg")) .tint(t.accent) .fit_to_exact_size(Vec2::splat(72.0)); img.paint_at( diff --git a/src/gui/views/goblin/widgets.rs b/src/gui/views/goblin/widgets.rs index 8cfd3a60..4358dc9a 100644 --- a/src/gui/views/goblin/widgets.rs +++ b/src/gui/views/goblin/widgets.rs @@ -107,15 +107,27 @@ pub fn amount_text_centered_ink( num_ink: Color32, mark_ink: Color32, ) { - let num = - ui.painter() - .layout_no_wrap(value.to_string(), FontId::new(size, fonts::bold()), num_ink); - let mark = ui.painter().layout_no_wrap( - TSU.to_string(), - FontId::new(size * 0.4, fonts::medium()), - mark_ink, - ); - let total = num.size().x + 1.0 + mark.size().x; + let avail = ui.available_width(); + let measure = |ui: &Ui, sz: f32| -> f32 { + let num = + ui.painter() + .layout_no_wrap(value.to_string(), FontId::new(sz, fonts::bold()), num_ink); + let mark = ui.painter().layout_no_wrap( + TSU.to_string(), + FontId::new(sz * 0.4, fonts::medium()), + mark_ink, + ); + num.size().x + 1.0 + mark.size().x + }; + // Shrink to fit: a long balance (e.g. 0.46520721ツ) must not run off the + // edge. Glyph width is ~linear in font size, so scale down to the available + // width with a small margin and a sane floor. + let mut size = size; + let total0 = measure(ui, size); + if total0 > avail && total0 > 1.0 { + size = (size * (avail / total0) * 0.97).clamp(14.0, size); + } + let total = measure(ui, size); ui.horizontal(|ui| { ui.spacing_mut().item_spacing.x = 0.0; ui.add_space(((ui.available_width() - total) / 2.0).max(0.0)); @@ -426,15 +438,23 @@ pub fn activity_row( ui.add_space(12.0); ui.vertical(|ui| { ui.add_space(2.0); - ui.label( - RichText::new(title) - .font(FontId::new(15.0, fonts::semibold())) - .color(t.text), + ui.add( + egui::Label::new( + RichText::new(title) + .font(FontId::new(15.0, fonts::semibold())) + .color(t.text), + ) + .truncate(), ); - ui.label( - RichText::new(subtitle) - .font(FontId::new(13.0, fonts::regular())) - .color(t.text_dim), + // Single-line, truncated: keeps the fixed-height row tidy even when + // the subtitle is a long value (e.g. a full npub in the picker). + ui.add( + egui::Label::new( + RichText::new(subtitle) + .font(FontId::new(13.0, fonts::regular())) + .color(t.text_dim), + ) + .truncate(), ); }); ui.with_layout(Layout::right_to_left(Align::Center), |ui| { @@ -481,10 +501,15 @@ pub fn info_row(ui: &mut Ui, label: &str, value: &str) { .color(t.text_dim), ); ui.with_layout(Layout::right_to_left(Align::Center), |ui| { - ui.label( - RichText::new(value) - .font(FontId::new(15.0, fonts::semibold())) - .color(t.text), + // Truncate so a long value (e.g. "Encrypted nostr DM over Nym") never + // runs past the edge or collides with the label on a narrow screen. + ui.add( + egui::Label::new( + RichText::new(value) + .font(FontId::new(15.0, fonts::semibold())) + .color(t.text), + ) + .truncate(), ); }); }); @@ -677,9 +702,9 @@ pub fn fill_bg(ui: &Ui, color: Color32) { /// would start the row a single line tall, so a `ScrollArea` inside would /// clip everything below the first widget. pub fn centered_column(ui: &mut Ui, width: f32, add: impl FnOnce(&mut Ui) -> R) -> R { - // Always keep a side gutter so content never runs flush to the screen - // edge on phones (where `width` exceeds the available width). - const MIN_SIDE_PAD: f32 = 18.0; + // Keep a small side gutter so content sits close to the screen edges on + // phones (where `width` exceeds the available width) without running flush. + const MIN_SIDE_PAD: f32 = 8.0; let avail = ui.available_width(); let w = width.min(avail - MIN_SIDE_PAD * 2.0).max(0.0); let margin = ((avail - w) / 2.0).max(MIN_SIDE_PAD);