Skip to main content

agg_gui/snap/
overlay.rs

1//! `SnapOverlay` — global overlay widget that paints the snap engine's
2//! visual guides on top of the rest of the UI.
3//!
4//! Reads from the thread-local guide buffer set by drag handlers via
5//! [`set_guides`].  Doesn't intercept any input — purely visual.
6//!
7//! Apps add one `SnapOverlay` as the topmost child of their root
8//! Stack (or any container that's the last to paint).  The widget's
9//! bounds are typically the full canvas; guide coordinates flow
10//! through unchanged because both the overlay and the rects use the
11//! same root coordinate space.
12//!
13//! [`set_guides`]: super::registry::set_guides
14
15use crate::color::Color;
16use crate::draw_ctx::DrawCtx;
17use crate::event::{Event, EventResult};
18use crate::geometry::{Rect, Size};
19use crate::widget::Widget;
20
21use super::registry::guides_snapshot;
22use super::SnapGuide;
23
24/// Visual guide overlay.  No state of its own — pure renderer over
25/// the thread-local guide buffer.
26pub struct SnapOverlay {
27    bounds: Rect,
28    children: Vec<Box<dyn Widget>>, // always empty
29}
30
31impl SnapOverlay {
32    pub fn new() -> Self {
33        Self {
34            bounds: Rect::default(),
35            children: Vec::new(),
36        }
37    }
38
39    /// Cyan-ish accent line for edge alignment guides.  Tuned to read
40    /// as a guide line without competing with content underneath.
41    fn alignment_color() -> Color {
42        Color::rgba(0.15, 0.70, 0.95, 0.95)
43    }
44
45    /// Pink-ish dimension line for equal-spacing markers — distinct
46    /// from alignment so the user can tell at a glance which kind of
47    /// snap engaged.
48    fn spacing_color() -> Color {
49        Color::rgba(0.95, 0.35, 0.55, 0.95)
50    }
51}
52
53impl Default for SnapOverlay {
54    fn default() -> Self {
55        Self::new()
56    }
57}
58
59impl Widget for SnapOverlay {
60    fn type_name(&self) -> &'static str {
61        "SnapOverlay"
62    }
63    fn bounds(&self) -> Rect {
64        self.bounds
65    }
66    fn set_bounds(&mut self, b: Rect) {
67        self.bounds = b;
68    }
69    fn children(&self) -> &[Box<dyn Widget>] {
70        &self.children
71    }
72    fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> {
73        &mut self.children
74    }
75    fn layout(&mut self, available: Size) -> Size {
76        // Fill the available area — guide coordinates are in the
77        // root frame, so the overlay needs to span the whole canvas
78        // to host them without clipping.
79        self.bounds = Rect::new(0.0, 0.0, available.width, available.height);
80        available
81    }
82    fn paint(&mut self, _ctx: &mut dyn DrawCtx) {
83        // Guides paint in the overlay pass — see `paint_global_overlay`.
84    }
85    fn paint_global_overlay(&mut self, ctx: &mut dyn DrawCtx) {
86        let guides = guides_snapshot();
87        if guides.is_empty() {
88            return;
89        }
90        ctx.set_line_width(1.0);
91        for guide in guides {
92            match guide {
93                SnapGuide::VLine { x, y0, y1 } => {
94                    ctx.set_stroke_color(Self::alignment_color());
95                    ctx.begin_path();
96                    ctx.move_to(x.round() + 0.5, y0);
97                    ctx.line_to(x.round() + 0.5, y1);
98                    ctx.stroke();
99                }
100                SnapGuide::HLine { y, x0, x1 } => {
101                    ctx.set_stroke_color(Self::alignment_color());
102                    ctx.begin_path();
103                    ctx.move_to(x0, y.round() + 0.5);
104                    ctx.line_to(x1, y.round() + 0.5);
105                    ctx.stroke();
106                }
107                SnapGuide::HSpacing { y, x0, x1 } => {
108                    ctx.set_stroke_color(Self::spacing_color());
109                    let yy = y.round() + 0.5;
110                    ctx.begin_path();
111                    ctx.move_to(x0, yy);
112                    ctx.line_to(x1, yy);
113                    ctx.stroke();
114                    // Tick marks at each end so the gap reads as a
115                    // dimension, not just a stray line.
116                    paint_tick_v(ctx, x0, yy, 4.0);
117                    paint_tick_v(ctx, x1, yy, 4.0);
118                }
119                SnapGuide::VSpacing { x, y0, y1 } => {
120                    ctx.set_stroke_color(Self::spacing_color());
121                    let xx = x.round() + 0.5;
122                    ctx.begin_path();
123                    ctx.move_to(xx, y0);
124                    ctx.line_to(xx, y1);
125                    ctx.stroke();
126                    paint_tick_h(ctx, xx, y0, 4.0);
127                    paint_tick_h(ctx, xx, y1, 4.0);
128                }
129            }
130        }
131    }
132    fn on_event(&mut self, _event: &Event) -> EventResult {
133        EventResult::Ignored
134    }
135    fn hit_test(&self, _p: crate::geometry::Point) -> bool {
136        // Pure visual overlay — never block input from underlying
137        // widgets.
138        false
139    }
140}
141
142fn paint_tick_v(ctx: &mut dyn DrawCtx, x: f64, y: f64, half: f64) {
143    ctx.begin_path();
144    ctx.move_to(x, y - half);
145    ctx.line_to(x, y + half);
146    ctx.stroke();
147}
148
149fn paint_tick_h(ctx: &mut dyn DrawCtx, x: f64, y: f64, half: f64) {
150    ctx.begin_path();
151    ctx.move_to(x - half, y);
152    ctx.line_to(x + half, y);
153    ctx.stroke();
154}