yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
canvas_interaction_handler.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 {
9namespace canvas {
10
11namespace {
12
13// Helper function to align position to grid
14ImVec2 AlignToGrid(ImVec2 pos, float grid_step) {
15 return ImVec2(std::floor(pos.x / grid_step) * grid_step,
16 std::floor(pos.y / grid_step) * grid_step);
17}
18
19} // namespace
20
21void CanvasInteractionHandler::Initialize(const std::string& canvas_id) {
22 canvas_id_ = canvas_id;
23 ClearState();
24}
25
27 hover_points_.clear();
28 selected_points_.clear();
29 selected_tiles_.clear();
30 drawn_tile_pos_ = ImVec2(-1, -1);
31 mouse_pos_in_canvas_ = ImVec2(0, 0);
32 selected_tile_pos_ = ImVec2(-1, -1);
33 rect_select_active_ = false;
34}
35
37 ImVec2 canvas_p0, ImVec2 scrolling, float /*global_scale*/, float /*tile_size*/,
38 ImVec2 /*canvas_size*/, bool is_hovered) {
39
41
42 if (!is_hovered) {
43 hover_points_.clear();
44 return result;
45 }
46
47 const ImGuiIO& imgui_io = ImGui::GetIO();
48 const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y);
49 mouse_pos_in_canvas_ = ImVec2(imgui_io.MousePos.x - origin.x, imgui_io.MousePos.y - origin.y);
50
51 // Update based on current mode - each mode is handled by its specific Draw method
52 // This method exists for future state updates if needed
53 (void)current_mode_; // Suppress unused warning
54
55 return result;
56}
57
59 const gfx::Bitmap& bitmap, ImDrawList* draw_list, ImVec2 canvas_p0,
60 ImVec2 scrolling, float global_scale, float tile_size, bool is_hovered) {
61
62 const ImGuiIO& imgui_io = ImGui::GetIO();
63 const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y);
64 const ImVec2 mouse_pos(imgui_io.MousePos.x - origin.x, imgui_io.MousePos.y - origin.y);
65 const auto scaled_size = tile_size * global_scale;
66
67 // Clear hover when not hovering
68 if (!is_hovered) {
69 hover_points_.clear();
70 return false;
71 }
72
73 // Reset previous hover
74 hover_points_.clear();
75
76 // Calculate grid-aligned paint position
77 ImVec2 paint_pos = AlignToGrid(mouse_pos, scaled_size);
78 mouse_pos_in_canvas_ = paint_pos;
79 auto paint_pos_end = ImVec2(paint_pos.x + scaled_size, paint_pos.y + scaled_size);
80
81 hover_points_.push_back(paint_pos);
82 hover_points_.push_back(paint_pos_end);
83
84 // Draw preview of tile at hover position
85 if (bitmap.is_active() && draw_list) {
86 draw_list->AddImage(
87 (ImTextureID)(intptr_t)bitmap.texture(),
88 ImVec2(origin.x + paint_pos.x, origin.y + paint_pos.y),
89 ImVec2(origin.x + paint_pos.x + scaled_size, origin.y + paint_pos.y + scaled_size));
90 }
91
92 // Check for paint action
93 if (ImGui::IsMouseClicked(ImGuiMouseButton_Left) &&
94 ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
95 drawn_tile_pos_ = paint_pos;
96 return true;
97 }
98
99 return false;
100}
101
103 gfx::Tilemap& tilemap, int current_tile, ImDrawList* draw_list,
104 ImVec2 canvas_p0, ImVec2 scrolling, float global_scale, bool is_hovered) {
105
106 const ImGuiIO& imgui_io = ImGui::GetIO();
107 const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y);
108 const ImVec2 mouse_pos(imgui_io.MousePos.x - origin.x, imgui_io.MousePos.y - origin.y);
109
110 // Safety check
111 if (!tilemap.atlas.is_active() || tilemap.tile_size.x <= 0) {
112 return false;
113 }
114
115 const auto scaled_size = tilemap.tile_size.x * global_scale;
116
117 if (!is_hovered) {
118 hover_points_.clear();
119 return false;
120 }
121
122 hover_points_.clear();
123
124 ImVec2 paint_pos = AlignToGrid(mouse_pos, scaled_size);
125 mouse_pos_in_canvas_ = paint_pos;
126
127 hover_points_.push_back(paint_pos);
128 hover_points_.push_back(ImVec2(paint_pos.x + scaled_size, paint_pos.y + scaled_size));
129
130 // Draw tile preview from atlas
131 if (tilemap.atlas.is_active() && tilemap.atlas.texture() && draw_list) {
132 int tiles_per_row = tilemap.atlas.width() / tilemap.tile_size.x;
133 if (tiles_per_row > 0) {
134 int tile_x = (current_tile % tiles_per_row) * tilemap.tile_size.x;
135 int tile_y = (current_tile / tiles_per_row) * tilemap.tile_size.y;
136
137 if (tile_x >= 0 && tile_x < tilemap.atlas.width() &&
138 tile_y >= 0 && tile_y < tilemap.atlas.height()) {
139
140 ImVec2 uv0 = ImVec2(static_cast<float>(tile_x) / tilemap.atlas.width(),
141 static_cast<float>(tile_y) / tilemap.atlas.height());
142 ImVec2 uv1 = ImVec2(static_cast<float>(tile_x + tilemap.tile_size.x) / tilemap.atlas.width(),
143 static_cast<float>(tile_y + tilemap.tile_size.y) / tilemap.atlas.height());
144
145 draw_list->AddImage(
146 (ImTextureID)(intptr_t)tilemap.atlas.texture(),
147 ImVec2(origin.x + paint_pos.x, origin.y + paint_pos.y),
148 ImVec2(origin.x + paint_pos.x + scaled_size, origin.y + paint_pos.y + scaled_size),
149 uv0, uv1);
150 }
151 }
152 }
153
154 if (ImGui::IsMouseClicked(ImGuiMouseButton_Left) ||
155 ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
156 drawn_tile_pos_ = paint_pos;
157 return true;
158 }
159
160 return false;
161}
162
164 const ImVec4& color, ImDrawList* draw_list, ImVec2 canvas_p0,
165 ImVec2 scrolling, float global_scale, float tile_size, bool is_hovered) {
166
167 const ImGuiIO& imgui_io = ImGui::GetIO();
168 const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y);
169 const ImVec2 mouse_pos(imgui_io.MousePos.x - origin.x, imgui_io.MousePos.y - origin.y);
170 auto scaled_tile_size = tile_size * global_scale;
171 static bool is_dragging = false;
172 static ImVec2 start_drag_pos;
173
174 if (!is_hovered) {
175 hover_points_.clear();
176 return false;
177 }
178
179 hover_points_.clear();
180
181 ImVec2 paint_pos = AlignToGrid(mouse_pos, scaled_tile_size);
182 mouse_pos_in_canvas_ = paint_pos;
183
184 // Clamp to canvas bounds (assuming canvas_size from Update)
185 // For now, skip clamping as we don't have canvas_size here
186
187 hover_points_.push_back(paint_pos);
188 hover_points_.push_back(ImVec2(paint_pos.x + scaled_tile_size, paint_pos.y + scaled_tile_size));
189
190 if (draw_list) {
191 draw_list->AddRectFilled(
192 ImVec2(origin.x + paint_pos.x + 1, origin.y + paint_pos.y + 1),
193 ImVec2(origin.x + paint_pos.x + scaled_tile_size,
194 origin.y + paint_pos.y + scaled_tile_size),
195 IM_COL32(color.x * 255, color.y * 255, color.z * 255, 255));
196 }
197
198 if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
199 is_dragging = true;
200 start_drag_pos = paint_pos;
201 }
202
203 if (is_dragging && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
204 is_dragging = false;
205 drawn_tile_pos_ = start_drag_pos;
206 return true;
207 }
208
209 return false;
210}
211
213 ImDrawList* /*draw_list*/, ImVec2 canvas_p0, ImVec2 scrolling, float tile_size,
214 bool is_hovered) {
215
216 const ImGuiIO& imgui_io = ImGui::GetIO();
217 const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y);
218 const ImVec2 mouse_pos(imgui_io.MousePos.x - origin.x, imgui_io.MousePos.y - origin.y);
219
220 if (is_hovered && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
221 hover_points_.clear();
222 ImVec2 painter_pos = AlignToGrid(mouse_pos, tile_size);
223
224 hover_points_.push_back(painter_pos);
225 hover_points_.push_back(ImVec2(painter_pos.x + tile_size, painter_pos.y + tile_size));
226 mouse_pos_in_canvas_ = painter_pos;
227 }
228
229 if (is_hovered && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
230 return true;
231 }
232
233 return false;
234}
235
237 int current_map, ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling,
238 float global_scale, float tile_size, bool is_hovered) {
239
240 const ImGuiIO& imgui_io = ImGui::GetIO();
241 const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y);
242 const ImVec2 mouse_pos(imgui_io.MousePos.x - origin.x, imgui_io.MousePos.y - origin.y);
243 const float scaled_size = tile_size * global_scale;
244 static ImVec2 drag_start_pos;
245 static bool dragging = false;
246 constexpr int small_map_size = 0x200;
247
248 if (!is_hovered) {
249 return false;
250 }
251
252 // Calculate superX and superY accounting for world offset
253 int super_y = 0;
254 int super_x = 0;
255 if (current_map < 0x40) {
256 super_y = current_map / 8;
257 super_x = current_map % 8;
258 } else if (current_map < 0x80) {
259 super_y = (current_map - 0x40) / 8;
260 super_x = (current_map - 0x40) % 8;
261 } else {
262 super_y = (current_map - 0x80) / 8;
263 super_x = (current_map - 0x80) % 8;
264 }
265
266 // Handle right click for single tile selection
267 if (ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
268 ImVec2 painter_pos = AlignToGrid(mouse_pos, scaled_size);
269 int painter_x = painter_pos.x;
270 int painter_y = painter_pos.y;
271
272 auto tile16_x = (painter_x % small_map_size) / (small_map_size / 0x20);
273 auto tile16_y = (painter_y % small_map_size) / (small_map_size / 0x20);
274
275 int index_x = super_x * 0x20 + tile16_x;
276 int index_y = super_y * 0x20 + tile16_y;
277 selected_tile_pos_ = ImVec2(index_x, index_y);
278 selected_points_.clear();
279 rect_select_active_ = false;
280
281 drag_start_pos = AlignToGrid(mouse_pos, scaled_size);
282 }
283
284 // Draw rectangle while dragging
285 ImVec2 drag_end_pos = AlignToGrid(mouse_pos, scaled_size);
286 if (ImGui::IsMouseDragging(ImGuiMouseButton_Right) && draw_list) {
287 auto start = ImVec2(canvas_p0.x + drag_start_pos.x,
288 canvas_p0.y + drag_start_pos.y);
289 auto end = ImVec2(canvas_p0.x + drag_end_pos.x + tile_size,
290 canvas_p0.y + drag_end_pos.y + tile_size);
291 draw_list->AddRect(start, end, IM_COL32(255, 255, 255, 255));
292 dragging = true;
293 }
294
295 // Complete selection on release
296 if (dragging && !ImGui::IsMouseDown(ImGuiMouseButton_Right)) {
297 dragging = false;
298
299 constexpr int tile16_size = 16;
300 int start_x = std::floor(drag_start_pos.x / scaled_size) * tile16_size;
301 int start_y = std::floor(drag_start_pos.y / scaled_size) * tile16_size;
302 int end_x = std::floor(drag_end_pos.x / scaled_size) * tile16_size;
303 int end_y = std::floor(drag_end_pos.y / scaled_size) * tile16_size;
304
305 if (start_x > end_x) std::swap(start_x, end_x);
306 if (start_y > end_y) std::swap(start_y, end_y);
307
308 selected_tiles_.clear();
309 selected_tiles_.reserve(((end_x - start_x) / tile16_size + 1) *
310 ((end_y - start_y) / tile16_size + 1));
311
312 constexpr int tiles_per_local_map = small_map_size / 16;
313
314 for (int tile_y = start_y; tile_y <= end_y; tile_y += tile16_size) {
315 for (int tile_x = start_x; tile_x <= end_x; tile_x += tile16_size) {
316 int local_map_x = tile_x / small_map_size;
317 int local_map_y = tile_y / small_map_size;
318
319 int tile16_x = (tile_x % small_map_size) / tile16_size;
320 int tile16_y = (tile_y % small_map_size) / tile16_size;
321
322 int index_x = local_map_x * tiles_per_local_map + tile16_x;
323 int index_y = local_map_y * tiles_per_local_map + tile16_y;
324
325 selected_tiles_.emplace_back(index_x, index_y);
326 }
327 }
328
329 selected_points_.clear();
330 selected_points_.push_back(drag_start_pos);
331 selected_points_.push_back(drag_end_pos);
332 rect_select_active_ = true;
333 return true;
334 }
335
336 return false;
337}
338
339// Helper methods - these are thin wrappers that could be static but kept as instance
340// methods for potential future state access
341ImVec2 CanvasInteractionHandler::AlignPosToGrid(ImVec2 pos, float grid_step) {
342 return AlignToGrid(pos, grid_step);
343}
344
345ImVec2 CanvasInteractionHandler::GetMousePosition(ImVec2 canvas_p0, ImVec2 scrolling) {
346 const ImGuiIO& imgui_io = ImGui::GetIO();
347 const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y);
348 return ImVec2(imgui_io.MousePos.x - origin.x, imgui_io.MousePos.y - origin.y);
349}
350
351bool CanvasInteractionHandler::IsMouseClicked(ImGuiMouseButton button) {
352 return ImGui::IsMouseClicked(button);
353}
354
356 return ImGui::IsMouseDoubleClicked(button);
357}
358
359bool CanvasInteractionHandler::IsMouseDragging(ImGuiMouseButton button) {
360 return ImGui::IsMouseDragging(button);
361}
362
363bool CanvasInteractionHandler::IsMouseReleased(ImGuiMouseButton button) {
364 return ImGui::IsMouseReleased(button);
365}
366
367} // namespace canvas
368} // namespace gui
369} // namespace yaze
Represents a bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:66
TextureHandle texture() const
Definition bitmap.h:260
bool is_active() const
Definition bitmap.h:264
int height() const
Definition bitmap.h:254
int width() const
Definition bitmap.h:253
TileInteractionResult Update(ImVec2 canvas_p0, ImVec2 scrolling, float global_scale, float tile_size, ImVec2 canvas_size, bool is_hovered)
Update interaction state (call once per frame)
ImVec2 GetMousePosition(ImVec2 canvas_p0, ImVec2 scrolling)
bool DrawTilePainter(const gfx::Bitmap &bitmap, ImDrawList *draw_list, ImVec2 canvas_p0, ImVec2 scrolling, float global_scale, float tile_size, bool is_hovered)
Draw tile painter (preview + interaction)
bool DrawTilemapPainter(gfx::Tilemap &tilemap, int current_tile, ImDrawList *draw_list, ImVec2 canvas_p0, ImVec2 scrolling, float global_scale, bool is_hovered)
Draw tilemap painter (preview + interaction)
bool DrawSelectRect(int current_map, ImDrawList *draw_list, ImVec2 canvas_p0, ImVec2 scrolling, float global_scale, float tile_size, bool is_hovered)
Draw rectangle selector (multi-tile selection)
bool DrawSolidTilePainter(const ImVec4 &color, ImDrawList *draw_list, ImVec2 canvas_p0, ImVec2 scrolling, float global_scale, float tile_size, bool is_hovered)
Draw solid color painter.
void Initialize(const std::string &canvas_id)
Initialize the interaction handler.
bool DrawTileSelector(ImDrawList *draw_list, ImVec2 canvas_p0, ImVec2 scrolling, float tile_size, bool is_hovered)
Draw tile selector (single tile selection)
Main namespace for the application.
int y
Y coordinate or height.
Definition tilemap.h:20
int x
X coordinate or width.
Definition tilemap.h:19
Tilemap structure for SNES tile-based graphics management.
Definition tilemap.h:109
Pair tile_size
Size of individual tiles (8x8 or 16x16)
Definition tilemap.h:113
Bitmap atlas
Master bitmap containing all tiles.
Definition tilemap.h:110
Result of a tile interaction operation.