egui_treeize/ui/
background_pattern.rs1use egui::{Painter, Rect, Style, Vec2, emath::Rot2, vec2};
2
3use super::TreeizeStyle;
4
5#[derive(Clone, Copy, Debug, PartialEq)]
8#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
9#[cfg_attr(feature = "egui-probe", derive(egui_probe::EguiProbe))]
10pub struct Grid {
11 pub spacing: Vec2,
13
14 #[cfg_attr(feature = "egui-probe", egui_probe(as egui_probe::angle))]
16 pub angle: f32,
17}
18
19const DEFAULT_GRID_SPACING: Vec2 = vec2(50.0, 50.0);
20macro_rules! default_grid_spacing {
21 () => {
22 stringify!(vec2(50.0, 50.0))
23 };
24}
25
26const DEFAULT_GRID_ANGLE: f32 = 1.0;
27macro_rules! default_grid_angle {
28 () => {
29 stringify!(1.0)
30 };
31}
32
33impl Default for Grid {
34 fn default() -> Self {
35 Self { spacing: DEFAULT_GRID_SPACING, angle: DEFAULT_GRID_ANGLE }
36 }
37}
38
39impl Grid {
40 #[must_use]
42 pub const fn new(spacing: Vec2, angle: f32) -> Self {
43 Self { spacing, angle }
44 }
45
46 fn draw(&self, viewport: &Rect, treeize_style: &TreeizeStyle, style: &Style, painter: &Painter) {
47 let bg_stroke = treeize_style.get_bg_pattern_stroke(style);
48
49 let spacing = vec2(self.spacing.x.max(1.0), self.spacing.y.max(1.0));
50
51 let rot = Rot2::from_angle(self.angle);
52 let rot_inv = rot.inverse();
53
54 let pattern_bounds = viewport.rotate_bb(rot_inv);
55
56 let min_x = (pattern_bounds.min.x / spacing.x).ceil();
57 let max_x = (pattern_bounds.max.x / spacing.x).floor();
58
59 #[allow(clippy::cast_possible_truncation)]
60 for x in 0..=f32::ceil(max_x - min_x) as i64 {
61 #[allow(clippy::cast_precision_loss)]
62 let x = (x as f32 + min_x) * spacing.x;
63
64 let top = (rot * vec2(x, pattern_bounds.min.y)).to_pos2();
65 let bottom = (rot * vec2(x, pattern_bounds.max.y)).to_pos2();
66
67 painter.line_segment([top, bottom], bg_stroke);
68 }
69
70 let min_y = (pattern_bounds.min.y / spacing.y).ceil();
71 let max_y = (pattern_bounds.max.y / spacing.y).floor();
72
73 #[allow(clippy::cast_possible_truncation)]
74 for y in 0..=f32::ceil(max_y - min_y) as i64 {
75 #[allow(clippy::cast_precision_loss)]
76 let y = (y as f32 + min_y) * spacing.y;
77
78 let top = (rot * vec2(pattern_bounds.min.x, y)).to_pos2();
79 let bottom = (rot * vec2(pattern_bounds.max.x, y)).to_pos2();
80
81 painter.line_segment([top, bottom], bg_stroke);
82 }
83 }
84}
85
86#[derive(Clone, Copy, Debug, PartialEq)]
88#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
89#[cfg_attr(feature = "egui-probe", derive(egui_probe::EguiProbe))]
90pub enum BackgroundPattern {
91 NoPattern,
93
94 #[cfg_attr(feature = "egui-probe", egui_probe(transparent))]
96 Grid(Grid),
97}
98
99impl Default for BackgroundPattern {
100 fn default() -> Self {
101 BackgroundPattern::new()
102 }
103}
104
105impl BackgroundPattern {
106 #[doc = default_grid_spacing!()]
110 #[doc = default_grid_angle!()]
112 #[must_use]
114 pub const fn new() -> Self {
115 Self::Grid(Grid::new(DEFAULT_GRID_SPACING, DEFAULT_GRID_ANGLE))
116 }
117
118 #[must_use]
120 pub const fn grid(spacing: Vec2, angle: f32) -> Self {
121 Self::Grid(Grid::new(spacing, angle))
122 }
123
124 pub fn draw(
126 &self,
127 viewport: &Rect,
128 treeize_style: &TreeizeStyle,
129 style: &Style,
130 painter: &Painter,
131 ) {
132 match self {
133 BackgroundPattern::Grid(g) => g.draw(viewport, treeize_style, style, painter),
134 BackgroundPattern::NoPattern => {}
135 }
136 }
137}