Skip to main content

armas_basic/components/
avatar.rs

1//! Avatar Component
2//!
3//! User profile images and initials styled like shadcn/ui Avatar.
4
5use crate::ext::ArmasContextExt;
6use egui::{vec2, Response, Sense, Ui};
7
8// shadcn Avatar default size
9const DEFAULT_SIZE: f32 = 32.0; // size-8 (2rem)
10
11/// Avatar size presets
12#[derive(Debug, Clone, Copy, PartialEq, Default)]
13pub enum AvatarSize {
14    /// Extra small (24px)
15    XSmall,
16    /// Small (32px) - shadcn default
17    #[default]
18    Small,
19    /// Medium (40px)
20    Medium,
21    /// Large (48px)
22    Large,
23    /// Extra large (64px)
24    XLarge,
25    /// Custom size
26    Custom(f32),
27}
28
29impl AvatarSize {
30    const fn to_pixels(self) -> f32 {
31        match self {
32            Self::XSmall => 24.0,
33            Self::Small => 32.0,
34            Self::Medium => 40.0,
35            Self::Large => 48.0,
36            Self::XLarge => 64.0,
37            Self::Custom(size) => size,
38        }
39    }
40}
41
42/// Avatar shape
43#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
44pub enum AvatarShape {
45    /// Circular avatar (shadcn default)
46    #[default]
47    Circle,
48    /// Rounded square
49    Rounded,
50}
51
52/// Avatar component for displaying user images or initials
53///
54/// Styled like shadcn/ui Avatar with a simple fallback showing initials.
55///
56/// # Example
57///
58/// ```rust,no_run
59/// # use egui::Ui;
60/// # fn example(ui: &mut Ui) {
61/// use armas_basic::Avatar;
62///
63/// // Simple avatar with initials
64/// Avatar::new("JD").show(ui);
65///
66/// // Larger avatar
67/// Avatar::new("AM").size(48.0).show(ui);
68/// # }
69/// ```
70pub struct Avatar {
71    text: String,
72    size: f32,
73    shape: AvatarShape,
74}
75
76impl Avatar {
77    /// Create a new avatar with initials or text
78    pub fn new(text: impl Into<String>) -> Self {
79        Self {
80            text: text.into(),
81            size: DEFAULT_SIZE,
82            shape: AvatarShape::Circle,
83        }
84    }
85
86    /// Set the avatar size in pixels
87    #[must_use]
88    pub const fn size(mut self, size: f32) -> Self {
89        self.size = size;
90        self
91    }
92
93    /// Set the avatar size using a preset
94    #[must_use]
95    pub const fn size_preset(mut self, size: AvatarSize) -> Self {
96        self.size = size.to_pixels();
97        self
98    }
99
100    /// Set the avatar shape
101    #[must_use]
102    pub const fn shape(mut self, shape: AvatarShape) -> Self {
103        self.shape = shape;
104        self
105    }
106
107    /// Show the avatar
108    pub fn show(self, ui: &mut Ui) -> Response {
109        let theme = ui.ctx().armas_theme();
110        let (rect, response) = ui.allocate_exact_size(vec2(self.size, self.size), Sense::hover());
111
112        if ui.is_rect_visible(rect) {
113            // shadcn uses rounded-full for circle
114            let rounding = match self.shape {
115                AvatarShape::Circle => self.size / 2.0,
116                AvatarShape::Rounded => 6.0, // rounded-md
117            };
118
119            // Background: bg-muted (shadcn fallback style)
120            ui.painter().rect_filled(rect, rounding, theme.muted());
121
122            // Text (initials) - centered
123            let font_size = self.size * 0.4;
124            let font_id = egui::FontId::proportional(font_size);
125
126            // Get just initials (first 2 chars, uppercase)
127            let initials: String = self
128                .text
129                .split_whitespace()
130                .filter_map(|word| word.chars().next())
131                .take(2)
132                .collect::<String>()
133                .to_uppercase();
134
135            let display_text = if initials.is_empty() {
136                self.text.chars().take(2).collect::<String>().to_uppercase()
137            } else {
138                initials
139            };
140
141            ui.painter().text(
142                rect.center(),
143                egui::Align2::CENTER_CENTER,
144                display_text,
145                font_id,
146                theme.muted_foreground(),
147            );
148        }
149
150        response
151    }
152}