Skip to main content

agg_gui/widgets/window/
snap_glue.rs

1//! Snap-layout glue for [`super::Window`]: the drag/resize snap passes, the
2//! internal→engine direction mapping, and the [`Snappable`](crate::snap::Snappable)
3//! trait impl. Extracted from `window.rs` so that file stays under the 800-line
4//! guardrail; the logic is unchanged, it just lives in its own cohesive module.
5
6use super::{snap, ResizeDir, Window};
7use crate::geometry::Rect;
8
9impl Window {
10    /// Snap pass for a title-bar drag.  Skipped entirely when the
11    /// global toggle is off — cheap when not in use.  Replaces
12    /// `self.bounds` with the engine's snapped result and writes the
13    /// guide list for `SnapOverlay` to paint.
14    pub(crate) fn apply_move_snap(&mut self) {
15        if !crate::snap::is_enabled() {
16            crate::snap::clear_guides();
17            return;
18        }
19        let targets = crate::snap::targets_snapshot();
20        // The registry and guides live in canvas-absolute space (see
21        // `widget_impl`'s `register_target`). A nested modal dialog's `bounds`
22        // are slot-local, so translate into canvas space by the cached slot
23        // offset before snapping, then translate the snapped rect back.  Zero
24        // offset for a top-level window makes this a no-op.
25        let (ox, oy) = self.world_offset.get();
26        let candidate = Rect::new(
27            self.bounds.x + ox,
28            self.bounds.y + oy,
29            self.bounds.width,
30            self.bounds.height,
31        );
32        let result = crate::snap::compute_snap(
33            candidate,
34            self.snap_id,
35            &targets,
36            crate::snap::DEFAULT_THRESHOLD,
37            crate::snap::SnapMode::Move,
38        );
39        let r = result.rect;
40        self.bounds = snap(Rect::new(r.x - ox, r.y - oy, r.width, r.height));
41        crate::snap::set_guides(result.guides);
42    }
43
44    /// Snap pass for an edge / corner resize drag.  Only edges that
45    /// the active handle is allowed to move can snap — the engine
46    /// enforces that internally via `SnapMode::Resize`.
47    pub(crate) fn apply_resize_snap(&mut self, dir: ResizeDir) {
48        if !crate::snap::is_enabled() {
49            crate::snap::clear_guides();
50            return;
51        }
52        let targets = crate::snap::targets_snapshot();
53        let edge = resize_dir_to_snap_edge(dir);
54        // Same canvas-absolute translation as `apply_move_snap` — resize snaps
55        // against the same registry, so a nested dialog's slot-local bounds must
56        // be lifted into canvas space and the result folded back.
57        let (ox, oy) = self.world_offset.get();
58        let candidate = Rect::new(
59            self.bounds.x + ox,
60            self.bounds.y + oy,
61            self.bounds.width,
62            self.bounds.height,
63        );
64        let result = crate::snap::compute_snap(
65            candidate,
66            self.snap_id,
67            &targets,
68            crate::snap::DEFAULT_THRESHOLD,
69            crate::snap::SnapMode::Resize(edge),
70        );
71        let r = result.rect;
72        self.bounds = snap(Rect::new(r.x - ox, r.y - oy, r.width, r.height));
73        crate::snap::set_guides(result.guides);
74    }
75}
76
77/// Map an internal `ResizeDir` to the snap engine's compass-direction
78/// enum.  Kept private — the snap engine owns its own enum so the
79/// engine isn't coupled to the Window widget.
80fn resize_dir_to_snap_edge(dir: ResizeDir) -> crate::snap::ResizeEdge {
81    use crate::snap::ResizeEdge as E;
82    match dir {
83        ResizeDir::N => E::North,
84        ResizeDir::NE => E::NorthEast,
85        ResizeDir::E => E::East,
86        ResizeDir::SE => E::SouthEast,
87        ResizeDir::S => E::South,
88        ResizeDir::SW => E::SouthWest,
89        ResizeDir::W => E::West,
90        ResizeDir::NW => E::NorthWest,
91    }
92}
93
94impl crate::snap::Snappable for Window {
95    fn snap_id(&self) -> crate::snap::SnapId {
96        self.snap_id
97    }
98    fn snap_rect(&self) -> Rect {
99        self.bounds
100    }
101    fn set_snap_rect(&mut self, r: Rect) {
102        self.bounds = snap(r);
103    }
104    fn is_snap_source(&self) -> bool {
105        self.requested_visible() && !self.maximized
106    }
107    fn is_snap_target(&self) -> bool {
108        // Maximized windows fill the canvas — pulling siblings to
109        // their edges would just glue everything to the canvas
110        // perimeter, which isn't useful as a layout aid.  Hidden
111        // windows aren't valid targets either.
112        self.requested_visible() && !self.maximized
113    }
114}