Files
grim/examples/avatar_ring.rs
T
2ro c701f0f480 Floonet scoped Nym exit + NIP-44 v3 + wallet polish
Money path:
- Scoped, unbonded Nym exit for the money-path relay: the wallet dials a
  relay operator's co-located exit over a MixnetStream (src/nym/streamexit.rs)
  which pipes to its one relay; hostname-validated TLS end to end, no public
  DNS. Anchor + fallback (never pin-only): any exit failure degrades to the
  smolmix tunnel. relay.goblin.st's exit address is pinned in the relay pool
  (src/nostr/pool.rs) and the maintainer gist so it bootstraps offline.
- STREAM_SETTLE bridges the open-before-accept gap so the first TLS byte is
  not dropped into a stalled handshake.
- Verified end to end: two wallets complete a real gift-wrapped Grin payment
  through relay.goblin.st over the exit, finalized + posted on mainnet
  (src/wallet/e2e.rs, ignored live test).

Encryption:
- Adopt NIP-44 v3 for the NIP-17 gift-wrap path (G4): src/nostr/wrapv3.rs,
  nip44 path dep; v3<->v3 and v3->v2 interop.

Also: mix-DNS (src/nym/dns.rs), full localization pass, GUI polish,
avatar-ring example, Android icon/script updates, GRIM deviation notes,
xrelay + connect-timing tests.
2026-07-02 04:17:59 -04:00

129 lines
3.8 KiB
Rust

//! G1 sizing-checkpoint harness: renders the REAL `avatar_tex` (custom-image
//! avatar + username conic ring) and `gradient_avatar` across every size the
//! app uses, so the ring thickness/inset can be dialed in by eye.
//! Run: `cargo run --example avatar_ring` (screenshots taken externally).
use eframe::egui;
use grim::gui::views::goblin::widgets as w;
const SIZES: [f32; 6] = [28.0, 40.0, 48.0, 56.0, 72.0, 96.0];
const NAMES: [&str; 3] = ["alice", "bob", "carmen"];
struct App {
tex: Vec<egui::TextureHandle>,
}
/// A synthetic "profile photo": diagonal two-tone blend with a light disc, so
/// the ring is judged against something photo-like rather than a flat fill.
fn photo(ctx: &egui::Context, name: &str, a: [u8; 3], b: [u8; 3]) -> egui::TextureHandle {
const N: usize = 128;
let mut px = Vec::with_capacity(N * N);
for y in 0..N {
for x in 0..N {
let t = (x + y) as f32 / (2 * N) as f32;
let mut r = a[0] as f32 * (1.0 - t) + b[0] as f32 * t;
let mut g = a[1] as f32 * (1.0 - t) + b[1] as f32 * t;
let mut bl = a[2] as f32 * (1.0 - t) + b[2] as f32 * t;
let dx = x as f32 - 44.0;
let dy = y as f32 - 40.0;
if (dx * dx + dy * dy).sqrt() < 26.0 {
r = (r + 90.0).min(255.0);
g = (g + 90.0).min(255.0);
bl = (bl + 90.0).min(255.0);
}
px.push(egui::Color32::from_rgb(r as u8, g as u8, bl as u8));
}
}
let img = egui::ColorImage {
size: [N, N],
source_size: egui::Vec2::splat(N as f32),
pixels: px,
};
ctx.load_texture(name.to_string(), img, Default::default())
}
impl App {
fn new(cc: &eframe::CreationContext) -> Self {
egui_extras::install_image_loaders(&cc.egui_ctx);
let tex = vec![
photo(&cc.egui_ctx, "alice", [180, 120, 90], [90, 60, 120]),
photo(&cc.egui_ctx, "bob", [70, 110, 160], [40, 160, 120]),
photo(&cc.egui_ctx, "carmen", [160, 70, 90], [220, 170, 80]),
];
Self { tex }
}
}
impl eframe::App for App {
fn update(&mut self, ctx: &egui::Context, _f: &mut eframe::Frame) {
egui::CentralPanel::default()
.frame(egui::Frame::default().fill(egui::Color32::from_rgb(0xFA, 0xFA, 0xF7)))
.show(ctx, |ui| {
ui.add_space(10.0);
ui.heading(
"G1 avatar ring — sizing sheet (thickness = max(1, size*0.06), gap = max(1, size*0.03))",
);
ui.add_space(12.0);
for (i, name) in NAMES.iter().enumerate() {
ui.horizontal(|ui| {
ui.add_space(12.0);
ui.label(format!("{name:>7}"));
for size in SIZES {
ui.add_space(14.0);
w::avatar_tex(ui, &self.tex[i], name, size);
}
});
ui.add_space(14.0);
}
ui.separator();
ui.label("anonymous npub (grinmark gradient, ring-less):");
ui.add_space(8.0);
ui.horizontal(|ui| {
ui.add_space(12.0);
ui.label(" ");
for (i, size) in SIZES.iter().enumerate() {
ui.add_space(14.0);
w::gradient_avatar(ui, &format!("{i}deadbeef{i}"), *size);
}
});
ui.add_space(14.0);
ui.label("named account (SAME gradient, unchanged) + username ring:");
ui.add_space(8.0);
for name in NAMES {
ui.horizontal(|ui| {
ui.add_space(12.0);
ui.label(format!("{name:>7}"));
for size in SIZES {
ui.add_space(14.0);
w::gradient_avatar_ringed(ui, "deadbeefcafe", name, size);
}
});
ui.add_space(6.0);
}
ui.add_space(10.0);
ui.horizontal(|ui| {
ui.add_space(12.0);
ui.label("sizes: ");
for size in SIZES {
ui.add_space(14.0);
ui.allocate_ui(egui::Vec2::new(size, 16.0), |ui| {
ui.centered_and_justified(|ui| ui.small(format!("{size}")));
});
}
});
});
}
}
fn main() -> eframe::Result {
let opts = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default().with_inner_size([900.0, 640.0]),
..Default::default()
};
eframe::run_native(
"avatar-ring",
opts,
Box::new(|cc| Ok(Box::new(App::new(cc)))),
)
}