egui_snarl/ui/
background_pattern.rs

1use egui::{emath::Rot2, vec2, Painter, Rect, Style, Vec2};
2
3use super::SnarlStyle;
4
5///Grid background pattern.
6///Use `SnarlStyle::background_pattern_stroke` for change stroke options
7#[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    /// Spacing between grid lines.
12    pub spacing: Vec2,
13
14    /// Angle of the grid.
15    #[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 {
36            spacing: DEFAULT_GRID_SPACING,
37            angle: DEFAULT_GRID_ANGLE,
38        }
39    }
40}
41
42impl Grid {
43    /// Create new grid with given spacing and angle.
44    #[must_use]
45    pub const fn new(spacing: Vec2, angle: f32) -> Self {
46        Self { spacing, angle }
47    }
48
49    fn draw(&self, viewport: &Rect, snarl_style: &SnarlStyle, style: &Style, painter: &Painter) {
50        let bg_stroke = snarl_style.get_bg_pattern_stroke(style);
51
52        let spacing = vec2(self.spacing.x.max(1.0), self.spacing.y.max(1.0));
53
54        let rot = Rot2::from_angle(self.angle);
55        let rot_inv = rot.inverse();
56
57        let pattern_bounds = viewport.rotate_bb(rot_inv);
58
59        let min_x = (pattern_bounds.min.x / spacing.x).ceil();
60        let max_x = (pattern_bounds.max.x / spacing.x).floor();
61
62        #[allow(clippy::cast_possible_truncation)]
63        for x in 0..=f32::ceil(max_x - min_x) as i64 {
64            #[allow(clippy::cast_precision_loss)]
65            let x = (x as f32 + min_x) * spacing.x;
66
67            let top = (rot * vec2(x, pattern_bounds.min.y)).to_pos2();
68            let bottom = (rot * vec2(x, pattern_bounds.max.y)).to_pos2();
69
70            painter.line_segment([top, bottom], bg_stroke);
71        }
72
73        let min_y = (pattern_bounds.min.y / spacing.y).ceil();
74        let max_y = (pattern_bounds.max.y / spacing.y).floor();
75
76        #[allow(clippy::cast_possible_truncation)]
77        for y in 0..=f32::ceil(max_y - min_y) as i64 {
78            #[allow(clippy::cast_precision_loss)]
79            let y = (y as f32 + min_y) * spacing.y;
80
81            let top = (rot * vec2(pattern_bounds.min.x, y)).to_pos2();
82            let bottom = (rot * vec2(pattern_bounds.max.x, y)).to_pos2();
83
84            painter.line_segment([top, bottom], bg_stroke);
85        }
86    }
87}
88
89/// Background pattern show beneath nodes and wires.
90#[derive(Clone, Copy, Debug, PartialEq)]
91#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
92#[cfg_attr(feature = "egui-probe", derive(egui_probe::EguiProbe))]
93pub enum BackgroundPattern {
94    /// No pattern.
95    NoPattern,
96
97    /// Linear grid.
98    #[cfg_attr(feature = "egui-probe", egui_probe(transparent))]
99    Grid(Grid),
100}
101
102impl Default for BackgroundPattern {
103    fn default() -> Self {
104        BackgroundPattern::new()
105    }
106}
107
108impl BackgroundPattern {
109    /// Create new background pattern with default values.
110    ///
111    /// Default patter is `Grid` with spacing - `
112    #[doc = default_grid_spacing!()]
113    /// ` and angle - `
114    #[doc = default_grid_angle!()]
115    /// ` radian.
116    #[must_use]
117    pub const fn new() -> Self {
118        Self::Grid(Grid::new(DEFAULT_GRID_SPACING, DEFAULT_GRID_ANGLE))
119    }
120
121    /// Create new grid background pattern with given spacing and angle.
122    #[must_use]
123    pub const fn grid(spacing: Vec2, angle: f32) -> Self {
124        Self::Grid(Grid::new(spacing, angle))
125    }
126
127    /// Draws background pattern.
128    pub fn draw(
129        &self,
130        viewport: &Rect,
131        snarl_style: &SnarlStyle,
132        style: &Style,
133        painter: &Painter,
134    ) {
135        match self {
136            BackgroundPattern::Grid(g) => g.draw(viewport, snarl_style, style, painter),
137            BackgroundPattern::NoPattern => {}
138        }
139    }
140}