cgt/
drawing.rs

1#![allow(missing_docs)]
2
3//! Drawing module
4
5use std::fmt::Arguments;
6
7use crate::{graph::VertexIndex, numeric::v2f::V2f};
8
9pub mod svg;
10
11#[cfg(feature = "tiny_skia")]
12pub mod tiny_skia;
13
14#[cfg(feature = "imgui")]
15pub mod imgui;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
18pub struct Color {
19    pub r: u8,
20    pub g: u8,
21    pub b: u8,
22    pub a: u8,
23}
24
25impl Color {
26    #[allow(clippy::unreadable_literal)]
27    pub const BLUE: Color = Color::from_hex(0x4e4afbff);
28
29    #[allow(clippy::unreadable_literal)]
30    pub const RED: Color = Color::from_hex(0xf92672ff);
31
32    #[allow(clippy::unreadable_literal)]
33    pub const BLACK: Color = Color::from_hex(0x000000ff);
34
35    #[allow(clippy::unreadable_literal)]
36    pub const LIGHT_GRAY: Color = Color::from_hex(0xccccccff);
37
38    #[allow(clippy::unreadable_literal)]
39    pub const DARK_GRAY: Color = Color::from_hex(0x444444ff);
40
41    #[must_use]
42    pub const fn from_hex(hex: u32) -> Color {
43        Color {
44            r: ((hex >> 24) & 0xff) as u8,
45            g: ((hex >> 16) & 0xff) as u8,
46            b: ((hex >> 8) & 0xff) as u8,
47            a: (hex & 0xff) as u8,
48        }
49    }
50
51    #[must_use]
52    pub const fn faded(self, alpha: u8) -> Color {
53        Color {
54            a: ((self.a as f32) * (alpha as f32 / 255.0)) as u8,
55            ..self
56        }
57    }
58}
59
60#[cfg(feature = "tiny_skia")]
61impl From<Color> for ::tiny_skia::Color {
62    fn from(color: Color) -> ::tiny_skia::Color {
63        ::tiny_skia::Color::from_rgba8(color.r, color.g, color.b, color.a)
64    }
65}
66
67#[cfg(feature = "imgui")]
68impl From<Color> for ::imgui::ImColor32 {
69    fn from(color: Color) -> ::imgui::ImColor32 {
70        ::imgui::ImColor32::from_rgba(color.r, color.g, color.b, color.a)
71    }
72}
73
74#[cfg(feature = "mint")]
75impl From<Color> for ::mint::Vector4<f32> {
76    fn from(color: Color) -> ::mint::Vector4<f32> {
77        ::mint::Vector4 {
78            x: color.r as f32 / 255.0,
79            y: color.g as f32 / 255.0,
80            z: color.b as f32 / 255.0,
81            w: color.a as f32 / 255.0,
82        }
83    }
84}
85
86#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
87pub enum Tile {
88    Square {
89        color: Color,
90    },
91    Circle {
92        tile_color: Color,
93        circle_color: Color,
94    },
95    Char {
96        tile_color: Color,
97        text_color: Color,
98        letter: char,
99    },
100}
101
102#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
103pub enum TextAlignment {
104    Left,
105    Center,
106    Right,
107}
108
109/// Anything that can be used for drawing
110pub trait Canvas {
111    fn rect(&mut self, position: V2f, size: V2f, color: Color);
112
113    fn circle(&mut self, position: V2f, radius: f32, color: Color);
114
115    fn line(&mut self, start: V2f, end: V2f, weight: f32, color: Color);
116
117    fn text(&mut self, position: V2f, text: Arguments<'_>, alignment: TextAlignment, color: Color);
118
119    fn large_char(&mut self, letter: char, position: V2f, color: Color);
120
121    fn tile(&mut self, position: V2f, tile: Tile) {
122        let tile_size = Self::tile_size();
123        match tile {
124            Tile::Square { color } => {
125                self.rect(position, tile_size, color);
126            }
127            Tile::Circle {
128                tile_color,
129                circle_color,
130            } => {
131                self.rect(position, tile_size, tile_color);
132                self.circle(position + tile_size * 0.5, tile_size.x * 0.4, circle_color);
133            }
134            Tile::Char {
135                tile_color,
136                text_color,
137                letter,
138            } => {
139                self.rect(position, tile_size, tile_color);
140                self.large_char(letter, position, text_color);
141            }
142        }
143    }
144
145    fn highlight_tile(&mut self, position: V2f, color: Color) {
146        let tile_size = Self::tile_size();
147        let weight = Self::thick_line_weight() * 2.0;
148
149        self.line(
150            position,
151            position
152                + V2f {
153                    x: tile_size.x,
154                    y: 0.0,
155                },
156            weight,
157            color,
158        );
159        self.line(
160            position,
161            position
162                + V2f {
163                    x: 0.0,
164                    y: Self::tile_size().y,
165                },
166            weight,
167            color,
168        );
169        self.line(
170            position
171                + V2f {
172                    x: tile_size.x,
173                    y: 0.0,
174                },
175            position + tile_size,
176            weight,
177            color,
178        );
179        self.line(
180            position
181                + V2f {
182                    x: 0.0,
183                    y: tile_size.y,
184                },
185            position + tile_size,
186            weight,
187            color,
188        );
189    }
190
191    fn grid(&mut self, position: V2f, columns: u32, rows: u32) {
192        let cell_size = Self::tile_size();
193        let grid_weight = Self::thick_line_weight();
194
195        for row in 0..=rows {
196            let line_start = V2f {
197                x: position.x,
198                y: grid_weight.mul_add(
199                    row as f32 + 0.5,
200                    cell_size.y.mul_add(row as f32, position.y),
201                ),
202            };
203            let line_end = V2f {
204                x: grid_weight.mul_add(
205                    (columns + 1) as f32,
206                    cell_size.x.mul_add(columns as f32, position.x),
207                ),
208                y: grid_weight.mul_add(
209                    row as f32 + 0.5,
210                    cell_size.y.mul_add(row as f32, position.y),
211                ),
212            };
213            self.line(
214                line_start,
215                line_end,
216                Self::thick_line_weight(),
217                Color::BLACK,
218            );
219        }
220
221        for column in 0..=columns {
222            let line_start = V2f {
223                x: grid_weight.mul_add(
224                    column as f32 + 0.5,
225                    cell_size.x.mul_add(column as f32, position.x),
226                ),
227                y: position.y,
228            };
229            let line_end = V2f {
230                x: grid_weight.mul_add(
231                    column as f32 + 0.5,
232                    cell_size.x.mul_add(column as f32, position.x),
233                ),
234                y: grid_weight.mul_add(
235                    (rows + 1) as f32,
236                    cell_size.y.mul_add(rows as f32, position.y),
237                ),
238            };
239            self.line(
240                line_start,
241                line_end,
242                Self::thick_line_weight(),
243                Color::BLACK,
244            );
245        }
246    }
247
248    fn vertex(&mut self, position: V2f, color: Color, _idx: VertexIndex) {
249        let radius = Self::vertex_radius();
250        self.circle(position, radius, Color::BLACK);
251        self.circle(position, radius - 1.0, color);
252    }
253
254    fn tile_size() -> V2f;
255
256    fn vertex_radius() -> f32 {
257        // FIXME: Remove default
258        Self::tile_size().x * 0.25
259    }
260
261    fn thick_line_weight() -> f32;
262
263    fn thin_line_weight() -> f32 {
264        Self::thick_line_weight() * 0.5
265    }
266
267    fn tile_position(x: u8, y: u8) -> V2f {
268        let tile_size = Self::tile_size();
269        let grid_weight = Self::thick_line_weight();
270        V2f {
271            x: (x as f32).mul_add(tile_size.x, (x + 1) as f32 * grid_weight),
272            y: (y as f32).mul_add(tile_size.y, (y + 1) as f32 * grid_weight),
273        }
274    }
275}
276
277#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
278pub struct BoundingBox {
279    pub top_left: V2f,
280    pub bottom_right: V2f,
281}
282
283impl BoundingBox {
284    pub fn size(self) -> V2f {
285        V2f {
286            x: f32::abs(self.bottom_right.x - self.top_left.x),
287            y: f32::abs(self.bottom_right.y - self.top_left.y),
288        }
289    }
290}
291
292pub trait Draw {
293    /// Paint position on existing canvas
294    fn draw<C>(&self, canvas: &mut C)
295    where
296        C: Canvas;
297
298    /// Minimum required canvas size to paint the whole position
299    fn required_canvas<C>(&self) -> BoundingBox
300    where
301        C: Canvas;
302}