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(layout, &self.id, nx, ny, nw, nh, Some(self.handle));
111            }
112        }
113    }
114
115    /// Returns the continuous pixel-rect for rendering the active element.
116    /// This prevents the 'Snap-Flicker' by using start_rect as the base instead of the current layout.
117    pub fn get_visual_rect(&self, current_mouse: (f32, f32)) -> (f32, f32, f32, f32) {
118        let (dx, dy) = self.get_smooth_offset(current_mouse);
119        let (pad_x, pad_y) = (
120            self.container_padding.0 as f32,
121            self.container_padding.1 as f32,
122        );
123
124        // Convert start grid-rect to pixels
125        let mut x = self.start_rect.0 as f32 * self.col_width_px + pad_x;
126        let mut y = self.start_rect.1 as f32 * (self.row_height_px + self.margin.1 as f32) + pad_y;
127        let mut w = (self.start_rect.2 as f32 * self.col_width_px) - self.margin.0 as f32;
128        let mut h = self.start_rect.3 as f32 * self.row_height_px
129            + (self.start_rect.3 as f32 - 1.0) * self.margin.1 as f32;
130
131        match self.interaction_type {
132            InteractionType::Drag => {
133                x += dx;
134                y += dy;
135            }
136            InteractionType::Resize => match self.handle {
137                ResizeHandle::East => {
138                    w += dx;
139                }
140                ResizeHandle::West => {
141                    x += dx;
142                    w -= dx;
143                }
144                ResizeHandle::South => {
145                    h += dy;
146                }
147                ResizeHandle::North => {
148                    y += dy;
149                    h -= dy;
150                }
151                ResizeHandle::SouthEast => {
152                    w += dx;
153                    h += dy;
154                }
155                ResizeHandle::SouthWest => {
156                    x += dx;
157                    w -= dx;
158                    h += dy;
159                }
160                ResizeHandle::NorthEast => {
161                    y += dy;
162                    h -= dy;
163                    w += dx;
164                }
165                ResizeHandle::NorthWest => {
166                    x += dx;
167                    w -= dx;
168                    y += dy;
169                    h -= dy;
170                }
171            },
172        }
173
174        (x, y, w, h)
175    }
176
177    /// Returns the pure mouse displacement delta (dx, dy, dw, dh)
178    /// to be combined with native CSS calc() for frame-perfect rendering.
179    pub fn get_visual_delta(&self, current_mouse: (f32, f32)) -> (f32, f32, f32, f32) {
180        let (dx, dy) = self.get_smooth_offset(current_mouse);
181        let mut out_dx = 0.0;
182        let mut out_dy = 0.0;
183        let mut out_dw = 0.0;
184        let mut out_dh = 0.0;
185
186        match self.interaction_type {
187            InteractionType::Drag => {
188                out_dx = dx;
189                out_dy = dy;
190            }
191            InteractionType::Resize => match self.handle {
192                ResizeHandle::East => {
193                    out_dw = dx;
194                }
195                ResizeHandle::West => {
196                    out_dx = dx;
197                    out_dw = -dx;
198                }
199                ResizeHandle::South => {
200                    out_dh = dy;
201                }
202                ResizeHandle::North => {
203                    out_dy = dy;
204                    out_dh = -dy;
205                }
206                ResizeHandle::SouthEast => {
207                    out_dw = dx;
208                    out_dh = dy;
209                }
210                ResizeHandle::SouthWest => {
211                    out_dx = dx;
212                    out_dw = -dx;
213                    out_dh = dy;
214                }
215                ResizeHandle::NorthEast => {
216                    out_dy = dy;
217                    out_dh = -dy;
218                    out_dw = dx;
219                }
220                ResizeHandle::NorthWest => {
221                    out_dx = dx;
222                    out_dw = -dx;
223                    out_dy = dy;
224                    out_dh = -dy;
225                }
226            },
227        }
228        (out_dx, out_dy, out_dw, out_dh)
229    }
230}