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#include "imgui/imgui.h"
6
7namespace yaze {
8namespace gui {
9
10namespace {
11
12// Static state for rectangle selection (temporary until we have proper state management)
14 ImVec2 drag_start_pos = ImVec2(-1, -1);
15 bool is_dragging = false;
16};
17
18// Per-canvas state (keyed by canvas geometry pointer for simplicity)
19// TODO(scawful): Replace with proper state management in Phase 2.5
21
22} // namespace
23
24// ============================================================================
25// Helper Functions
26// ============================================================================
27
28ImVec2 AlignToGrid(ImVec2 pos, float grid_step) {
29 return ImVec2(std::floor(pos.x / grid_step) * grid_step,
30 std::floor(pos.y / grid_step) * grid_step);
31}
32
33ImVec2 GetMouseInCanvasSpace(const CanvasGeometry& geometry) {
34 const ImGuiIO& imgui_io = ImGui::GetIO();
35 const ImVec2 origin(geometry.canvas_p0.x + geometry.scrolling.x,
36 geometry.canvas_p0.y + geometry.scrolling.y);
37 return ImVec2(imgui_io.MousePos.x - origin.x, imgui_io.MousePos.y - origin.y);
38}
39
40bool IsMouseInCanvas(const CanvasGeometry& geometry) {
41 const ImGuiIO& imgui_io = ImGui::GetIO();
42 return imgui_io.MousePos.x >= geometry.canvas_p0.x &&
43 imgui_io.MousePos.x <= geometry.canvas_p1.x &&
44 imgui_io.MousePos.y >= geometry.canvas_p0.y &&
45 imgui_io.MousePos.y <= geometry.canvas_p1.y;
46}
47
48ImVec2 CanvasToTileGrid(ImVec2 canvas_pos, float tile_size, float global_scale) {
49 const float scaled_size = tile_size * global_scale;
50 return ImVec2(std::floor(canvas_pos.x / scaled_size),
51 std::floor(canvas_pos.y / scaled_size));
52}
53
54// ============================================================================
55// Rectangle Selection Implementation
56// ============================================================================
57
59 const CanvasGeometry& geometry,
60 int current_map,
61 float tile_size,
62 ImDrawList* draw_list,
63 ImGuiMouseButton mouse_button) {
64
66 event.current_map = current_map;
67
68 if (!IsMouseInCanvas(geometry)) {
69 return event;
70 }
71
72 const ImVec2 mouse_pos = GetMouseInCanvasSpace(geometry);
73 const float scaled_size = 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(ImVec2(static_cast<float>(index_x),
111 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 = ImVec2(geometry.canvas_p0.x + g_select_rect_state.drag_start_pos.x,
124 geometry.canvas_p0.y + g_select_rect_state.drag_start_pos.y);
125 auto end = ImVec2(geometry.canvas_p0.x + drag_end_pos.x + tile_size,
126 geometry.canvas_p0.y + drag_end_pos.y + tile_size);
127 draw_list->AddRect(start, end, IM_COL32(255, 255, 255, 255));
128 }
129
130 // Handle mouse release - complete selection
131 if (g_select_rect_state.is_dragging && !ImGui::IsMouseDown(mouse_button)) {
132 g_select_rect_state.is_dragging = false;
133 event.is_complete = true;
134 event.is_active = false;
135 event.start_pos = g_select_rect_state.drag_start_pos;
136 event.end_pos = drag_end_pos;
137
138 // Calculate selected tiles
139 constexpr int kTile16Size = 16;
140 int start_x = static_cast<int>(std::floor(g_select_rect_state.drag_start_pos.x / scaled_size)) * kTile16Size;
141 int start_y = static_cast<int>(std::floor(g_select_rect_state.drag_start_pos.y / scaled_size)) * kTile16Size;
142 int end_x = static_cast<int>(std::floor(drag_end_pos.x / scaled_size)) * kTile16Size;
143 int end_y = static_cast<int>(std::floor(drag_end_pos.y / scaled_size)) * kTile16Size;
144
145 if (start_x > end_x) std::swap(start_x, end_x);
146 if (start_y > end_y) std::swap(start_y, end_y);
147
148 constexpr int kTilesPerLocalMap = kSmallMapSize / 16;
149
150 for (int tile_y = start_y; tile_y <= end_y; tile_y += kTile16Size) {
151 for (int tile_x = start_x; tile_x <= end_x; tile_x += kTile16Size) {
152 int local_map_x = tile_x / kSmallMapSize;
153 int local_map_y = tile_y / kSmallMapSize;
154
155 int tile16_x = (tile_x % kSmallMapSize) / kTile16Size;
156 int tile16_y = (tile_y % kSmallMapSize) / kTile16Size;
157
158 int index_x = local_map_x * kTilesPerLocalMap + tile16_x;
159 int index_y = local_map_y * kTilesPerLocalMap + tile16_y;
160
161 event.selected_tiles.emplace_back(static_cast<float>(index_x),
162 static_cast<float>(index_y));
163 }
164 }
165 }
166
167 return event;
168}
169
171 const CanvasGeometry& geometry,
172 int current_map,
173 float tile_size,
174 ImGuiMouseButton mouse_button) {
175
176 TileSelectionEvent event;
177
178 if (!IsMouseInCanvas(geometry) || !ImGui::IsMouseClicked(mouse_button)) {
179 return event;
180 }
181
182 const ImVec2 mouse_pos = GetMouseInCanvasSpace(geometry);
183 const float scaled_size = tile_size * geometry.scaled_size.x / geometry.canvas_sz.x;
184 constexpr int kSmallMapSize = 0x200;
185
186 // Calculate super X/Y
187 int super_y = 0;
188 int super_x = 0;
189 if (current_map < 0x40) {
190 super_y = current_map / 8;
191 super_x = current_map % 8;
192 } else if (current_map < 0x80) {
193 super_y = (current_map - 0x40) / 8;
194 super_x = (current_map - 0x40) % 8;
195 } else {
196 super_y = (current_map - 0x80) / 8;
197 super_x = (current_map - 0x80) % 8;
198 }
199
200 ImVec2 painter_pos = AlignToGrid(mouse_pos, scaled_size);
201 int painter_x = static_cast<int>(painter_pos.x);
202 int painter_y = static_cast<int>(painter_pos.y);
203
204 auto tile16_x = (painter_x % kSmallMapSize) / (kSmallMapSize / 0x20);
205 auto tile16_y = (painter_y % kSmallMapSize) / (kSmallMapSize / 0x20);
206
207 int index_x = super_x * 0x20 + tile16_x;
208 int index_y = super_y * 0x20 + tile16_y;
209
210 event.tile_position = ImVec2(static_cast<float>(index_x),
211 static_cast<float>(index_y));
212 event.is_valid = true;
213
214 return event;
215}
216
217// ============================================================================
218// Tile Painting Implementation
219// ============================================================================
220
222 const CanvasGeometry& geometry,
223 int tile_id,
224 float tile_size,
225 ImGuiMouseButton mouse_button) {
226
227 TilePaintEvent event;
228 event.tile_id = tile_id;
229
230 if (!IsMouseInCanvas(geometry)) {
231 return event;
232 }
233
234 const ImVec2 mouse_pos = GetMouseInCanvasSpace(geometry);
235 const float scaled_size = tile_size * geometry.scaled_size.x / geometry.canvas_sz.x;
236
237 ImVec2 paint_pos = AlignToGrid(mouse_pos, scaled_size);
238 event.position = mouse_pos;
239 event.grid_position = paint_pos;
240
241 // Check for paint action
242 if (ImGui::IsMouseClicked(mouse_button)) {
243 event.is_complete = true;
244 event.is_drag = false;
245 } else if (ImGui::IsMouseDragging(mouse_button)) {
246 event.is_complete = true;
247 event.is_drag = true;
248 }
249
250 return event;
251}
252
254 const CanvasGeometry& geometry,
255 const gfx::Bitmap& bitmap,
256 float tile_size,
257 ImDrawList* draw_list,
258 ImGuiMouseButton mouse_button) {
259
260 TilePaintEvent event;
261
262 if (!IsMouseInCanvas(geometry)) {
263 return event;
264 }
265
266 const ImVec2 mouse_pos = GetMouseInCanvasSpace(geometry);
267 const float scaled_size = tile_size * geometry.scaled_size.x / geometry.canvas_sz.x;
268
269 // Calculate grid-aligned paint position
270 ImVec2 paint_pos = AlignToGrid(mouse_pos, scaled_size);
271 event.position = mouse_pos;
272 event.grid_position = paint_pos;
273
274 auto paint_pos_end = ImVec2(paint_pos.x + scaled_size, paint_pos.y + scaled_size);
275
276 // Draw preview of tile at hover position
277 if (bitmap.is_active() && draw_list) {
278 const ImVec2 origin(geometry.canvas_p0.x + geometry.scrolling.x,
279 geometry.canvas_p0.y + geometry.scrolling.y);
280 draw_list->AddImage(
281 reinterpret_cast<ImTextureID>(bitmap.texture()),
282 ImVec2(origin.x + paint_pos.x, origin.y + paint_pos.y),
283 ImVec2(origin.x + paint_pos_end.x, origin.y + paint_pos_end.y));
284 }
285
286 // Check for paint action
287 if (ImGui::IsMouseClicked(mouse_button) &&
288 ImGui::IsMouseDragging(mouse_button)) {
289 event.is_complete = true;
290 event.is_drag = true;
291 }
292
293 return event;
294}
295
297 const CanvasGeometry& geometry,
298 const gfx::Tilemap& tilemap,
299 int current_tile,
300 ImDrawList* draw_list,
301 ImGuiMouseButton mouse_button) {
302
303 TilePaintEvent event;
304 event.tile_id = current_tile;
305
306 if (!IsMouseInCanvas(geometry)) {
307 return event;
308 }
309
310 const ImVec2 mouse_pos = GetMouseInCanvasSpace(geometry);
311 const float scaled_size = 16.0f * geometry.scaled_size.x / geometry.canvas_sz.x;
312
313 ImVec2 paint_pos = AlignToGrid(mouse_pos, scaled_size);
314 event.position = mouse_pos;
315 event.grid_position = paint_pos;
316
317 // Draw preview if tilemap has texture
318 if (tilemap.atlas.is_active() && draw_list) {
319 const ImVec2 origin(geometry.canvas_p0.x + geometry.scrolling.x,
320 geometry.canvas_p0.y + geometry.scrolling.y);
321 // TODO(scawful): Render tilemap preview
322 (void)origin; // Suppress unused warning
323 }
324
325 // Check for paint action
326 if (ImGui::IsMouseDown(mouse_button)) {
327 event.is_complete = true;
328 event.is_drag = ImGui::IsMouseDragging(mouse_button);
329 }
330
331 return event;
332}
333
334// ============================================================================
335// Hover and Preview Implementation
336// ============================================================================
337
338HoverEvent HandleHover(const CanvasGeometry& geometry, float tile_size) {
339 HoverEvent event;
340
341 if (!IsMouseInCanvas(geometry)) {
342 return event;
343 }
344
345 const ImVec2 mouse_pos = GetMouseInCanvasSpace(geometry);
346 const float scaled_size = tile_size * geometry.scaled_size.x / geometry.canvas_sz.x;
347
348 event.position = mouse_pos;
349 event.grid_position = AlignToGrid(mouse_pos, scaled_size);
350 event.is_valid = true;
351
352 return event;
353}
354
356 const CanvasGeometry& geometry,
357 const HoverEvent& hover,
358 float tile_size,
359 ImDrawList* draw_list,
360 ImU32 color) {
361
362 if (!hover.is_valid || !draw_list) {
363 return;
364 }
365
366 const float scaled_size = tile_size * geometry.scaled_size.x / geometry.canvas_sz.x;
367 const ImVec2 origin(geometry.canvas_p0.x + geometry.scrolling.x,
368 geometry.canvas_p0.y + geometry.scrolling.y);
369
370 ImVec2 preview_start = ImVec2(origin.x + hover.grid_position.x,
371 origin.y + hover.grid_position.y);
372 ImVec2 preview_end = ImVec2(preview_start.x + scaled_size,
373 preview_start.y + scaled_size);
374
375 draw_list->AddRectFilled(preview_start, preview_end, color);
376}
377
378// ============================================================================
379// Entity Interaction Implementation (Stub for Phase 2.4)
380// ============================================================================
381
383 const CanvasGeometry& geometry,
384 int entity_id,
385 ImVec2 entity_position) {
386
388 event.entity_id = entity_id;
389 event.position = entity_position;
390
391 if (!IsMouseInCanvas(geometry)) {
392 return event;
393 }
394
395 // TODO(scawful): Implement entity interaction logic in Phase 2.4
396 // For now, just return empty event
397
398 return event;
399}
400
401} // namespace gui
402} // namespace yaze
403
Free functions for canvas interaction handling.
Represents a bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:66
TextureHandle texture() const
Definition bitmap.h:289
bool is_active() const
Definition bitmap.h:293
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.
Main namespace for the application.
Definition controller.cc:20
Tilemap structure for SNES tile-based graphics management.
Definition tilemap.h:109
Bitmap atlas
Master bitmap containing all tiles.
Definition tilemap.h:110
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.