Krolyxon
Commit 7c15b41

Branches

Refs →

budctlcratesbudctl-guisrcicons.rs

302 lines · 9.0 KB · Rust

1//! Icons, drawn as vector paths so they take any theme color and stay sharp at
2//! any scale. Icons are designed on a 24 × 24 grid.
3
4use iced::widget::canvas::{self, Frame, LineCap, LineJoin, Path, Stroke, path::Arc};
5use iced::{Color, Element, Point, Radians, Rectangle, Renderer, Size, Theme, mouse};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum Icon {
9 Equalizer,
10 Gestures,
11 Earbud,
12 Settings,
13 NoiseOff,
14 Anc,
15 Transparency,
16 Game,
17 Spatial,
18 Bass,
19 Wind,
20 Dual,
21 Chevron,
22 Bolt,
23 Find,
24 Info,
25 Chip,
26 Keys,
27 Pencil,
28 Trash,
29 Laptop,
30 Phone,
31}
32
33/// `icon` at `size` pixels, in `color`.
34pub fn icon<'a, Message: 'a>(icon: Icon, size: f32, color: Color) -> Element<'a, Message> {
35 iced::widget::canvas(Glyph { icon, color })
36 .width(size)
37 .height(size)
38 .into()
39}
40
41struct Glyph {
42 icon: Icon,
43 color: Color,
44}
45
46impl<Message> canvas::Program<Message> for Glyph {
47 type State = ();
48
49 fn draw(
50 &self,
51 _state: &(),
52 renderer: &Renderer,
53 _theme: &Theme,
54 bounds: Rectangle,
55 _cursor: mouse::Cursor,
56 ) -> Vec<canvas::Geometry> {
57 let mut frame = Frame::new(renderer, bounds.size());
58 let mut pen = Pen::new(&mut frame, Point::ORIGIN, bounds.width, self.color);
59 draw_icon(&mut pen, self.icon);
60 vec![frame.into_geometry()]
61 }
62}
63
64/// Draws on a 24 × 24 grid placed at `origin`, `size` pixels wide.
65struct Pen<'f> {
66 frame: &'f mut Frame,
67 origin: Point,
68 scale: f32,
69 color: Color,
70 width: f32,
71}
72
73impl<'f> Pen<'f> {
74 fn new(frame: &'f mut Frame, origin: Point, size: f32, color: Color) -> Self {
75 let scale = size / 24.0;
76 Pen {
77 frame,
78 origin,
79 scale,
80 color,
81 width: 1.9 * scale,
82 }
83 }
84
85 fn at(&self, x: f32, y: f32) -> Point {
86 Point::new(self.origin.x + x * self.scale, self.origin.y + y * self.scale)
87 }
88
89 fn stroke(&mut self, path: &Path) {
90 let stroke = Stroke::default()
91 .with_color(self.color)
92 .with_width(self.width)
93 .with_line_cap(LineCap::Round)
94 .with_line_join(LineJoin::Round);
95 self.frame.stroke(path, stroke);
96 }
97
98 fn polyline(&mut self, points: &[(f32, f32)], closed: bool) {
99 let path = Path::new(|b| {
100 for (n, &(x, y)) in points.iter().enumerate() {
101 if n == 0 {
102 b.move_to(self.at(x, y));
103 } else {
104 b.line_to(self.at(x, y));
105 }
106 }
107 if closed {
108 b.close();
109 }
110 });
111 self.stroke(&path);
112 }
113
114 fn line(&mut self, from: (f32, f32), to: (f32, f32)) {
115 self.polyline(&[from, to], false);
116 }
117
118 fn circle(&mut self, x: f32, y: f32, r: f32) {
119 let path = Path::circle(self.at(x, y), r * self.scale);
120 self.stroke(&path);
121 }
122
123 fn dot(&mut self, x: f32, y: f32, r: f32) {
124 let path = Path::circle(self.at(x, y), r * self.scale);
125 self.frame.fill(&path, self.color);
126 }
127
128 /// Degrees, clockwise from three o'clock. `to` may be below `from` to go the other way.
129 fn arc(&mut self, x: f32, y: f32, r: f32, from: f32, to: f32) {
130 let path = Path::new(|b| {
131 b.arc(Arc {
132 center: self.at(x, y),
133 radius: r * self.scale,
134 start_angle: Radians(from.to_radians()),
135 end_angle: Radians(to.to_radians()),
136 });
137 });
138 self.stroke(&path);
139 }
140
141 fn rect(&mut self, x: f32, y: f32, w: f32, h: f32, r: f32) {
142 let path = self.rounded(x, y, w, h, r);
143 self.stroke(&path);
144 }
145
146 fn fill_rect(&mut self, x: f32, y: f32, w: f32, h: f32, r: f32) {
147 let path = self.rounded(x, y, w, h, r);
148 self.frame.fill(&path, self.color);
149 }
150
151 fn rounded(&self, x: f32, y: f32, w: f32, h: f32, r: f32) -> Path {
152 Path::rounded_rectangle(
153 self.at(x, y),
154 Size::new(w * self.scale, h * self.scale),
155 (r * self.scale).into(),
156 )
157 }
158}
159
160fn draw_icon(pen: &mut Pen, icon: Icon) {
161 match icon {
162 Icon::Equalizer => {
163 for (x, knob) in [(6.0, 14.0), (12.0, 8.0), (18.0, 16.0)] {
164 pen.line((x, 3.5), (x, 20.5));
165 pen.line((x - 2.5, knob), (x + 2.5, knob));
166 }
167 }
168 Icon::Gestures => {
169 pen.rect(9.5, 11.0, 5.0, 11.0, 2.5);
170 pen.arc(12.0, 11.5, 4.0, 200.0, 340.0);
171 pen.arc(12.0, 11.5, 7.5, 210.0, 330.0);
172 }
173 Icon::Earbud => earbud(pen),
174 Icon::Settings => {
175 for (y, knob) in [(6.0, 15.0), (12.0, 8.0), (18.0, 13.0)] {
176 pen.line((3.5, y), (20.5, y));
177 pen.dot(knob, y, 2.6);
178 }
179 }
180 Icon::NoiseOff => {
181 pen.circle(12.0, 12.0, 8.5);
182 pen.line((6.0, 6.0), (18.0, 18.0));
183 }
184 Icon::Anc => {
185 pen.circle(12.0, 12.0, 8.5);
186 pen.circle(12.0, 12.0, 4.5);
187 pen.dot(12.0, 12.0, 1.4);
188 }
189 Icon::Transparency => {
190 for n in 0..12u8 {
191 let angle = f32::from(n) * 30f32.to_radians();
192 pen.dot(12.0 + 8.5 * angle.cos(), 12.0 + 8.5 * angle.sin(), 1.15);
193 }
194 pen.dot(12.0, 12.0, 2.2);
195 }
196 Icon::Game | Icon::Bolt => pen.polyline(
197 &[
198 (13.5, 2.5),
199 (5.0, 13.5),
200 (11.5, 13.5),
201 (10.5, 21.5),
202 (19.0, 10.5),
203 (12.5, 10.5),
204 ],
205 true,
206 ),
207 Icon::Spatial => {
208 pen.dot(12.0, 12.0, 2.4);
209 for (r, spread) in [(5.5, 40.0), (9.5, 35.0)] {
210 pen.arc(12.0, 12.0, r, -spread, spread);
211 pen.arc(12.0, 12.0, r, 180.0 - spread, 180.0 + spread);
212 }
213 }
214 Icon::Bass => {
215 pen.polyline(
216 &[
217 (3.5, 9.5),
218 (7.5, 9.5),
219 (12.5, 5.0),
220 (12.5, 19.0),
221 (7.5, 14.5),
222 (3.5, 14.5),
223 ],
224 true,
225 );
226 pen.arc(13.0, 12.0, 4.0, -50.0, 50.0);
227 pen.arc(13.0, 12.0, 8.0, -50.0, 50.0);
228 }
229 Icon::Wind => {
230 pen.line((3.0, 8.5), (13.0, 8.5));
231 pen.arc(13.0, 6.0, 2.5, 90.0, -180.0);
232 pen.line((3.0, 12.5), (17.5, 12.5));
233 pen.arc(17.5, 15.5, 3.0, -90.0, 180.0);
234 pen.line((3.0, 16.5), (9.0, 16.5));
235 }
236 Icon::Dual => {
237 pen.rect(2.5, 5.0, 12.5, 9.0, 1.5);
238 pen.line((1.5, 17.5), (16.0, 17.5));
239 pen.rect(17.5, 8.0, 4.5, 11.0, 1.2);
240 }
241 Icon::Chevron => pen.polyline(&[(9.0, 5.5), (15.5, 12.0), (9.0, 18.5)], false),
242 Icon::Find => {
243 pen.circle(12.0, 12.0, 6.5);
244 pen.dot(12.0, 12.0, 2.0);
245 for (from, to) in [
246 ((12.0, 2.0), (12.0, 5.0)),
247 ((12.0, 19.0), (12.0, 22.0)),
248 ((2.0, 12.0), (5.0, 12.0)),
249 ((19.0, 12.0), (22.0, 12.0)),
250 ] {
251 pen.line(from, to);
252 }
253 }
254 Icon::Pencil => {
255 pen.polyline(&[(4.5, 19.5), (5.5, 15.0), (15.5, 5.0), (19.0, 8.5), (9.0, 18.5)], true);
256 pen.line((13.0, 7.5), (16.5, 11.0));
257 }
258 Icon::Trash => {
259 pen.line((4.0, 6.5), (20.0, 6.5));
260 pen.polyline(&[(9.0, 6.5), (9.5, 3.5), (14.5, 3.5), (15.0, 6.5)], false);
261 pen.polyline(&[(6.0, 6.5), (7.0, 20.5), (17.0, 20.5), (18.0, 6.5)], false);
262 pen.line((10.0, 10.5), (10.0, 16.5));
263 pen.line((14.0, 10.5), (14.0, 16.5));
264 }
265 Icon::Info => {
266 pen.circle(12.0, 12.0, 9.0);
267 pen.line((12.0, 11.0), (12.0, 16.5));
268 pen.dot(12.0, 7.8, 1.2);
269 }
270 Icon::Chip => {
271 pen.rect(6.5, 6.5, 11.0, 11.0, 2.0);
272 for at in [9.5, 14.5] {
273 pen.line((at, 2.5), (at, 6.5));
274 pen.line((at, 17.5), (at, 21.5));
275 pen.line((2.5, at), (6.5, at));
276 pen.line((17.5, at), (21.5, at));
277 }
278 }
279 Icon::Keys => {
280 pen.rect(2.5, 6.0, 19.0, 12.0, 2.5);
281 for x in [6.5, 10.0, 13.5, 17.0] {
282 pen.dot(x, 10.0, 0.9);
283 }
284 pen.line((8.0, 14.0), (16.0, 14.0));
285 }
286 Icon::Laptop => {
287 pen.rect(4.0, 5.0, 16.0, 11.0, 1.5);
288 pen.line((2.0, 19.0), (22.0, 19.0));
289 }
290 Icon::Phone => {
291 pen.rect(7.0, 2.5, 10.0, 19.0, 2.0);
292 pen.line((10.5, 18.5), (13.5, 18.5));
293 }
294 }
295}
296
297/// A filled earbud: the head, the ear tip pointing in, and the stem going down.
298fn earbud(pen: &mut Pen) {
299 pen.fill_rect(14.5, 5.0, 6.0, 7.0, 2.8);
300 pen.dot(11.0, 8.5, 6.3);
301 pen.fill_rect(6.6, 10.0, 4.4, 12.5, 2.2);
302}