| 1 | //! Monoline toolbar icons drawn with the painter: 1.5 px strokes on a |
| 2 | //! 16-unit grid, at most six strokes each, no fills. |
| 3 | |
| 4 | use std::f32::consts::TAU; |
| 5 | |
| 6 | use eframe::egui::{Color32, IconData, Painter, Pos2, Rect, Shape, Stroke, vec2}; |
| 7 | |
| 8 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
| 9 | pub enum Icon { |
| 10 | New, |
| 11 | Open, |
| 12 | Save, |
| 13 | Build, |
| 14 | Debug, |
| 15 | Reset, |
| 16 | Run, |
| 17 | Pause, |
| 18 | StepInto, |
| 19 | StepOver, |
| 20 | StepOut, |
| 21 | RunToCursor, |
| 22 | Breakpoint, |
| 23 | } |
| 24 | |
| 25 | pub fn paint(painter: &Painter, rect: Rect, icon: Icon, color: Color32) { |
| 26 | let s = rect.width().min(rect.height()) / 16.0; |
| 27 | let o = rect.center() - vec2(8.0, 8.0) * s; |
| 28 | let p = |x: f32, y: f32| o + vec2(x, y) * s; |
| 29 | let stroke = Stroke::new(1.5, color); |
| 30 | let line = |pts: &[(f32, f32)]| { |
| 31 | painter.add(Shape::line( |
| 32 | pts.iter().map(|&(x, y)| p(x, y)).collect(), |
| 33 | stroke, |
| 34 | )); |
| 35 | }; |
| 36 | let closed = |pts: &[(f32, f32)]| { |
| 37 | painter.add(Shape::closed_line( |
| 38 | pts.iter().map(|&(x, y)| p(x, y)).collect(), |
| 39 | stroke, |
| 40 | )); |
| 41 | }; |
| 42 | // Arc around (cx, cy) from angle a0 to a1 (radians, clockwise on screen). |
| 43 | let arc = |cx: f32, cy: f32, r: f32, a0: f32, a1: f32| -> Vec<Pos2> { |
| 44 | let n = 24; |
| 45 | (0..=n) |
| 46 | .map(|i| { |
| 47 | let a = a0 + (a1 - a0) * i as f32 / n as f32; |
| 48 | p(cx + r * a.cos(), cy + r * a.sin()) |
| 49 | }) |
| 50 | .collect() |
| 51 | }; |
| 52 | let dot = |x: f32, y: f32| painter.circle_stroke(p(x, y), 1.5 * s, stroke); |
| 53 | |
| 54 | match icon { |
| 55 | Icon::New => { |
| 56 | closed(&[ |
| 57 | (3.0, 1.5), |
| 58 | (9.5, 1.5), |
| 59 | (13.0, 5.0), |
| 60 | (13.0, 14.5), |
| 61 | (3.0, 14.5), |
| 62 | ]); |
| 63 | line(&[(9.5, 1.5), (9.5, 5.0), (13.0, 5.0)]); |
| 64 | } |
| 65 | Icon::Open => { |
| 66 | closed(&[ |
| 67 | (1.5, 3.0), |
| 68 | (6.0, 3.0), |
| 69 | (7.5, 4.5), |
| 70 | (14.5, 4.5), |
| 71 | (14.5, 13.0), |
| 72 | (1.5, 13.0), |
| 73 | ]); |
| 74 | line(&[(1.5, 7.0), (14.5, 7.0)]); |
| 75 | } |
| 76 | Icon::Save => { |
| 77 | line(&[(8.0, 1.5), (8.0, 10.0)]); |
| 78 | line(&[(4.5, 6.5), (8.0, 10.0), (11.5, 6.5)]); |
| 79 | line(&[(2.0, 10.0), (2.0, 14.0), (14.0, 14.0), (14.0, 10.0)]); |
| 80 | } |
| 81 | Icon::Build => { |
| 82 | // A DIP chip: body and four pins. |
| 83 | closed(&[(4.0, 3.5), (12.0, 3.5), (12.0, 12.5), (4.0, 12.5)]); |
| 84 | for y in [6.0, 10.0] { |
| 85 | line(&[(1.5, y), (4.0, y)]); |
| 86 | line(&[(12.0, y), (14.5, y)]); |
| 87 | } |
| 88 | } |
| 89 | Icon::Debug => { |
| 90 | // Power switch: starts and stops the debug session. |
| 91 | painter.add(Shape::line( |
| 92 | arc(8.0, 9.0, 5.5, -TAU * 0.17, TAU * 0.67), |
| 93 | stroke, |
| 94 | )); |
| 95 | line(&[(8.0, 1.5), (8.0, 8.0)]); |
| 96 | } |
| 97 | Icon::Reset => { |
| 98 | painter.add(Shape::line( |
| 99 | arc(8.0, 8.5, 5.5, -TAU * 0.2, TAU * 0.6), |
| 100 | stroke, |
| 101 | )); |
| 102 | let tip = ( |
| 103 | 8.0 + 5.5 * (TAU * 0.6f32).cos(), |
| 104 | 8.5 + 5.5 * (TAU * 0.6f32).sin(), |
| 105 | ); |
| 106 | line(&[(tip.0 - 3.5, tip.1 - 0.5), tip, (tip.0 + 0.5, tip.1 - 3.5)]); |
| 107 | } |
| 108 | Icon::Run => closed(&[(4.5, 2.5), (13.0, 8.0), (4.5, 13.5)]), |
| 109 | Icon::Pause => { |
| 110 | line(&[(5.5, 3.0), (5.5, 13.0)]); |
| 111 | line(&[(10.5, 3.0), (10.5, 13.0)]); |
| 112 | } |
| 113 | Icon::StepInto => { |
| 114 | line(&[(8.0, 1.5), (8.0, 9.5)]); |
| 115 | line(&[(4.5, 6.0), (8.0, 9.5), (11.5, 6.0)]); |
| 116 | dot(8.0, 13.5); |
| 117 | } |
| 118 | Icon::StepOver => { |
| 119 | painter.add(Shape::line(arc(8.0, 9.0, 5.5, TAU * 0.5, TAU), stroke)); |
| 120 | line(&[(10.5, 8.0), (13.5, 9.0), (14.5, 6.0)]); |
| 121 | dot(8.0, 13.5); |
| 122 | } |
| 123 | Icon::StepOut => { |
| 124 | line(&[(8.0, 10.5), (8.0, 2.0)]); |
| 125 | line(&[(4.5, 5.5), (8.0, 2.0), (11.5, 5.5)]); |
| 126 | dot(8.0, 13.5); |
| 127 | } |
| 128 | Icon::RunToCursor => { |
| 129 | line(&[(1.5, 8.0), (11.0, 8.0)]); |
| 130 | line(&[(7.5, 4.5), (11.0, 8.0), (7.5, 11.5)]); |
| 131 | line(&[(14.5, 3.0), (14.5, 13.0)]); |
| 132 | } |
| 133 | Icon::Breakpoint => { |
| 134 | painter.circle_stroke(p(8.0, 8.0), 5.0 * s, stroke); |
| 135 | } |
| 136 | } |
| 137 | } |
| 138 | |
| 139 | /// Window icon: a dot-matrix "51" in white on black with one red dot. |
| 140 | pub fn app_icon() -> IconData { |
| 141 | const N: usize = 64; |
| 142 | // 5×7 glyphs. |
| 143 | const FIVE: [&str; 7] = [ |
| 144 | "#####", "#....", "####.", "....#", "....#", "#...#", ".###.", |
| 145 | ]; |
| 146 | const ONE: [&str; 7] = [ |
| 147 | "..#..", ".##..", "..#..", "..#..", "..#..", "..#..", ".###.", |
| 148 | ]; |
| 149 | let mut rgba = vec![0u8; N * N * 4]; |
| 150 | // Rounded black tile. |
| 151 | let r = 12.0; |
| 152 | for y in 0..N { |
| 153 | for x in 0..N { |
| 154 | let (fx, fy) = (x as f32 + 0.5, y as f32 + 0.5); |
| 155 | let dx = (r - fx).max(fx - (N as f32 - r)).max(0.0); |
| 156 | let dy = (r - fy).max(fy - (N as f32 - r)).max(0.0); |
| 157 | let a = (r + 0.5 - (dx * dx + dy * dy).sqrt()).clamp(0.0, 1.0); |
| 158 | rgba[(y * N + x) * 4 + 3] = (a * 255.0) as u8; |
| 159 | } |
| 160 | } |
| 161 | let mut put_disc = |cx: f32, cy: f32, r: f32, c: [u8; 3]| { |
| 162 | for y in 0..N { |
| 163 | for x in 0..N { |
| 164 | let d = ((x as f32 + 0.5 - cx).powi(2) + (y as f32 + 0.5 - cy).powi(2)).sqrt(); |
| 165 | let a = (r + 0.5 - d).clamp(0.0, 1.0); |
| 166 | if a > 0.0 { |
| 167 | let i = (y * N + x) * 4; |
| 168 | for k in 0..3 { |
| 169 | rgba[i + k] = (rgba[i + k] as f32 * (1.0 - a) + c[k] as f32 * a) as u8; |
| 170 | } |
| 171 | rgba[i + 3] = 255; |
| 172 | } |
| 173 | } |
| 174 | } |
| 175 | }; |
| 176 | let pitch = 4.0; |
| 177 | let (x0, y0) = (11.0, 19.0); |
| 178 | for (gi, glyph) in [FIVE, ONE].iter().enumerate() { |
| 179 | for (row, bits) in glyph.iter().enumerate() { |
| 180 | for (col, b) in bits.bytes().enumerate() { |
| 181 | if b == b'#' { |
| 182 | let cx = x0 + (gi * 6 + col) as f32 * pitch + pitch / 2.0; |
| 183 | let cy = y0 + row as f32 * pitch + pitch / 2.0; |
| 184 | put_disc(cx, cy, 1.5, [255, 255, 255]); |
| 185 | } |
| 186 | } |
| 187 | } |
| 188 | } |
| 189 | put_disc(52.0, 12.0, 3.0, [0xD7, 0x19, 0x21]); |
| 190 | IconData { |
| 191 | rgba, |
| 192 | width: N as u32, |
| 193 | height: N as u32, |
| 194 | } |
| 195 | } |
| 196 | |
| 197 | /// Square box for an icon of size `size` centred in `rect`. |
| 198 | pub fn icon_rect(rect: Rect, size: f32) -> Rect { |
| 199 | Rect::from_center_size(rect.center(), vec2(size, size)) |
| 200 | } |