1use crate::avatar::{Eye, Face, Shape};
6use gpui::{
7 AnyElement, Bounds, Hsla, IntoElement, Pixels, Styled, Window, canvas, fill, point, px, size,
8};
9use theme::Theme;
10
11pub const GRID: usize = 8;
13
14const SQUEEZE: f32 = 0.92;
17
18fn cells(shape: &Shape, eyes: &[Eye; 2], lid: f32) -> [u8; GRID] {
20 let span = shape
23 .outline()
24 .iter()
25 .fold(0.0f32, |far, (x, y)| far.max(x.hypot(*y)));
26
27 let mut rows = [0u8; GRID];
28 for (y, row) in rows.iter_mut().enumerate() {
29 let uy = unit(y, span);
30 for x in 0..GRID {
31 let ux = unit(x, span);
32 if ux.hypot(uy) <= shape.reach(ux, uy) {
36 *row |= 1 << x;
37 }
38 }
39 }
40 for eye in eyes {
41 punch(&mut rows, eye, lid, span);
42 }
43 rows
44}
45
46fn unit(i: usize, span: f32) -> f32 {
48 ((i as f32 + 0.5) / GRID as f32 * 2.0 - 1.0) * span
49}
50
51fn punch(rows: &mut [u8; GRID], eye: &Eye, lid: f32, span: f32) {
52 if lid >= 1.0 {
53 return;
54 }
55 let ry = (eye.ry * (1.0 - lid * SQUEEZE)).max(f32::EPSILON);
56 let (sin, cos) = eye.rot.to_radians().sin_cos();
57 let mut hit = false;
58 for (y, row) in rows.iter_mut().enumerate() {
59 for x in 0..GRID {
60 let (dx, dy) = (unit(x, span) - eye.cx, unit(y, span) - eye.cy);
61 let (a, b) = (dx * cos + dy * sin, dy * cos - dx * sin);
62 if (a / eye.rx).abs().powf(eye.n) + (b / ry).abs().powf(eye.n) <= 1.0 {
63 *row &= !(1 << x);
64 hit = true;
65 }
66 }
67 }
68 if hit || lid >= 0.5 {
69 return;
70 }
71 for step in 0..=GRID {
75 let k = 1.0 - step as f32 / GRID as f32;
76 let (x, y) = (cell(eye.cx * k, span), cell(eye.cy * k, span));
77 if rows[y] >> x & 1 == 1 {
78 rows[y] &= !(1 << x);
79 return;
80 }
81 }
82}
83
84fn cell(u: f32, span: f32) -> usize {
86 (((u / span + 1.0) / 2.0 * GRID as f32) as usize).min(GRID - 1)
87}
88
89pub fn mascot(face: &Face, t: f32) -> AnyElement {
92 let rows = cells(
96 &face.shape,
97 &face.eyes.place(&face.shape, 0.0),
98 face.motion.beat(t).lid,
99 );
100 let tint = face.color;
101 canvas(
102 move |_: Bounds<Pixels>, _, _| (),
103 move |bounds, (), window, cx| {
104 let color = tint.unwrap_or(Theme::of(cx).accent);
105 paint(window, bounds, &rows, color);
106 },
107 )
108 .size_full()
109 .into_any_element()
110}
111
112fn paint(window: &mut Window, bounds: Bounds<Pixels>, rows: &[u8; GRID], color: Hsla) {
113 let scale = window.scale_factor();
114 let snap = |v: f32| (v * scale).round() / scale;
115 let box_side = bounds.size.width.min(bounds.size.height).to_f64() as f32;
116 let side = ((box_side * scale / GRID as f32).floor() / scale).max(1.0 / scale);
118 let sprite = side * GRID as f32;
119 let left = snap(bounds.center().x.to_f64() as f32 - sprite / 2.0);
120 let top = snap(bounds.center().y.to_f64() as f32 - sprite / 2.0);
121
122 for (y, row) in rows.iter().enumerate() {
123 let mut x = 0;
124 while x < GRID {
125 if row >> x & 1 == 0 {
126 x += 1;
127 continue;
128 }
129 let mut run = 1;
131 while x + run < GRID && row >> (x + run) & 1 == 1 {
132 run += 1;
133 }
134 window.paint_quad(fill(
135 Bounds::new(
136 point(px(left + side * x as f32), px(top + side * y as f32)),
137 size(px(side * run as f32), px(side)),
138 ),
139 color,
140 ));
141 x += run;
142 }
143 }
144}