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
7#include "imgui/imgui.h"
8
9namespace yaze {
10namespace gui {
11
12namespace {
13
14// Static state for rectangle selection (temporary until we have proper state
15// management)
17 ImVec2 drag_start_pos = ImVec2(-1, -1);
18 bool is_dragging = false;
19};
20
21// Per-canvas rectangle selection drag state (thread-local for tests).
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 ScopedNearestSampling nearest_sampling(draw_list);
286 draw_list->AddImage(
287 reinterpret_cast<ImTextureID>(bitmap.texture()),
288 ImVec2(origin.x + paint_pos.x, origin.y + paint_pos.y),
289 ImVec2(origin.x + paint_pos_end.x, origin.y + paint_pos_end.y),
290 ImVec2(0, 0), ImVec2(1, 1), ImGui::GetColorU32(ImVec4(1, 1, 1, 1)));
291 }
292
293 // Check for paint action
294 if (ImGui::IsMouseClicked(mouse_button) &&
295 ImGui::IsMouseDragging(mouse_button)) {
296 event.is_complete = true;
297 event.is_drag = true;
298 }
299
300 return event;
301}
302
304 const gfx::Tilemap& tilemap, int current_tile,
305 ImDrawList* draw_list,
306 ImGuiMouseButton mouse_button) {
307 TilePaintEvent event;
308 event.tile_id = current_tile;
309
310 if (!IsMouseInCanvas(geometry)) {
311 return event;
312 }
313
314 const ImVec2 mouse_pos = GetMouseInCanvasSpace(geometry);
315 const float scaled_size =
316 16.0f * geometry.scaled_size.x / geometry.canvas_sz.x;
317
318 ImVec2 paint_pos = AlignToGrid(mouse_pos, scaled_size);
319 event.position = mouse_pos;
320 event.grid_position = paint_pos;
321
322 (void)tilemap;
323 (void)draw_list;
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 =
347 tile_size * geometry.scaled_size.x / geometry.canvas_sz.x;
348
349 event.position = mouse_pos;
350 event.grid_position = AlignToGrid(mouse_pos, scaled_size);
351 event.is_valid = true;
352
353 return event;
354}
355
356void RenderHoverPreview(const CanvasGeometry& geometry, const HoverEvent& hover,
357 float tile_size, ImDrawList* draw_list, ImU32 color) {
358 if (!hover.is_valid || !draw_list) {
359 return;
360 }
361
362 const float scaled_size =
363 tile_size * geometry.scaled_size.x / geometry.canvas_sz.x;
364 const ImVec2 origin(geometry.canvas_p0.x + geometry.scrolling.x,
365 geometry.canvas_p0.y + geometry.scrolling.y);
366
367 ImVec2 preview_start = ImVec2(origin.x + hover.grid_position.x,
368 origin.y + hover.grid_position.y);
369 ImVec2 preview_end =
370 ImVec2(preview_start.x + scaled_size, preview_start.y + scaled_size);
371
372 draw_list->AddRectFilled(preview_start, preview_end, color);
373}
374
375// ============================================================================
376// Entity Interaction Implementation (Stub for Phase 2.4)
377// ============================================================================
378
380 int entity_id,
381 ImVec2 entity_position) {
383 event.entity_id = entity_id;
384 event.position = entity_position;
385
386 if (!IsMouseInCanvas(geometry)) {
387 return event;
388 }
389
390 return event;
391}
392
393} // namespace gui
394} // namespace yaze
Free functions for canvas interaction handling.
Represents a bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:70
TextureHandle texture() const
Definition bitmap.h:410
bool is_active() const
Definition bitmap.h:414
RAII scope that samples the images drawn inside it with nearest-neighbor filtering.
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.