Skip to main content

hadrone_core/
interaction.rs

1use crate::{CollisionStrategy, CompactionType, LayoutItem, ResizeHandle, layout_engine};
2
3/// The type of user interaction currently active.
4#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
5pub enum InteractionType {
6    /// Moving an item across the grid.
7    Drag,
8    /// Changing an item's dimensions.
9    Resize,
10}
11
12/// Captures the state of an active drag or resize operation.
13/// This struct handles the high-frequency sub-pixel tracking and
14/// periodic grid snapping logic.
15#[derive(Debug, Clone, PartialEq)]
16pub struct InteractionSession {
17    /// ID of the item being interacted with.
18    pub id: String,
19    /// Whether this is a drag or resize.
20    pub interaction_type: InteractionType,
21    /// Initial mouse coordinates (X, Y) when the interaction started.
22    pub start_mouse: (f32, f32),
23    /// Initial position and dimensions (X, Y, W, H) in grid units.
24    pub start_rect: (i32, i32, i32, i32),
25    /// The resize handle being used (only relevant for Resize).
26    pub handle: ResizeHandle,
27    /// Current rendered width of a single column in pixels.
28    pub col_width_px: f32,
29    /// Current rendered height of a single row in pixels.
30    pub row_height_px: f32,
31    /// Margin between items (X, Y) in pixels.
32    pub margin: (i32, i32),
33    /// Padding inside the container around the grid content (left, top), in px.
34    /// Added to [`Self::get_visual_rect`] output; pointer deltas remain unchanged.
35    pub container_padding: (i32, i32),
36    /// The compaction strategy to apply during the interaction.
37    pub compaction: CompactionType,
38    /// Collision handling during the interaction (usually [`CollisionStrategy::PushDown`]).
39    pub collision: CollisionStrategy,
40}
41
42impl InteractionSession {
43    /// Returns the raw pixel displacement for smooth rendering
44    pub fn get_smooth_offset(&self, current_mouse: (f32, f32)) -> (f32, f32) {
45        (
46            current_mouse.0 - self.start_mouse.0,
47            current_mouse.1 - self.start_mouse.1,
48        )
49    }
50
51    /// Performs a single-pass interaction update:
52    /// Mouse Delta -> Handle-Aware Rect -> Grid Logic
53    pub fn update(&self, current_mouse: (f32, f32), layout: &mut Vec<LayoutItem>, cols: i32) {
54        let (dx, dy) = self.get_smooth_offset(current_mouse);
55
56        let grid_dx = (dx / self.col_width_px).round() as i32;
57        let grid_dy = (dy / (self.row_height_px + self.margin.1 as f32)).round() as i32;
58
59        let engine = layout_engine(self.compaction, self.collision, cols);
60
61        match self.interaction_type {
62            InteractionType::Drag => {
63                engine.move_element(
64                    layout,
65                    &self.id,
66                    self.start_rect.0 + grid_dx,
67                    self.start_rect.1 + grid_dy,
68                );
69            }
70            InteractionType::Resize => {
71                let (mut nx, mut ny, mut nw, mut nh) = self.start_rect;
72
73                match self.handle {
74                    ResizeHandle::East => {
75                        nw += grid_dx;
76                    }
77                    ResizeHandle::West => {
78                        nx += grid_dx;
79                        nw -= grid_dx;
80                    }
81                    ResizeHandle::South => {
82                        nh += grid_dy;
83                    }
84                    ResizeHandle::North => {
85                        ny += grid_dy;
86                        nh -= grid_dy;
87                    }
88                    ResizeHandle::SouthEast => {
89                        nw += grid_dx;
90                        nh += grid_dy;
91                    }
92                    ResizeHandle::SouthWest => {
93                        nx += grid_dx;
94                        nw -= grid_dx;
95                        nh += grid_dy;
96                    }
97                    ResizeHandle::NorthEast => {
98                        ny += grid_dy;
99                        nh -= grid_dy;
100                        nw += grid_dx;
101                    }
102                    ResizeHandle::NorthWest => {
103                        nx += grid_dx;
104                        nw -= grid_dx;
105                        ny += grid_dy;
106                        nh -= grid_dy;
107                    }
108                }
109
110                engine.resize_element(
111                    layout,
112                    &self.id,
113                    nx,
114                    ny,
115                    nw,
116                    nh,
117                    Some(self.handle),
118                );
119            }
120        }
121    }
122
123    /// Returns the continuous pixel-rect for rendering the active element.
124    /// This prevents the 'Snap-Flicker' by using start_rect as the base instead of the current layout.
125    pub fn get_visual_rect(&self, current_mouse: (f32, f32)) -> (f32, f32, f32, f32) {
126        let (dx, dy) = self.get_smooth_offset(current_mouse);
127        let (pad_x, pad_y) = (
128            self.container_padding.0 as f32,
129            self.container_padding.1 as f32,
130        );
131
132        // Convert start grid-rect to pixels
133        let mut x = self.start_rect.0 as f32 * self.col_width_px + pad_x;
134        let mut y = self.start_rect.1 as f32 * (self.row_height_px + self.margin.1 as f32) + pad_y;
135        let mut w = (self.start_rect.2 as f32 * self.col_width_px) - self.margin.0 as f32;
136        let mut h = self.start_rect.3 as f32 * self.row_height_px
137            + (self.start_rect.3 as f32 - 1.0) * self.margin.1 as f32;
138
139        match self.interaction_type {
140            InteractionType::Drag => {
141                x += dx;
142                y += dy;
143            }
144            InteractionType::Resize => match self.handle {
145                ResizeHandle::East => {
146                    w += dx;
147                }
148                ResizeHandle::West => {
149                    x += dx;
150                    w -= dx;
151                }
152                ResizeHandle::South => {
153                    h += dy;
154                }
155                ResizeHandle::North => {
156                    y += dy;
157                    h -= dy;
158                }
159                ResizeHandle::SouthEast => {
160                    w += dx;
161                    h += dy;
162                }
163                ResizeHandle::SouthWest => {
164                    x += dx;
165                    w -= dx;
166                    h += dy;
167                }
168                ResizeHandle::NorthEast => {
169                    y += dy;
170                    h -= dy;
171                    w += dx;
172                }
173                ResizeHandle::NorthWest => {
174                    x += dx;
175                    w -= dx;
176                    y += dy;
177                    h -= dy;
178                }
179            },
180        }
181
182        (x, y, w, h)
183    }
184
185    /// Returns the pure mouse displacement delta (dx, dy, dw, dh)
186    /// to be combined with native CSS calc() for frame-perfect rendering.
187    pub fn get_visual_delta(&self, current_mouse: (f32, f32)) -> (f32, f32, f32, f32) {
188        let (dx, dy) = self.get_smooth_offset(current_mouse);
189        let mut out_dx = 0.0;
190        let mut out_dy = 0.0;
191        let mut out_dw = 0.0;
192        let mut out_dh = 0.0;
193
194        match self.interaction_type {
195            InteractionType::Drag => {
196                out_dx = dx;
197                out_dy = dy;
198            }
199            InteractionType::Resize => match self.handle {
200                ResizeHandle::East => {
201                    out_dw = dx;
202                }
203                ResizeHandle::West => {
204                    out_dx = dx;
205                    out_dw = -dx;
206                }
207                ResizeHandle::South => {
208                    out_dh = dy;
209                }
210                ResizeHandle::North => {
211                    out_dy = dy;
212                    out_dh = -dy;
213                }
214                ResizeHandle::SouthEast => {
215                    out_dw = dx;
216                    out_dh = dy;
217                }
218                ResizeHandle::SouthWest => {
219                    out_dx = dx;
220                    out_dw = -dx;
221                    out_dh = dy;
222                }
223                ResizeHandle::NorthEast => {
224                    out_dy = dy;
225                    out_dh = -dy;
226                    out_dw = dx;
227                }
228                ResizeHandle::NorthWest => {
229                    out_dx = dx;
230                    out_dw = -dx;
231                    out_dy = dy;
232                    out_dh = -dy;
233                }
234            },
235        }
236        (out_dx, out_dy, out_dw, out_dh)
237    }
238}