goblin: enable the activity-row identity badge (approved cue)
Turn the per-identity activity cue on (SHOW_ROW_IDENTITY_CUE = true) and render it as the owner-approved corner badge. The row's main avatar stays the COUNTERPARTY; overlaid on its bottom-right corner is a small ~14px badge filled with the USER's OWN identity gradient for that transaction (from ActivityItem.owner_pubkey via the exact identicon gradient math, falling back to the primary identity for pre-feature rows), so a glance clusters which of your identities each payment used. Shown only when the wallet holds more than one identity, never on system (mining) rows. The badge is a true smooth rotated linear gradient (an egui mesh reproducing the avatar's SVG gradient, not a flat chip) at 0.9 fill opacity, with a 1px theme-aware hairline ring (near-black on light, near-white on dark) so it stays legible on both pure-white and pure-black and against the avatar it sits on. identity_dot is now the single gradient renderer, shared by the row badge and the transaction-detail legend.
This commit is contained in:
@@ -96,6 +96,16 @@ pub fn gradient_rgb8(id: &str) -> ((u8, u8, u8), (u8, u8, u8)) {
|
||||
(c1, c2)
|
||||
}
|
||||
|
||||
/// The identity's gradient stops PLUS the rotation angle (degrees), so a small
|
||||
/// egui-drawn badge can reproduce the same rotated linear gradient the SVG
|
||||
/// avatar uses (`gradient_avatar_svg`: `x1=0 y1=0 x2=1 y2=1` then
|
||||
/// `rotate(angle, 0.5, 0.5)`). `id` may be an npub or raw hex.
|
||||
pub fn gradient_stops(id: &str) -> ((u8, u8, u8), (u8, u8, u8), f32) {
|
||||
let hex = to_hex_seed(id);
|
||||
let (c1, c2, angle) = gradient_rgb(&hex);
|
||||
(c1, c2, angle as f32)
|
||||
}
|
||||
|
||||
/// The gradient avatar as a standalone SVG document, seeded by `hex` (lowercase
|
||||
/// hex pubkey). `id_suffix` makes the gradient element id unique when several
|
||||
/// are inlined into ONE html document; for a standalone document (how egui
|
||||
|
||||
+15
-21
@@ -130,15 +130,12 @@ pub struct GoblinWalletView {
|
||||
wipe_confirm: bool,
|
||||
}
|
||||
|
||||
/// Whether a per-identity cue is drawn on activity rows. OFF by design: on an
|
||||
/// activity row the avatar is the COUNTERPARTY, so a dot in an identity gradient
|
||||
/// there is both redundant with that avatar and does not convey which of the
|
||||
/// USER's own identities was involved. A correct in-list cue would need a
|
||||
/// distinct element encoding the user's own active identity, which is a separate
|
||||
/// follow-up with its own design pass. The seam (this const + the plumbing below)
|
||||
/// is kept so that cue can be added in one place; the detail-view identity
|
||||
/// attribution is the shipped "which identity" answer. Leave false.
|
||||
const SHOW_ROW_IDENTITY_CUE: bool = false;
|
||||
/// Whether the per-identity cue is drawn on activity rows (owner-approved). The
|
||||
/// row's main avatar stays the COUNTERPARTY; the cue is a SMALL corner badge on
|
||||
/// that avatar, filled with the USER's OWN identity gradient for the tx (from
|
||||
/// `ActivityItem.owner_pubkey`), so a glance clusters which of your identities
|
||||
/// each payment used. Only shown when the wallet holds more than one identity.
|
||||
const SHOW_ROW_IDENTITY_CUE: bool = true;
|
||||
|
||||
/// Per-frame identity context for the activity rows: whether the wallet holds
|
||||
/// more than one identity (the cue only shows then) and the primary identity's
|
||||
@@ -2132,22 +2129,19 @@ impl GoblinWalletView {
|
||||
item.system,
|
||||
tex.as_ref(),
|
||||
);
|
||||
// Provisional per-identity cue (owner reviews the look before it is final):
|
||||
// only when the wallet holds MORE THAN ONE identity, and never on system
|
||||
// (mining) rows. A single, quiet gradient dot at the row's top-left corner,
|
||||
// seeded by the identity this tx used (its own gradient, matching that
|
||||
// identity's avatar in the switcher). Behind SHOW_ROW_IDENTITY_CUE so it can
|
||||
// be toned down or dropped in one place without touching the rest.
|
||||
// Per-identity cue (owner-approved): only when the wallet holds MORE THAN
|
||||
// ONE identity, and never on system (mining) rows. A small corner badge on
|
||||
// the counterparty avatar, filled with the identity THIS tx used (its own
|
||||
// gradient; falls back to the primary for pre-feature rows). The row avatar
|
||||
// is 40px, flush to the row's left and vertically centred, so its
|
||||
// bottom-right corner is at (left+40, mid+20); the badge overhangs that
|
||||
// corner by ~4px (matching the mock's right:-4/bottom:-4, 14px badge).
|
||||
if SHOW_ROW_IDENTITY_CUE && id_cue.multi && !item.system {
|
||||
let seed = item.owner_pubkey.clone().or_else(|| id_cue.primary.clone());
|
||||
if let Some(seed) = seed {
|
||||
let r = resp.rect;
|
||||
w::identity_dot(
|
||||
ui.painter(),
|
||||
egui::pos2(r.left() + 8.0, r.top() + 7.0),
|
||||
4.0,
|
||||
&seed,
|
||||
);
|
||||
let badge = egui::pos2(r.left() + 37.0, r.center().y + 17.0);
|
||||
w::identity_dot(ui.painter(), badge, 6.0, &seed);
|
||||
}
|
||||
}
|
||||
if resp.clicked() {
|
||||
|
||||
@@ -1101,37 +1101,59 @@ pub fn info_row_dot(ui: &mut Ui, label: &str, value: &str, seed: &str) {
|
||||
ui.add_space(8.0);
|
||||
}
|
||||
|
||||
/// A deliberately QUIET per-identity cue: a small two-tone dot in an identity's
|
||||
/// OWN gradient — the same pubkey-seeded math as its avatar (`identicon`), so the
|
||||
/// dot reads as a legend for that identity and matches everywhere it appears
|
||||
/// (activity row, switcher page, transaction detail). A faint, theme-aware ring
|
||||
/// keeps a PALE gradient legible on a light background and a DARK one on a dark
|
||||
/// background, without ever becoming a bright chip. `seed` is the identity's npub
|
||||
/// or pubkey hex. This is the single shared renderer, so toning it down (or
|
||||
/// removing the row cue) happens in exactly one place.
|
||||
/// A per-identity cue: a small disc filled with an identity's OWN gradient — the
|
||||
/// same pubkey-seeded, rotated two-stop linear gradient its avatar uses
|
||||
/// (`identicon`), so the disc reads as a color legend for that identity and
|
||||
/// matches its avatar everywhere it appears (activity-row corner badge, switcher,
|
||||
/// transaction detail). The fill is a true smooth gradient (an egui mesh, not a
|
||||
/// flat chip) at 0.9 opacity, with a 1px theme-aware hairline ring that keeps it
|
||||
/// legible on both a pure-white and a pure-black background. `seed` is the
|
||||
/// identity's npub or pubkey hex. Single shared renderer, so the look is tuned in
|
||||
/// exactly one place. Matches the owner-approved cue mock precisely.
|
||||
pub fn identity_dot(painter: &egui::Painter, center: egui::Pos2, radius: f32, seed: &str) {
|
||||
let t = theme::tokens();
|
||||
let ((r1, g1, b1), (r2, g2, b2)) = super::identicon::gradient_rgb8(seed);
|
||||
// Faint fill in BOTH themes; the ring below carries legibility, not the fill.
|
||||
let fill_a = if t.dark_base { 165 } else { 185 };
|
||||
let c1 = Color32::from_rgba_unmultiplied(r1, g1, b1, fill_a);
|
||||
let c2 = Color32::from_rgba_unmultiplied(r2, g2, b2, fill_a);
|
||||
// Base disc in the first stop, then the second stop as a half-disc so the dot
|
||||
// is two-tone like the gradient avatar rather than a flat chip.
|
||||
painter.circle_filled(center, radius, c1);
|
||||
let start = std::f32::consts::FRAC_PI_4; // 45 degrees, the gradient axis-ish
|
||||
let mut pts = Vec::with_capacity(14);
|
||||
for i in 0..=12 {
|
||||
let a = start + std::f32::consts::PI * (i as f32 / 12.0);
|
||||
pts.push(center + radius * egui::Vec2::angled(a));
|
||||
let ((r1, g1, b1), (r2, g2, b2), angle) = super::identicon::gradient_stops(seed);
|
||||
// 0.9 fill opacity per the mock.
|
||||
const FILL_A: u8 = 230;
|
||||
let lerp = |a: u8, b: u8, f: f32| (a as f32 + (b as f32 - a as f32) * f).round() as u8;
|
||||
let col = |f: f32| {
|
||||
let f = f.clamp(0.0, 1.0);
|
||||
Color32::from_rgba_unmultiplied(lerp(r1, r2, f), lerp(g1, g2, f), lerp(b1, b2, f), FILL_A)
|
||||
};
|
||||
// Reproduce the SVG's rotated linear gradient (base axis 0,0 -> 1,1 in the
|
||||
// unit bounding box, then rotate(angle) about the centre). For a rim point in
|
||||
// direction (cv, sv) on the unit circle, its bounding-box offset from centre
|
||||
// is (0.5·cv, 0.5·sv); inverse-rotating by the gradient angle and projecting
|
||||
// onto the (1,1) axis gives the stop parameter t.
|
||||
let (sin_a, cos_a) = angle.to_radians().sin_cos();
|
||||
let t_at = |cv: f32, sv: f32| -> f32 {
|
||||
let dx = 0.5 * cv;
|
||||
let dy = 0.5 * sv;
|
||||
let rx = cos_a * dx + sin_a * dy;
|
||||
let ry = -sin_a * dx + cos_a * dy;
|
||||
(rx + ry) * 0.5 + 0.5
|
||||
};
|
||||
// Gradient-filled disc as a triangle fan; egui interpolates the per-vertex
|
||||
// colours, so the fill is a smooth gradient at this size.
|
||||
let n = 28usize;
|
||||
let mut mesh = egui::Mesh::default();
|
||||
mesh.colored_vertex(center, col(t_at(0.0, 0.0)));
|
||||
for i in 0..=n {
|
||||
let a = std::f32::consts::TAU * (i as f32 / n as f32);
|
||||
let (sv, cv) = a.sin_cos();
|
||||
mesh.colored_vertex(center + radius * egui::vec2(cv, sv), col(t_at(cv, sv)));
|
||||
}
|
||||
painter.add(egui::Shape::convex_polygon(pts, c2, Stroke::NONE));
|
||||
// Faint ring: light on dark, dark on light — a low-contrast gradient still
|
||||
// gets an edge on either background.
|
||||
for i in 1..=n as u32 {
|
||||
mesh.add_triangle(0, i, i + 1);
|
||||
}
|
||||
painter.add(egui::Shape::mesh(mesh));
|
||||
// 1px theme-aware hairline ring (matches the mock): near-black on light,
|
||||
// near-white on dark, so the disc has a defined edge on either background and
|
||||
// against the avatar it badges.
|
||||
let ring = if t.dark_base {
|
||||
Color32::from_rgba_unmultiplied(255, 255, 255, 55)
|
||||
Color32::from_rgba_unmultiplied(250, 250, 247, 82)
|
||||
} else {
|
||||
Color32::from_rgba_unmultiplied(0, 0, 0, 45)
|
||||
Color32::from_rgba_unmultiplied(14, 14, 12, 71)
|
||||
};
|
||||
painter.circle_stroke(center, radius, Stroke::new(1.0, ring));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user