use crate::core::{Color, Font, Point, Rect};
use crate::event::{Event, EventHandler};
use crate::render::RenderContext;
use crate::widget::{BaseWidget, Draw, Widget, WidgetKind};
pub struct Avatar {
base: BaseWidget,
text: String,
bg_color: Color,
square: bool,
size: u32,
}
impl Avatar {
pub fn new(geometry: Rect) -> Self {
let sz = if geometry.width > 0 && geometry.height > 0 {
geometry.width.min(geometry.height)
} else {
40
};
Self {
base: BaseWidget::new(
WidgetKind::Avatar,
Rect::new(geometry.x, geometry.y, sz, sz),
"Avatar",
),
text: String::new(),
bg_color: Color::PRIMARY,
square: false,
size: sz,
}
}
pub fn set_text(&mut self, text: &str) {
self.text = text.to_string();
self.base.request_redraw();
}
pub fn text(&self) -> &str {
&self.text
}
pub fn set_square(&mut self, square: bool) {
self.square = square;
self.base.request_redraw();
}
pub fn is_square(&self) -> bool {
self.square
}
pub fn set_bg_color(&mut self, color: Color) {
self.bg_color = color;
self.base.request_redraw();
}
pub fn bg_color(&self) -> Color {
self.bg_color
}
pub fn set_size(&mut self, size: u32) {
self.size = size;
let rect = self.geometry();
self.set_geometry(Rect::new(rect.x, rect.y, size, size));
self.base.request_redraw();
}
pub fn size(&self) -> u32 {
self.size
}
}
impl Widget for Avatar {
fn base(&self) -> &BaseWidget {
&self.base
}
fn base_mut(&mut self) -> &mut BaseWidget {
&mut self.base
}
}
impl Draw for Avatar {
fn draw(&mut self, context: &mut RenderContext) {
let rect = self.geometry();
let size = rect.width.min(rect.height);
let center = Point::new(rect.x + (size as i32) / 2, rect.y + (size as i32) / 2);
if self.square {
let corner_radius = size / 4;
context.fill_rounded_rect(rect, corner_radius, self.bg_color);
} else {
context.fill_circle(center, size / 2, self.bg_color);
}
if !self.text.is_empty() {
let font_size = (size as f32 * 0.45).max(8.0);
let font = Font::bold("Arial", font_size);
let metrics = context.measure_text(&self.text, &font);
let text_width = metrics.width as i32;
let text_height = metrics.ascent as i32 + metrics.descent as i32;
let text_x = rect.x + (rect.width as i32 - text_width) / 2;
let text_y = rect.y + (rect.height as i32 - text_height) / 2 + metrics.ascent as i32;
context.draw_text(
Point::new(text_x.max(rect.x), text_y.max(rect.y)),
&self.text,
&font,
Color::WHITE,
);
}
}
}
impl EventHandler for Avatar {
fn handle_event(&mut self, event: &Event) {
self.base.handle_event(event);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::Point;
use crate::widget::WidgetKind;
#[test]
fn avatar_default_state() {
let avatar = Avatar::new(Rect::new(0, 0, 40, 40));
assert_eq!(avatar.text(), "");
assert!(!avatar.is_square());
assert_eq!(avatar.bg_color(), Color::PRIMARY);
assert_eq!(avatar.size(), 40);
assert_eq!(avatar.kind(), WidgetKind::Avatar);
}
#[test]
fn avatar_default_size_when_zero_geometry() {
let avatar = Avatar::new(Rect::new(10, 20, 0, 0));
assert_eq!(avatar.size(), 40);
assert_eq!(avatar.geometry().width, 40);
assert_eq!(avatar.geometry().height, 40);
}
#[test]
fn avatar_set_text() {
let mut avatar = Avatar::new(Rect::new(0, 0, 40, 40));
avatar.set_text("JD");
assert_eq!(avatar.text(), "JD");
}
#[test]
fn avatar_set_text_clears() {
let mut avatar = Avatar::new(Rect::new(0, 0, 40, 40));
avatar.set_text("AB");
avatar.set_text("");
assert_eq!(avatar.text(), "");
}
#[test]
fn avatar_set_bg_color() {
let mut avatar = Avatar::new(Rect::new(0, 0, 40, 40));
let custom = Color::rgb(255, 0, 0);
avatar.set_bg_color(custom);
assert_eq!(avatar.bg_color(), custom);
}
#[test]
fn avatar_set_square() {
let mut avatar = Avatar::new(Rect::new(0, 0, 40, 40));
assert!(!avatar.is_square());
avatar.set_square(true);
assert!(avatar.is_square());
avatar.set_square(false);
assert!(!avatar.is_square());
}
#[test]
fn avatar_set_size() {
let mut avatar = Avatar::new(Rect::new(0, 0, 40, 40));
avatar.set_size(64);
assert_eq!(avatar.size(), 64);
assert_eq!(avatar.geometry().width, 64);
assert_eq!(avatar.geometry().height, 64);
}
#[test]
fn avatar_set_size_preserves_position() {
let mut avatar = Avatar::new(Rect::new(10, 20, 40, 40));
avatar.set_size(56);
assert_eq!(avatar.geometry().x, 10);
assert_eq!(avatar.geometry().y, 20);
assert_eq!(avatar.geometry().width, 56);
assert_eq!(avatar.geometry().height, 56);
}
#[test]
fn avatar_widget_trait_geometry() {
let mut avatar = Avatar::new(Rect::new(5, 10, 48, 48));
assert_eq!(avatar.geometry(), Rect::new(5, 10, 48, 48));
avatar.set_geometry(Rect::new(0, 0, 64, 64));
assert_eq!(avatar.geometry(), Rect::new(0, 0, 64, 64));
}
#[test]
fn avatar_widget_trait_kind() {
let avatar = Avatar::new(Rect::new(0, 0, 40, 40));
assert_eq!(avatar.kind(), WidgetKind::Avatar);
}
#[test]
fn avatar_handle_event_no_panic() {
let mut avatar = Avatar::new(Rect::new(0, 0, 40, 40));
avatar.handle_event(&Event::MousePress { pos: Point::new(10, 10), button: 1 });
avatar.handle_event(&Event::MouseRelease { pos: Point::new(10, 10), button: 1 });
avatar.handle_event(&Event::MouseMove { pos: Point::new(20, 20) });
avatar.handle_event(&Event::KeyPress { key: 0x41, modifiers: 0 });
avatar.handle_event(&Event::KeyRelease { key: 0x41, modifiers: 0 });
}
#[test]
fn avatar_disabled_blocks_events() {
let mut avatar = Avatar::new(Rect::new(0, 0, 40, 40));
avatar.set_enabled(false);
avatar.handle_event(&Event::MousePress { pos: Point::new(10, 10), button: 1 });
avatar.handle_event(&Event::MouseRelease { pos: Point::new(10, 10), button: 1 });
}
#[test]
fn avatar_svg_output() {
let mut avatar = Avatar::new(Rect::new(0, 0, 40, 40));
avatar.set_text("MW");
let svg = crate::widget::svg::render_to_svg(&mut avatar);
assert!(svg.starts_with("<svg"), "SVG output should start with <svg, got: {svg:.80}");
}
#[test]
fn avatar_square_svg_output() {
let mut avatar = Avatar::new(Rect::new(0, 0, 48, 48));
avatar.set_text("AB");
avatar.set_square(true);
let svg = crate::widget::svg::render_to_svg(&mut avatar);
assert!(
svg.starts_with("<svg"),
"Square SVG output should start with <svg, got: {svg:.80}"
);
}
#[test]
fn avatar_different_sizes() {
let small = Avatar::new(Rect::new(0, 0, 24, 24));
assert_eq!(small.size(), 24);
let large = Avatar::new(Rect::new(0, 0, 96, 96));
assert_eq!(large.size(), 96);
}
#[test]
fn avatar_size_min_of_width_and_height() {
let avatar = Avatar::new(Rect::new(0, 0, 60, 40));
assert_eq!(avatar.size(), 40);
assert_eq!(avatar.geometry().width, 40);
assert_eq!(avatar.geometry().height, 40);
}
}