use std::fmt::Display; use gpui::{ App, ElementId, InteractiveElement as _, IntoElement, ParentElement as _, RenderOnce, Role, SharedString, StatefulInteractiveElement as _, StyleRefinement, Styled as _, Window, div, }; use gpui_native_theme::{ActiveTheme as _, Oklch, Size, ThemeVariant}; use crate::common::{Sizable, StyledExt, h_flex}; /// Number of rows and columns in the pixel grid. const GRID_SIZE: usize = 8; /// Empty cells kept between the pattern and the avatar edge. const MARGIN: usize = 1; /// Probability that a cell in the left half of the pattern area is filled. const FILL_PROBABILITY: f32 = 0.42; /// Probability that a filled cell uses the shade instead of the main color. const SHADE_PROBABILITY: f32 = 0.25; /// Minimum number of filled left-half cells. const MIN_FILLED: usize = 5; /// An avatar for a user or other entity. #[derive(IntoElement)] pub struct Avatar { id: SharedString, name: Option, picture: Option, size: Size, fully_rounded: bool, style: StyleRefinement, } impl Avatar { /// Create an avatar for `id`, a public key or any other stable string. /// /// The id seeds the pixel fallback shown when no picture is set. pub fn new(id: T) -> Self where T: Display, { Self { id: id.to_string().into(), name: None, picture: None, size: Size::default(), fully_rounded: false, style: StyleRefinement::default(), } } /// The display name of the avatar's subject, if known. pub fn name(mut self, name: N) -> Self where N: Into, { self.name = Some(name.into()); self } /// The user's picture URL. pub fn picture

(mut self, picture: Option

) -> Self where P: Into, { self.picture = picture.map(Into::into); self } /// Renders the avatar circular instead of the control radius. pub fn fully_rounded(mut self, fully_rounded: bool) -> Self { self.fully_rounded = fully_rounded; self } } impl Sizable for Avatar { fn apply_size(&mut self, size: Size) { self.size = size; } } impl gpui::Styled for Avatar { fn style(&mut self) -> &mut gpui::StyleRefinement { &mut self.style } } impl RenderOnce for Avatar { fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement { let theme = cx.theme(); let radius = if self.fully_rounded { theme.radius.full } else { theme.radius.control }; let side = theme.sizes.resolve(self.size).avatar_size; let base = h_flex() .id(ElementId::from(self.id.clone())) .flex_none() .justify_center() .overflow_hidden() .size(side) .bg(theme.colors.secondary) .rounded(radius) .role(Role::Image) .aria_label(self.name.clone().unwrap_or_else(|| self.id.clone())) .refine_style(&self.style); match self.picture { Some(url) => base.child(gpui::img(url).size_full().rounded(radius)), None => base.child(PixelAvatar { seed: fnv1a(self.id.as_bytes()), side, radius, style: self.style, }), } .into_any_element() } } /// A deterministic, offline pixel-art avatar. #[derive(IntoElement)] struct PixelAvatar { seed: u64, side: gpui::Pixels, radius: gpui::Pixels, style: StyleRefinement, } impl gpui::Styled for PixelAvatar { fn style(&mut self) -> &mut StyleRefinement { &mut self.style } } impl RenderOnce for PixelAvatar { fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement { let theme = cx.theme(); let pattern = pattern(self.seed); let mut cells = Vec::new(); let hue = (self.seed as f32 / u64::MAX as f32) * 360.0; let accent = Oklch::from_hsla(theme.colors.accent); let main = Oklch { h: hue, ..accent }.to_hsla(); let shade = if theme.variant == ThemeVariant::Dark { Oklch { l: (accent.l * 1.6).min(0.95), ..Oklch::from_hsla(main) } .to_hsla() } else { Oklch { l: (accent.l * 0.45).max(0.18), ..Oklch::from_hsla(main) } .to_hsla() }; for row in 0..GRID_SIZE { for col in 0..GRID_SIZE { let value = pattern[row * GRID_SIZE + col]; if value != 0 { let color = if value == 2 { shade } else { main }; cells.push( div() .row_start(row as i16 + 1) .row_end(row as i16 + 2) .col_start(col as i16 + 1) .col_end(col as i16 + 2) .bg(color), ); } } } div() .grid() .grid_cols(GRID_SIZE as u16) .grid_rows(GRID_SIZE as u16) .size(self.side) .flex_none() .rounded(self.radius) .overflow_hidden() .bg(gpui::Hsla { a: 0.16, ..main }) .children(cells) .refine_style(&self.style) } } fn pattern(seed: u64) -> [u8; GRID_SIZE * GRID_SIZE] { let mut rng = PixelRng::new(seed); let mut pattern = [0u8; GRID_SIZE * GRID_SIZE]; let mut filled = 0usize; let art_rows = GRID_SIZE - 2 * MARGIN; let art_columns = GRID_SIZE / 2 - MARGIN; for row in MARGIN..GRID_SIZE - MARGIN { for col in MARGIN..GRID_SIZE / 2 { if rng.chance(FILL_PROBABILITY) { let shade = rng.chance(SHADE_PROBABILITY); set_cell(&mut pattern, row, col, if shade { 2 } else { 1 }); filled += 1; } } } if filled < MIN_FILLED { let total = art_rows * art_columns; let start = (rng.next() % total as u64) as usize; for offset in 0..total { if filled >= MIN_FILLED { break; } let ix = (start + offset) % total; let row = MARGIN + ix / art_columns; let col = MARGIN + ix % art_columns; if pattern[row * GRID_SIZE + col] == 0 { set_cell(&mut pattern, row, col, 1); filled += 1; } } } pattern } /// Fill `cell (row, col)` and its horizontal mirror. fn set_cell(pattern: &mut [u8; GRID_SIZE * GRID_SIZE], row: usize, col: usize, value: u8) { pattern[row * GRID_SIZE + col] = value; pattern[row * GRID_SIZE + (GRID_SIZE - 1 - col)] = value; } /// FNV-1a 64-bit hash, stable across platforms and runs. fn fnv1a(bytes: &[u8]) -> u64 { let mut hash = 0xcbf2_9ce4_8422_2325u64; for &byte in bytes { hash ^= byte as u64; hash = hash.wrapping_mul(0x0000_0100_0000_01b3); } hash } /// Tiny xorshift64* PRNG for deriving the pattern from the seed. struct PixelRng(u64); impl PixelRng { fn new(seed: u64) -> Self { Self(seed.max(1)) } fn next(&mut self) -> u64 { let mut x = self.0; x ^= x >> 12; x ^= x << 25; x ^= x >> 27; self.0 = x; x.wrapping_mul(0x2545_f491_4f6c_dd1d) } fn chance(&mut self, probability: f32) -> bool { self.next() as f32 / (u64::MAX as f32) < probability } } #[cfg(test)] mod tests { use super::*; #[test] fn the_pattern_is_mirror_symmetric() { for seed in [0u64, 1, 42, u64::MAX] { let pattern = pattern(seed); for row in 0..GRID_SIZE { for col in 0..GRID_SIZE / 2 { assert_eq!( pattern[row * GRID_SIZE + col], pattern[row * GRID_SIZE + (GRID_SIZE - 1 - col)], "seed {seed} row {row} col {col}" ); } } } } #[test] fn the_pattern_keeps_the_outer_ring_empty() { for seed in [0u64, 1, 42, u64::MAX] { let pattern = pattern(seed); for col in 0..GRID_SIZE { assert_eq!(pattern[col], 0, "top row, seed {seed}"); assert_eq!(pattern[(GRID_SIZE - 1) * GRID_SIZE + col], 0, "bottom row"); } for row in 0..GRID_SIZE { assert_eq!(pattern[row * GRID_SIZE], 0, "left column, seed {seed}"); assert_eq!(pattern[row * GRID_SIZE + GRID_SIZE - 1], 0, "right column"); } } } #[test] fn the_pattern_is_deterministic_and_fills_the_minimum() { for seed in [0u64, 7, 42, u64::MAX] { let first = pattern(seed); assert_eq!(first, pattern(seed), "seed {seed} must be stable"); // Mirrored cells count twice, so both halves carry the minimum. let filled = first.iter().filter(|cell| **cell != 0).count(); assert!(filled >= MIN_FILLED * 2, "seed {seed} filled {filled}"); } } #[test] fn fnv1a_is_stable_across_calls() { assert_eq!(fnv1a(b"reya"), fnv1a(b"reya")); assert_ne!(fnv1a(b"reya"), fnv1a(b"signed")); } }