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 state (keyed by canvas geometry pointer for simplicity)
21// TODO(scawful): Replace with proper state management in Phase 2.5
23
24} // namespace
25
26// ============================================================================
27// Helper Functions
28// ============================================================================
29
30ImVec2 AlignToGrid(ImVec2 pos, float grid_step) {
31 return ImVec2(std::floor(pos.x / grid_step) * grid_step,
32 std::floor(pos.y / grid_step) * grid_step);
33}
34
35ImVec2 GetMouseInCanvasSpace(const CanvasGeometry& geometry) {
36 const ImGuiIO& imgui_io = ImGui::GetIO();
37 const ImVec2 origin(geometry.canvas_p0.x + geometry.scrolling.x,
38 geometry.canvas_p0.y + geometry.scrolling.y);
39 return ImVec2(imgui_io.MousePos.x - origin.x, imgui_io.MousePos.y - origin.y);
40}
41
42bool IsMouseInCanvas(const CanvasGeometry& geometry) {
43 const ImGuiIO& imgui_io = ImGui::GetIO();
44 return imgui_io.MousePos.x >= geometry.canvas_p0.x &&
45 imgui_io.MousePos.x <= geometry.canvas_p1.x &&
46 imgui_io.MousePos.y >= geometry.canvas_p0.y &&
47 imgui_io.MousePos.y <= geometry.canvas_p1.y;
48}
49
50ImVec2 CanvasToTileGrid(ImVec2 canvas_pos, float tile_size,
51 float global_scale) {
52 const float scaled_size = tile_size * global_scale;
53 return ImVec2(std::floor(canvas_pos.x / scaled_size),
54 std::floor(canvas_pos.y / scaled_size));
55}
56
57// ============================================================================
58// Rectangle Selection Implementation
59// ============================================================================
60
62 int current_map, float tile_size,
63 ImDrawList* draw_list,
64 ImGuiMouseButton mouse_button) {
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 =
74 tile_size * geometry.scaled_size.x / geometry.canvas_sz.x;
75 constexpr int kSmallMapSize = 0x200; // 512 pixels
76
77 // Calculate super X/Y accounting for world offset
78 int super_y = 0;
79 int super_x = 0;
80 if (current_map < 0x40) {
81 // Light World (0x00-0x3F)
82 super_y = current_map / 8;
83 super_x = current_map % 8;
84 } else if (current_map < 0x80) {
85 // Dark World (0x40-0x7F)
86 super_y = (current_map - 0x40) / 8;
87 super_x = (current_map - 0x40) % 8;
88 } else {
89 // Special World (0x80+)
90 super_y = (current_map - 0x80) / 8;
91 super_x = (current_map - 0x80) % 8;
92 }
93
94 // Handle mouse button press - start selection
95 if (ImGui::IsMouseClicked(mouse_button)) {
96 g_select_rect_state.drag_start_pos = AlignToGrid(mouse_pos, scaled_size);
97 g_select_rect_state.is_dragging = false;
98
99 // Single tile selection on click
100 ImVec2 painter_pos = AlignToGrid(mouse_pos, scaled_size);
101 int painter_x = static_cast<int>(painter_pos.x);
102 int painter_y = static_cast<int>(painter_pos.y);
103
104 auto tile16_x = (painter_x % kSmallMapSize) / (kSmallMapSize / 0x20);
105 auto tile16_y = (painter_y % kSmallMapSize) / (kSmallMapSize / 0x20);
106
107 int index_x = super_x * 0x20 + tile16_x;
108 int index_y = super_y * 0x20 + tile16_y;
109
110 event.start_pos = painter_pos;
111 event.selected_tiles.push_back(
112 ImVec2(static_cast<float>(index_x), static_cast<float>(index_y)));
113 }
114
115 // Handle dragging - draw preview rectangle
116 ImVec2 drag_end_pos = AlignToGrid(mouse_pos, scaled_size);
117 if (ImGui::IsMouseDragging(mouse_button) && draw_list) {
118 g_select_rect_state.is_dragging = true;
119 event.is_active = true;
120 event.start_pos = g_select_rect_state.drag_start_pos;
121 event.end_pos = drag_end_pos;
122
123 // Draw preview rectangle
124 auto start =
125 ImVec2(geometry.canvas_p0.x + g_select_rect_state.drag_start_pos.x,
126 geometry.canvas_p0.y + g_select_rect_state.drag_start_pos.y);
127 auto end = ImVec2(geometry.canvas_p0.x + drag_end_pos.x + tile_size,
128 geometry.canvas_p0.y + drag_end_pos.y + tile_size);
129 draw_list->AddRect(start, end, IM_COL32(255, 255, 255, 255));
130 }
131
132 // Handle mouse release - complete selection
133 if (g_select_rect_state.is_dragging && !ImGui::IsMouseDown(mouse_button)) {
134 g_select_rect_state.is_dragging = false;
135 event.is_complete = true;
136 event.is_active = false;
137 event.start_pos = g_select_rect_state.drag_start_pos;
138 event.end_pos = drag_end_pos;
139
140 // Calculate selected tiles
141 constexpr int kTile16Size = 16;
142 int start_x = static_cast<int>(std::floor(
143 g_select_rect_state.drag_start_pos.x / scaled_size)) *
144 kTile16Size;
145 int start_y = static_cast<int>(std::floor(
146 g_select_rect_state.drag_start_pos.y / scaled_size)) *
147 kTile16Size;
148 int end_x = static_cast<int>(std::floor(drag_end_pos.x / scaled_size)) *
149 kTile16Size;
150 int end_y = static_cast<int>(std::floor(drag_end_pos.y / scaled_size)) *
151 kTile16Size;
152
153 if (start_x > end_x)
154 std::swap(start_x, end_x);
155 if (start_y > end_y)
156 std::swap(start_y, end_y);
157
158 constexpr int kTilesPerLocalMap = kSmallMapSize / 16;
159
160 for (int tile_y = start_y; tile_y <= end_y; tile_y += kTile16Size) {
161 for (int tile_x = start_x; tile_x <= end_x; tile_x += kTile16Size) {
162 int local_map_x = tile_x / kSmallMapSize;
163 int local_map_y = tile_y / kSmallMapSize;
164
165 int tile16_x = (tile_x % kSmallMapSize) / kTile16Size;
166 int tile16_y = (tile_y % kSmallMapSize) / kTile16Size;
167
168 int index_x = local_map_x * kTilesPerLocalMap + tile16_x;
169 int index_y = local_map_y * kTilesPerLocalMap + tile16_y;
170
171 event.selected_tiles.emplace_back(static_cast<float>(index_x),
172 static_cast<float>(index_y));
173 }
174 }
175 }
176
177 return event;
178}
179
181 int current_map, float tile_size,
182 ImGuiMouseButton mouse_button) {
183 TileSelectionEvent event;
184
185 if (!IsMouseInCanvas(geometry) || !ImGui::IsMouseClicked(mouse_button)) {
186 return event;
187 }
188
189 const ImVec2 mouse_pos = GetMouseInCanvasSpace(geometry);
190 const float scaled_size =
191 tile_size * geometry.scaled_size.x / geometry.canvas_sz.x;
192 constexpr int kSmallMapSize = 0x200;
193
194 // Calculate super X/Y
195 int super_y = 0;
196 int super_x = 0;
197 if (current_map < 0x40) {
198 super_y = current_map / 8;
199 super_x = current_map % 8;
200 } else if (current_map < 0x80) {
201 super_y = (current_map - 0x40) / 8;
202 super_x = (current_map - 0x40) % 8;
203 } else {
204 super_y = (current_map - 0x80) / 8;
205 super_x = (current_map - 0x80) % 8;
206 }
207
208 ImVec2 painter_pos = AlignToGrid(mouse_pos, scaled_size);
209 int painter_x = static_cast<int>(painter_pos.x);
210 int painter_y = static_cast<int>(painter_pos.y);
211
212 auto tile16_x = (painter_x % kSmallMapSize) / (kSmallMapSize / 0x20);
213 auto tile16_y = (painter_y % kSmallMapSize) / (kSmallMapSize / 0x20);
214
215 int index_x = super_x * 0x20 + tile16_x;
216 int index_y = super_y * 0x20 + tile16_y;
217
218 event.tile_position =
219 ImVec2(static_cast<float>(index_x), static_cast<float>(index_y));
220 event.is_valid = true;
221
222 return event;
223}
224
225// ============================================================================
226// Tile Painting Implementation
227// ============================================================================
228
229TilePaintEvent HandleTilePaint(const CanvasGeometry& geometry, int tile_id,
230 float tile_size, ImGuiMouseButton mouse_button) {
231 TilePaintEvent event;
232 event.tile_id = tile_id;
233
234 if (!IsMouseInCanvas(geometry)) {
235 return event;
236 }
237
238 const ImVec2 mouse_pos = GetMouseInCanvasSpace(geometry);
239 const float scaled_size =
240 tile_size * geometry.scaled_size.x / geometry.canvas_sz.x;
241
242 ImVec2 paint_pos = AlignToGrid(mouse_pos, scaled_size);
243 event.position = mouse_pos;
244 event.grid_position = paint_pos;
245
246 // Check for paint action
247 if (ImGui::IsMouseClicked(mouse_button)) {
248 event.is_complete = true;
249 event.is_drag = false;
250 } else if (ImGui::IsMouseDragging(mouse_button)) {
251 event.is_complete = true;
252 event.is_drag = true;
253 }
254
255 return event;
256}
257
259 const gfx::Bitmap& bitmap,
260 float tile_size,
261 ImDrawList* draw_list,
262 ImGuiMouseButton mouse_button) {
263 TilePaintEvent event;
264
265 if (!IsMouseInCanvas(geometry)) {
266 return event;
267 }
268
269 const ImVec2 mouse_pos = GetMouseInCanvasSpace(geometry);
270 const float scaled_size =
271 tile_size * geometry.scaled_size.x / geometry.canvas_sz.x;
272
273 // Calculate grid-aligned paint position
274 ImVec2 paint_pos = AlignToGrid(mouse_pos, scaled_size);
275 event.position = mouse_pos;
276 event.grid_position = paint_pos;
277
278 auto paint_pos_end =
279 ImVec2(paint_pos.x + scaled_size, paint_pos.y + scaled_size);
280
281 // Draw preview of tile at hover position
282 if (bitmap.is_active() && draw_list) {
283 const ImVec2 origin(geometry.canvas_p0.x + geometry.scrolling.x,
284 geometry.canvas_p0.y + geometry.scrolling.y);
285 draw_list->AddImage(
286 reinterpret_cast<ImTextureID>(bitmap.texture()),
287 ImVec2(origin.x + paint_pos.x, origin.y + paint_pos.y),
288 ImVec2(origin.x + paint_pos_end.x, origin.y + paint_pos_end.y));
289 }
290
291 // Check for paint action
292 if (ImGui::IsMouseClicked(mouse_button) &&
293 ImGui::IsMouseDragging(mouse_button)) {
294 event.is_complete = true;
295 event.is_drag = true;
296 }
297
298 return event;
299}
300
302 const gfx::Tilemap& tilemap, int current_tile,
303 ImDrawList* draw_list,
304 ImGuiMouseButton mouse_button) {
305 TilePaintEvent event;
306 event.tile_id = current_tile;
307
308 if (!IsMouseInCanvas(geometry)) {
309 return event;
310 }
311
312 const ImVec2 mouse_pos = GetMouseInCanvasSpace(geometry);
313 const float scaled_size =
314 16.0f * geometry.scaled_size.x / geometry.canvas_sz.x;
315
316 ImVec2 paint_pos = AlignToGrid(mouse_pos, scaled_size);
317 event.position = mouse_pos;
318 event.grid_position = paint_pos;
319
320 // Draw preview if tilemap has texture
321 if (tilemap.atlas.is_active() && draw_list) {
322 const ImVec2 origin(geometry.canvas_p0.x + geometry.scrolling.x,
323 geometry.canvas_p0.y + geometry.scrolling.y);
324 // TODO(scawful): Render tilemap preview
325 (void)origin; // Suppress unused warning
326 }
327
328 // Check for paint action
329 if (ImGui::IsMouseDown(mouse_button)) {
330 event.is_complete = true;
331 event.is_drag = ImGui::IsMouseDragging(mouse_button);
332 }
333
334 return event;
335}
336
337// ============================================================================
338// Hover and Preview Implementation
339// ============================================================================
340
341HoverEvent HandleHover(const CanvasGeometry& geometry, float tile_size) {
342 HoverEvent event;
343
344 if (!IsMouseInCanvas(geometry)) {
345 return event;
346 }
347
348 const ImVec2 mouse_pos = GetMouseInCanvasSpace(geometry);
349 const float scaled_size =
350 tile_size * geometry.scaled_size.x / geometry.canvas_sz.x;
351
352 event.position = mouse_pos;
353 event.grid_position = AlignToGrid(mouse_pos, scaled_size);
354 event.is_valid = true;
355
356 return event;
357}
358
359void RenderHoverPreview(const CanvasGeometry& geometry, const HoverEvent& hover,
360 float tile_size, ImDrawList* draw_list, ImU32 color) {
361 if (!hover.is_valid || !draw_list) {
362 return;
363 }
364
365 const float scaled_size =
366 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 =
373 ImVec2(preview_start.x + scaled_size, 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 int entity_id,
384 ImVec2 entity_position) {
386 event.entity_id = entity_id;
387 event.position = entity_position;
388
389 if (!IsMouseInCanvas(geometry)) {
390 return event;
391 }
392
393 // TODO(scawful): Implement entity interaction logic in Phase 2.4
394 // For now, just return empty event
395
396 return event;
397}
398
399} // namespace gui
400} // 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:380
bool is_active() const
Definition bitmap.h:384
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
Bitmap atlas
Master bitmap containing all tiles.
Definition tilemap.h:119
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.