yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
canvas_interaction.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <cmath>
5
6#include "imgui/imgui.h"
7
8namespace yaze {
9namespace gui {
10
11namespace {
12
13// Static state for rectangle selection (temporary until we have proper state
14// management)
16 ImVec2 drag_start_pos = ImVec2(-1, -1);
17 bool is_dragging = false;
18};
19
20// Per-canvas rectangle selection drag state (thread-local for tests).
22
23} // namespace
24
25// ============================================================================
26// Helper Functions
27// ============================================================================
28
29ImVec2 AlignToGrid(ImVec2 pos, float grid_step) {
30 return ImVec2(std::floor(pos.x / grid_step) * grid_step,
31 std::floor(pos.y / grid_step) * grid_step);
32}
33
34ImVec2 GetMouseInCanvasSpace(const CanvasGeometry& geometry) {
35 const ImGuiIO& imgui_io = ImGui::GetIO();
36 const ImVec2 origin(geometry.canvas_p0.x + geometry.scrolling.x,
37 geometry.canvas_p0.y + geometry.scrolling.y);
38 return ImVec2(imgui_io.MousePos.x - origin.x, imgui_io.MousePos.y - origin.y);
39}
40
41bool IsMouseInCanvas(const CanvasGeometry& geometry) {
42 const ImGuiIO& imgui_io = ImGui::GetIO();
43 return imgui_io.MousePos.x >= geometry.canvas_p0.x &&
44 imgui_io.MousePos.x <= geometry.canvas_p1.x &&
45 imgui_io.MousePos.y >= geometry.canvas_p0.y &&
46 imgui_io.MousePos.y <= geometry.canvas_p1.y;
47}
48
49ImVec2 CanvasToTileGrid(ImVec2 canvas_pos, float tile_size,
50 float global_scale) {
51 const float scaled_size = tile_size * global_scale;
52 return ImVec2(std::floor(canvas_pos.x / scaled_size),
53 std::floor(canvas_pos.y / scaled_size));
54}
55
56// ============================================================================
57// Rectangle Selection Implementation
58// ============================================================================
59
61 int current_map, float tile_size,
62 ImDrawList* draw_list,
63 ImGuiMouseButton mouse_button) {
65 event.current_map = current_map;
66
67 if (!IsMouseInCanvas(geometry)) {
68 return event;
69 }
70
71 const ImVec2 mouse_pos = GetMouseInCanvasSpace(geometry);
72 const float scaled_size =
73 tile_size * geometry.scaled_size.x / geometry.canvas_sz.x;
74 constexpr int kSmallMapSize = 0x200; // 512 pixels
75
76 // Calculate super X/Y accounting for world offset
77 int super_y = 0;
78 int super_x = 0;
79 if (current_map < 0x40) {
80 // Light World (0x00-0x3F)
81 super_y = current_map / 8;
82 super_x = current_map % 8;
83 } else if (current_map < 0x80) {
84 // Dark World (0x40-0x7F)
85 super_y = (current_map - 0x40) / 8;
86 super_x = (current_map - 0x40) % 8;
87 } else {
88 // Special World (0x80+)
89 super_y = (current_map - 0x80) / 8;
90 super_x = (current_map - 0x80) % 8;
91 }
92
93 // Handle mouse button press - start selection
94 if (ImGui::IsMouseClicked(mouse_button)) {
95 g_select_rect_state.drag_start_pos = AlignToGrid(mouse_pos, scaled_size);
96 g_select_rect_state.is_dragging = false;
97
98 // Single tile selection on click
99 ImVec2 painter_pos = AlignToGrid(mouse_pos, scaled_size);
100 int painter_x = static_cast<int>(painter_pos.x);
101 int painter_y = static_cast<int>(painter_pos.y);
102
103 auto tile16_x = (painter_x % kSmallMapSize) / (kSmallMapSize / 0x20);
104 auto tile16_y = (painter_y % kSmallMapSize) / (kSmallMapSize / 0x20);
105
106 int index_x = super_x * 0x20 + tile16_x;
107 int index_y = super_y * 0x20 + tile16_y;
108
109 event.start_pos = painter_pos;
110 event.selected_tiles.push_back(
111 ImVec2(static_cast<float>(index_x), static_cast<float>(index_y)));
112 }
113
114 // Handle dragging - draw preview rectangle
115 ImVec2 drag_end_pos = AlignToGrid(mouse_pos, scaled_size);
116 if (ImGui::IsMouseDragging(mouse_button) && draw_list) {
117 g_select_rect_state.is_dragging = true;
118 event.is_active = true;
119 event.start_pos = g_select_rect_state.drag_start_pos;
120 event.end_pos = drag_end_pos;
121
122 // Draw preview rectangle
123 auto start =
124 ImVec2(geometry.canvas_p0.x + g_select_rect_state.drag_start_pos.x,
125 geometry.canvas_p0.y + g_select_rect_state.drag_start_pos.y);
126 auto end = ImVec2(geometry.canvas_p0.x + drag_end_pos.x + tile_size,
127 geometry.canvas_p0.y + drag_end_pos.y + tile_size);
128 draw_list->AddRect(start, end, IM_COL32(255, 255, 255, 255));
129 }
130
131 // Handle mouse release - complete selection
132 if (g_select_rect_state.is_dragging && !ImGui::IsMouseDown(mouse_button)) {
133 g_select_rect_state.is_dragging = false;
134 event.is_complete = true;
135 event.is_active = false;
136 event.start_pos = g_select_rect_state.drag_start_pos;
137 event.end_pos = drag_end_pos;
138
139 // Calculate selected tiles
140 constexpr int kTile16Size = 16;
141 int start_x = static_cast<int>(std::floor(
142 g_select_rect_state.drag_start_pos.x / scaled_size)) *
143 kTile16Size;
144 int start_y = static_cast<int>(std::floor(
145 g_select_rect_state.drag_start_pos.y / scaled_size)) *
146 kTile16Size;
147 int end_x = static_cast<int>(std::floor(drag_end_pos.x / scaled_size)) *
148 kTile16Size;
149 int end_y = static_cast<int>(std::floor(drag_end_pos.y / scaled_size)) *
150 kTile16Size;
151
152 if (start_x > end_x)
153 std::swap(start_x, end_x);
154 if (start_y > end_y)
155 std::swap(start_y, end_y);
156
157 constexpr int kTilesPerLocalMap = kSmallMapSize / 16;
158
159 for (int tile_y = start_y; tile_y <= end_y; tile_y += kTile16Size) {
160 for (int tile_x = start_x; tile_x <= end_x; tile_x += kTile16Size) {
161 int local_map_x = tile_x / kSmallMapSize;
162 int local_map_y = tile_y / kSmallMapSize;
163
164 int tile16_x = (tile_x % kSmallMapSize) / kTile16Size;
165 int tile16_y = (tile_y % kSmallMapSize) / kTile16Size;
166
167 int index_x = local_map_x * kTilesPerLocalMap + tile16_x;
168 int index_y = local_map_y * kTilesPerLocalMap + tile16_y;
169
170 event.selected_tiles.emplace_back(static_cast<float>(index_x),
171 static_cast<float>(index_y));
172 }
173 }
174 }
175
176 return event;
177}
178
180 int current_map, float tile_size,
181 ImGuiMouseButton mouse_button) {
182 TileSelectionEvent event;
183
184 if (!IsMouseInCanvas(geometry) || !ImGui::IsMouseClicked(mouse_button)) {
185 return event;
186 }
187
188 const ImVec2 mouse_pos = GetMouseInCanvasSpace(geometry);
189 const float scaled_size =
190 tile_size * geometry.scaled_size.x / geometry.canvas_sz.x;
191 constexpr int kSmallMapSize = 0x200;
192
193 // Calculate super X/Y
194 int super_y = 0;
195 int super_x = 0;
196 if (current_map < 0x40) {
197 super_y = current_map / 8;
198 super_x = current_map % 8;
199 } else if (current_map < 0x80) {
200 super_y = (current_map - 0x40) / 8;
201 super_x = (current_map - 0x40) % 8;
202 } else {
203 super_y = (current_map - 0x80) / 8;
204 super_x = (current_map - 0x80) % 8;
205 }
206
207 ImVec2 painter_pos = AlignToGrid(mouse_pos, scaled_size);
208 int painter_x = static_cast<int>(painter_pos.x);
209 int painter_y = static_cast<int>(painter_pos.y);
210
211 auto tile16_x = (painter_x % kSmallMapSize) / (kSmallMapSize / 0x20);
212 auto tile16_y = (painter_y % kSmallMapSize) / (kSmallMapSize / 0x20);
213
214 int index_x = super_x * 0x20 + tile16_x;
215 int index_y = super_y * 0x20 + tile16_y;
216
217 event.tile_position =
218 ImVec2(static_cast<float>(index_x), static_cast<float>(index_y));
219 event.is_valid = true;
220
221 return event;
222}
223
224// ============================================================================
225// Tile Painting Implementation
226// ============================================================================
227
228TilePaintEvent HandleTilePaint(const CanvasGeometry& geometry, int tile_id,
229 float tile_size, ImGuiMouseButton mouse_button) {
230 TilePaintEvent event;
231 event.tile_id = tile_id;
232
233 if (!IsMouseInCanvas(geometry)) {
234 return event;
235 }
236
237 const ImVec2 mouse_pos = GetMouseInCanvasSpace(geometry);
238 const float scaled_size =
239 tile_size * geometry.scaled_size.x / geometry.canvas_sz.x;
240
241 ImVec2 paint_pos = AlignToGrid(mouse_pos, scaled_size);
242 event.position = mouse_pos;
243 event.grid_position = paint_pos;
244
245 // Check for paint action
246 if (ImGui::IsMouseClicked(mouse_button)) {
247 event.is_complete = true;
248 event.is_drag = false;
249 } else if (ImGui::IsMouseDragging(mouse_button)) {
250 event.is_complete = true;
251 event.is_drag = true;
252 }
253
254 return event;
255}
256
258 const gfx::Bitmap& bitmap,
259 float tile_size,
260 ImDrawList* draw_list,
261 ImGuiMouseButton mouse_button) {
262 TilePaintEvent event;
263
264 if (!IsMouseInCanvas(geometry)) {
265 return event;
266 }
267
268 const ImVec2 mouse_pos = GetMouseInCanvasSpace(geometry);
269 const float scaled_size =
270 tile_size * geometry.scaled_size.x / geometry.canvas_sz.x;
271
272 // Calculate grid-aligned paint position
273 ImVec2 paint_pos = AlignToGrid(mouse_pos, scaled_size);
274 event.position = mouse_pos;
275 event.grid_position = paint_pos;
276
277 auto paint_pos_end =
278 ImVec2(paint_pos.x + scaled_size, paint_pos.y + scaled_size);
279
280 // Draw preview of tile at hover position
281 if (bitmap.is_active() && draw_list) {
282 const ImVec2 origin(geometry.canvas_p0.x + geometry.scrolling.x,
283 geometry.canvas_p0.y + geometry.scrolling.y);
284 draw_list->AddImage(
285 reinterpret_cast<ImTextureID>(bitmap.texture()),
286 ImVec2(origin.x + paint_pos.x, origin.y + paint_pos.y),
287 ImVec2(origin.x + paint_pos_end.x, origin.y + paint_pos_end.y));
288 }
289
290 // Check for paint action
291 if (ImGui::IsMouseClicked(mouse_button) &&
292 ImGui::IsMouseDragging(mouse_button)) {
293 event.is_complete = true;
294 event.is_drag = true;
295 }
296
297 return event;
298}
299
301 const gfx::Tilemap& tilemap, int current_tile,
302 ImDrawList* draw_list,
303 ImGuiMouseButton mouse_button) {
304 TilePaintEvent event;
305 event.tile_id = current_tile;
306
307 if (!IsMouseInCanvas(geometry)) {
308 return event;
309 }
310
311 const ImVec2 mouse_pos = GetMouseInCanvasSpace(geometry);
312 const float scaled_size =
313 16.0f * geometry.scaled_size.x / geometry.canvas_sz.x;
314
315 ImVec2 paint_pos = AlignToGrid(mouse_pos, scaled_size);
316 event.position = mouse_pos;
317 event.grid_position = paint_pos;
318
319 (void)tilemap;
320 (void)draw_list;
321
322 // Check for paint action
323 if (ImGui::IsMouseDown(mouse_button)) {
324 event.is_complete = true;
325 event.is_drag = ImGui::IsMouseDragging(mouse_button);
326 }
327
328 return event;
329}
330
331// ============================================================================
332// Hover and Preview Implementation
333// ============================================================================
334
335HoverEvent HandleHover(const CanvasGeometry& geometry, float tile_size) {
336 HoverEvent event;
337
338 if (!IsMouseInCanvas(geometry)) {
339 return event;
340 }
341
342 const ImVec2 mouse_pos = GetMouseInCanvasSpace(geometry);
343 const float scaled_size =
344 tile_size * geometry.scaled_size.x / geometry.canvas_sz.x;
345
346 event.position = mouse_pos;
347 event.grid_position = AlignToGrid(mouse_pos, scaled_size);
348 event.is_valid = true;
349
350 return event;
351}
352
353void RenderHoverPreview(const CanvasGeometry& geometry, const HoverEvent& hover,
354 float tile_size, ImDrawList* draw_list, ImU32 color) {
355 if (!hover.is_valid || !draw_list) {
356 return;
357 }
358
359 const float scaled_size =
360 tile_size * geometry.scaled_size.x / geometry.canvas_sz.x;
361 const ImVec2 origin(geometry.canvas_p0.x + geometry.scrolling.x,
362 geometry.canvas_p0.y + geometry.scrolling.y);
363
364 ImVec2 preview_start = ImVec2(origin.x + hover.grid_position.x,
365 origin.y + hover.grid_position.y);
366 ImVec2 preview_end =
367 ImVec2(preview_start.x + scaled_size, preview_start.y + scaled_size);
368
369 draw_list->AddRectFilled(preview_start, preview_end, color);
370}
371
372// ============================================================================
373// Entity Interaction Implementation (Stub for Phase 2.4)
374// ============================================================================
375
377 int entity_id,
378 ImVec2 entity_position) {
380 event.entity_id = entity_id;
381 event.position = entity_position;
382
383 if (!IsMouseInCanvas(geometry)) {
384 return event;
385 }
386
387 return event;
388}
389
390} // namespace gui
391} // namespace yaze
Free functions for canvas interaction handling.
Represents a bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:67
TextureHandle texture() const
Definition bitmap.h:401
bool is_active() const
Definition bitmap.h:405
void RenderHoverPreview(const CanvasGeometry &geometry, const HoverEvent &hover, float tile_size, ImDrawList *draw_list, ImU32 color)
Render hover preview overlay.
TilePaintEvent HandleTilePaintWithPreview(const CanvasGeometry &geometry, const gfx::Bitmap &bitmap, float tile_size, ImDrawList *draw_list, ImGuiMouseButton mouse_button)
Handle tile painter with bitmap preview.
TileSelectionEvent HandleTileSelection(const CanvasGeometry &geometry, int current_map, float tile_size, ImGuiMouseButton mouse_button)
Handle single tile selection (right-click)
ImVec2 CanvasToTileGrid(ImVec2 canvas_pos, float tile_size, float global_scale)
Calculate tile grid indices from canvas position.
TilePaintEvent HandleTilePaint(const CanvasGeometry &geometry, int tile_id, float tile_size, ImGuiMouseButton mouse_button)
Handle tile painting interaction.
ImVec2 GetMouseInCanvasSpace(const CanvasGeometry &geometry)
Get mouse position in canvas space.
ImVec2 AlignToGrid(ImVec2 pos, float grid_step)
Align position to grid.
HoverEvent HandleHover(const CanvasGeometry &geometry, float tile_size)
Update hover state for canvas.
RectSelectionEvent HandleRectangleSelection(const CanvasGeometry &geometry, int current_map, float tile_size, ImDrawList *draw_list, ImGuiMouseButton mouse_button)
Handle rectangle selection interaction.
TilePaintEvent HandleTilemapPaint(const CanvasGeometry &geometry, const gfx::Tilemap &tilemap, int current_tile, ImDrawList *draw_list, ImGuiMouseButton mouse_button)
Handle tilemap painting interaction.
EntityInteractionEvent HandleEntityInteraction(const CanvasGeometry &geometry, int entity_id, ImVec2 entity_position)
Handle entity interaction (hover, click, drag)
bool IsMouseInCanvas(const CanvasGeometry &geometry)
Check if mouse is in canvas bounds.
Tilemap structure for SNES tile-based graphics management.
Definition tilemap.h:118
Canvas geometry calculated per-frame.
Event payload for entity interactions.
int entity_id
Entity being interacted with.
Event payload for hover preview.
bool is_valid
True if hovering over canvas.
ImVec2 grid_position
Grid-aligned hover position.
Event payload for rectangle selection operations.
int current_map
Map ID for coordinate calculation.
Event payload for tile painting operations.
int tile_id
Tile ID being painted (-1 if none)
Event payload for single tile selection.