yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
canvas_utils.cc
Go to the documentation of this file.
1#include "canvas_utils.h"
2
3#include <algorithm>
4#include <cmath>
5
8#include "util/log.h"
9
10namespace yaze {
11namespace gui {
12namespace CanvasUtils {
13
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
19float CalculateEffectiveScale(ImVec2 canvas_size, ImVec2 content_size,
20 float global_scale) {
21 if (content_size.x <= 0 || content_size.y <= 0)
22 return global_scale;
23
24 float scale_x = (canvas_size.x * global_scale) / content_size.x;
25 float scale_y = (canvas_size.y * global_scale) / content_size.y;
26 return std::min(scale_x, scale_y);
27}
28
29int GetTileIdFromPosition(ImVec2 mouse_pos, float tile_size, float scale,
30 int tiles_per_row) {
31 float scaled_tile_size = tile_size * scale;
32 int tile_x = static_cast<int>(mouse_pos.x / scaled_tile_size);
33 int tile_y = static_cast<int>(mouse_pos.y / scaled_tile_size);
34
35 return tile_x + (tile_y * tiles_per_row);
36}
37
39 CanvasPaletteManager& palette_manager) {
40 if (!game_data || palette_manager.palettes_loaded) {
41 return palette_manager.palettes_loaded;
42 }
43
44 try {
45 const auto& palette_groups = game_data->palette_groups;
46 palette_manager.rom_palette_groups.clear();
47 palette_manager.palette_group_names.clear();
48
49 // Overworld palettes
50 if (palette_groups.overworld_main.size() > 0) {
51 palette_manager.rom_palette_groups.push_back(
52 palette_groups.overworld_main[0]);
53 palette_manager.palette_group_names.push_back("Overworld Main");
54 }
55 if (palette_groups.overworld_aux.size() > 0) {
56 palette_manager.rom_palette_groups.push_back(
57 palette_groups.overworld_aux[0]);
58 palette_manager.palette_group_names.push_back("Overworld Aux");
59 }
60 if (palette_groups.overworld_animated.size() > 0) {
61 palette_manager.rom_palette_groups.push_back(
62 palette_groups.overworld_animated[0]);
63 palette_manager.palette_group_names.push_back("Overworld Animated");
64 }
65
66 // Dungeon palettes
67 if (palette_groups.dungeon_main.size() > 0) {
68 palette_manager.rom_palette_groups.push_back(
69 palette_groups.dungeon_main[0]);
70 palette_manager.palette_group_names.push_back("Dungeon Main");
71 }
72
73 // Sprite palettes
74 if (palette_groups.global_sprites.size() > 0) {
75 palette_manager.rom_palette_groups.push_back(
76 palette_groups.global_sprites[0]);
77 palette_manager.palette_group_names.push_back("Global Sprites");
78 }
79 if (palette_groups.armors.size() > 0) {
80 palette_manager.rom_palette_groups.push_back(palette_groups.armors[0]);
81 palette_manager.palette_group_names.push_back("Armor");
82 }
83 if (palette_groups.swords.size() > 0) {
84 palette_manager.rom_palette_groups.push_back(palette_groups.swords[0]);
85 palette_manager.palette_group_names.push_back("Swords");
86 }
87
88 palette_manager.palettes_loaded = true;
89 LOG_DEBUG("Canvas", "Loaded %zu ROM palette groups",
90 palette_manager.rom_palette_groups.size());
91 return true;
92
93 } catch (const std::exception& e) {
94 LOG_ERROR("Canvas", "Failed to load ROM palette groups");
95 return false;
96 }
97}
98
100 CanvasPaletteManager& palette_manager, int group_index,
101 int palette_index) {
102 if (!bitmap)
103 return false;
104
105 if (group_index < 0 ||
106 group_index >= palette_manager.rom_palette_groups.size()) {
107 return false;
108 }
109
110 const auto& palette = palette_manager.rom_palette_groups[group_index];
111
112 // Apply the full palette or use SetPaletteWithTransparent if palette_index is
113 // specified
114 if (palette_index == 0) {
115 bitmap->SetPalette(palette);
116 } else {
117 bitmap->SetPaletteWithTransparent(palette, palette_index);
118 }
119 bitmap->set_modified(true);
120 palette_manager.palette_dirty = true;
121
122 // Queue texture update only if live_update is enabled
123 if (palette_manager.live_update_enabled && renderer) {
126 palette_manager.palette_dirty = false; // Clear dirty flag after update
127 }
128 return true;
129}
130
131// Drawing utility functions
132void DrawCanvasRect(ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling,
133 int x, int y, int w, int h, ImVec4 color,
134 float global_scale) {
135 // Apply global scale to position and size
136 float scaled_x = x * global_scale;
137 float scaled_y = y * global_scale;
138 float scaled_w = w * global_scale;
139 float scaled_h = h * global_scale;
140
141 ImVec2 origin(canvas_p0.x + scrolling.x + scaled_x,
142 canvas_p0.y + scrolling.y + scaled_y);
143 ImVec2 size(canvas_p0.x + scrolling.x + scaled_x + scaled_w,
144 canvas_p0.y + scrolling.y + scaled_y + scaled_h);
145
146 uint32_t color_u32 =
147 IM_COL32(color.x * 255, color.y * 255, color.z * 255, color.w * 255);
148 draw_list->AddRectFilled(origin, size, color_u32);
149
150 // Add a black outline
151 ImVec2 outline_origin(origin.x - 1, origin.y - 1);
152 ImVec2 outline_size(size.x + 1, size.y + 1);
153 draw_list->AddRect(outline_origin, outline_size, IM_COL32(0, 0, 0, 255));
154}
155
156void DrawCanvasText(ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling,
157 const std::string& text, int x, int y, float global_scale) {
158 // Apply global scale to text position
159 float scaled_x = x * global_scale;
160 float scaled_y = y * global_scale;
161
162 ImVec2 text_pos(canvas_p0.x + scrolling.x + scaled_x,
163 canvas_p0.y + scrolling.y + scaled_y);
164
165 // White text with a 1px black outline. A single drop shadow left labels
166 // hard to read on light marker boxes (cyan-white exits, yellow entrances).
167 constexpr ImU32 kOutline = IM_COL32(0, 0, 0, 255);
168 for (const ImVec2 offset : {ImVec2(-1, 0), ImVec2(1, 0), ImVec2(0, -1),
169 ImVec2(0, 1), ImVec2(1, 1)}) {
170 draw_list->AddText(ImVec2(text_pos.x + offset.x, text_pos.y + offset.y),
171 kOutline, text.c_str());
172 }
173 draw_list->AddText(text_pos, IM_COL32(255, 255, 255, 255), text.c_str());
174}
175
176void DrawCanvasOutline(ImDrawList* draw_list, ImVec2 canvas_p0,
177 ImVec2 scrolling, int x, int y, int w, int h,
178 uint32_t color, float global_scale) {
179 const float scale = global_scale > 0.0f ? global_scale : 1.0f;
180 const float scaled_x = static_cast<float>(x) * scale;
181 const float scaled_y = static_cast<float>(y) * scale;
182 const float scaled_w = static_cast<float>(w) * scale;
183 const float scaled_h = static_cast<float>(h) * scale;
184 ImVec2 origin(canvas_p0.x + scrolling.x + scaled_x,
185 canvas_p0.y + scrolling.y + scaled_y);
186 ImVec2 size(origin.x + scaled_w, origin.y + scaled_h);
187 draw_list->AddRect(origin, size, color, 0.0f, 1.5f);
188}
189
190void DrawCanvasOutlineWithColor(ImDrawList* draw_list, ImVec2 canvas_p0,
191 ImVec2 scrolling, int x, int y, int w, int h,
192 ImVec4 color, float global_scale) {
193 uint32_t color_u32 =
194 IM_COL32(color.x * 255, color.y * 255, color.z * 255, color.w * 255);
195 DrawCanvasOutline(draw_list, canvas_p0, scrolling, x, y, w, h, color_u32,
196 global_scale);
197}
198
199// Grid utility functions
200void DrawCanvasGridLines(ImDrawList* draw_list, ImVec2 canvas_p0,
201 ImVec2 canvas_p1, ImVec2 scrolling, float grid_step,
202 float global_scale) {
203 const uint32_t grid_color = IM_COL32(200, 200, 200, 50);
204 const float grid_thickness = 0.5f;
205
206 float scaled_grid_step = grid_step * global_scale;
207
208 for (float x = fmodf(scrolling.x, scaled_grid_step);
209 x < (canvas_p1.x - canvas_p0.x); x += scaled_grid_step) {
210 draw_list->AddLineV(canvas_p0.x + x, canvas_p0.y, canvas_p1.y, grid_color,
211 grid_thickness);
212 }
213
214 for (float y = fmodf(scrolling.y, scaled_grid_step);
215 y < (canvas_p1.y - canvas_p0.y); y += scaled_grid_step) {
216 draw_list->AddLineH(canvas_p0.x, canvas_p1.x, canvas_p0.y + y, grid_color,
217 grid_thickness);
218 }
219}
220
221void DrawCustomHighlight(ImDrawList* draw_list, ImVec2 canvas_p0,
222 ImVec2 scrolling, int highlight_tile_id,
223 float grid_step) {
224 if (highlight_tile_id == -1)
225 return;
226
227 int tile_x = highlight_tile_id % 8;
228 int tile_y = highlight_tile_id / 8;
229 ImVec2 tile_pos(canvas_p0.x + scrolling.x + tile_x * grid_step,
230 canvas_p0.y + scrolling.y + tile_y * grid_step);
231 ImVec2 tile_pos_end(tile_pos.x + grid_step, tile_pos.y + grid_step);
232
233 draw_list->AddRectFilled(tile_pos, tile_pos_end, IM_COL32(255, 0, 255, 255));
234}
235
236void DrawHexTileLabels(ImDrawList* draw_list, ImVec2 canvas_p0,
237 ImVec2 scrolling, ImVec2 canvas_sz, float grid_step,
238 float global_scale) {
239 float scaled_grid_step = grid_step * global_scale;
240
241 for (float x = fmodf(scrolling.x, scaled_grid_step);
242 x < canvas_sz.x * global_scale; x += scaled_grid_step) {
243 for (float y = fmodf(scrolling.y, scaled_grid_step);
244 y < canvas_sz.y * global_scale; y += scaled_grid_step) {
245 int tile_x = (x - scrolling.x) / scaled_grid_step;
246 int tile_y = (y - scrolling.y) / scaled_grid_step;
247 int tile_id = tile_x + (tile_y * 16);
248
249 char hex_id[8];
250 snprintf(hex_id, sizeof(hex_id), "%02X", tile_id);
251
252 draw_list->AddText(ImVec2(canvas_p0.x + x + (scaled_grid_step / 2) - 4,
253 canvas_p0.y + y + (scaled_grid_step / 2) - 4),
254 IM_COL32(255, 255, 255, 255), hex_id);
255 }
256 }
257}
258
259// Layout and interaction utilities
260ImVec2 CalculateCanvasSize(ImVec2 content_region, ImVec2 custom_size,
261 bool use_custom) {
262 return use_custom ? custom_size : content_region;
263}
264
265ImVec2 CalculateScaledCanvasSize(ImVec2 canvas_size, float global_scale) {
266 return ImVec2(canvas_size.x * global_scale, canvas_size.y * global_scale);
267}
268
269bool IsPointInCanvas(ImVec2 point, ImVec2 canvas_p0, ImVec2 canvas_p1) {
270 return point.x >= canvas_p0.x && point.x <= canvas_p1.x &&
271 point.y >= canvas_p0.y && point.y <= canvas_p1.y;
272}
273
274// Size reporting for ImGui table integration
275ImVec2 CalculateMinimumCanvasSize(ImVec2 content_size, float global_scale,
276 float padding) {
277 // Calculate minimum size needed to display content with padding
278 ImVec2 min_size = ImVec2(content_size.x * global_scale + padding * 2,
279 content_size.y * global_scale + padding * 2);
280
281 // Ensure minimum practical size
282 min_size.x = std::max(min_size.x, 64.0f);
283 min_size.y = std::max(min_size.y, 64.0f);
284
285 return min_size;
286}
287
288ImVec2 CalculatePreferredCanvasSize(ImVec2 content_size, float global_scale,
289 float min_scale) {
290 // Calculate preferred size with minimum scale constraint
291 float effective_scale = std::max(global_scale, min_scale);
292 ImVec2 preferred_size = ImVec2(content_size.x * effective_scale + 8.0f,
293 content_size.y * effective_scale + 8.0f);
294
295 // Cap to reasonable maximum sizes for table integration
296 preferred_size.x = std::min(preferred_size.x, 800.0f);
297 preferred_size.y = std::min(preferred_size.y, 600.0f);
298
299 return preferred_size;
300}
301
302void ReserveCanvasSpace(ImVec2 canvas_size, const std::string& label) {
303 // Reserve space in ImGui layout so tables know the size
304 if (!label.empty()) {
305 ImGui::Text("%s", label.c_str());
306 }
307 ImGui::Dummy(canvas_size);
308 ImGui::SameLine();
309 ImGui::SetCursorPosX(ImGui::GetCursorPosX() -
310 canvas_size.x); // Move back to start
311}
312
313void SetNextCanvasSize(ImVec2 size, bool auto_resize) {
314 if (auto_resize) {
315 // Use auto-sizing child window for table integration
316 ImGui::SetNextWindowContentSize(size);
317 } else {
318 // Fixed size
319 ImGui::SetNextWindowSize(size);
320 }
321}
322
323// High-level composite operations
324void DrawCanvasGrid(const CanvasRenderContext& ctx, int highlight_tile_id) {
325 if (!ctx.enable_grid)
326 return;
327
328 ctx.draw_list->PushClipRect(ctx.canvas_p0, ctx.canvas_p1, true);
329
330 // Draw grid lines
332 ctx.scrolling, ctx.grid_step, ctx.global_scale);
333
334 // Draw highlight if specified
335 if (highlight_tile_id != -1) {
337 highlight_tile_id, ctx.grid_step * ctx.global_scale);
338 }
339
340 // Draw hex labels if enabled
341 if (ctx.enable_hex_labels) {
343 ImVec2(ctx.canvas_p1.x - ctx.canvas_p0.x,
344 ctx.canvas_p1.y - ctx.canvas_p0.y),
345 ctx.grid_step, ctx.global_scale);
346 }
347
348 ctx.draw_list->PopClipRect();
349}
350
352 const ImVector<ImVec2>& points,
353 const ImVector<ImVec2>& selected_points) {
354 const ImVec2 origin(ctx.canvas_p0.x + ctx.scrolling.x,
355 ctx.canvas_p0.y + ctx.scrolling.y);
356 const float scale = ctx.global_scale;
357
358 // Draw hover points (already in screen coordinates)
359 for (int n = 0; n < points.Size; n += 2) {
360 ctx.draw_list->AddRect(
361 ImVec2(origin.x + points[n].x, origin.y + points[n].y),
362 ImVec2(origin.x + points[n + 1].x, origin.y + points[n + 1].y),
363 IM_COL32(255, 255, 255, 255), 0.0f, 1.0f);
364 }
365
366 // Draw selection rectangles (selected_points are in world coordinates)
367 // Scale world coordinates to screen coordinates for proper display
368 if (!selected_points.empty()) {
369 constexpr float kTile16Size = 16.0f;
370 for (int n = 0; n < selected_points.size(); n += 2) {
371 // Release retains the drag direction. Order the tile corners before
372 // including the last tile, as AddRect requires increasing bounds.
373 const ImVec2 min(
374 std::min(selected_points[n].x, selected_points[n + 1].x),
375 std::min(selected_points[n].y, selected_points[n + 1].y));
376 const ImVec2 max(
377 std::max(selected_points[n].x, selected_points[n + 1].x),
378 std::max(selected_points[n].y, selected_points[n + 1].y));
379 float start_x = origin.x + min.x * scale;
380 float start_y = origin.y + min.y * scale;
381 float end_x = origin.x + (max.x + kTile16Size) * scale;
382 float end_y = origin.y + (max.y + kTile16Size) * scale;
383 ctx.draw_list->AddRect(ImVec2(start_x, start_y), ImVec2(end_x, end_y),
384 IM_COL32(255, 255, 255, 255), 0.0f, 1.0f);
385 }
386 }
387}
388
390 const ImVector<ImVector<std::string>>& labels,
391 int current_labels, int tile_id_offset) {
392 if (current_labels >= labels.size())
393 return;
394
395 float scaled_grid_step = ctx.grid_step * ctx.global_scale;
396
397 for (float x = fmodf(ctx.scrolling.x, scaled_grid_step);
398 x < (ctx.canvas_p1.x - ctx.canvas_p0.x); x += scaled_grid_step) {
399 for (float y = fmodf(ctx.scrolling.y, scaled_grid_step);
400 y < (ctx.canvas_p1.y - ctx.canvas_p0.y); y += scaled_grid_step) {
401 int tile_x = (x - ctx.scrolling.x) / scaled_grid_step;
402 int tile_y = (y - ctx.scrolling.y) / scaled_grid_step;
403 int tile_id = tile_x + (tile_y * tile_id_offset);
404
405 if (tile_id >= labels[current_labels].size()) {
406 break;
407 }
408
409 const std::string& label = labels[current_labels][tile_id];
410 ctx.draw_list->AddText(
411 ImVec2(ctx.canvas_p0.x + x + (scaled_grid_step / 2) - tile_id_offset,
412 ctx.canvas_p0.y + y + (scaled_grid_step / 2) - tile_id_offset),
413 IM_COL32(255, 255, 255, 255), label.c_str());
414 }
415 }
416}
417
418} // namespace CanvasUtils
419
420// CanvasConfig theme-aware methods implementation
422 if (!use_theme_sizing) {
423 return 32.0f; // Legacy fixed height
424 }
425
426 // Use layout helpers for theme-aware sizing
427 // We need to include layout_helpers.h in the implementation file
428 // For now, return a reasonable default that respects ImGui font size
429 return ImGui::GetFontSize() *
430 0.75f; // Will be replaced with LayoutHelpers call
431}
432
434 if (!use_theme_sizing) {
435 return grid_step; // Use configured grid_step as-is
436 }
437
438 // Apply minimal theme-aware adjustment based on font size
439 // Grid should stay consistent, but scale slightly with UI density
440 float base_spacing = ImGui::GetFontSize() * 0.5f;
441 return std::max(grid_step, base_spacing);
442}
443
444} // namespace gui
445} // namespace yaze
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
Definition arena.cc:39
static Arena & Get()
Definition arena.cc:24
Represents a bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:70
void set_modified(bool modified)
Definition bitmap.h:418
void SetPalette(const SnesPalette &palette)
Set the palette for the bitmap using SNES palette format.
Definition bitmap.cc:452
void SetPaletteWithTransparent(const SnesPalette &palette, size_t index, int length=7)
Set the palette with a transparent color.
Definition bitmap.cc:524
Defines an abstract interface for all rendering operations.
Definition irenderer.h:60
#define LOG_DEBUG(category, format,...)
Definition log.h:133
#define LOG_ERROR(category, format,...)
Definition log.h:140
void ReserveCanvasSpace(ImVec2 canvas_size, const std::string &label)
void SetNextCanvasSize(ImVec2 size, bool auto_resize)
void DrawCanvasRect(ImDrawList *draw_list, ImVec2 canvas_p0, ImVec2 scrolling, int x, int y, int w, int h, ImVec4 color, float global_scale)
void DrawCanvasLabels(const CanvasRenderContext &ctx, const ImVector< ImVector< std::string > > &labels, int current_labels, int tile_id_offset)
bool IsPointInCanvas(ImVec2 point, ImVec2 canvas_p0, ImVec2 canvas_p1)
void DrawCanvasOverlay(const CanvasRenderContext &ctx, const ImVector< ImVec2 > &points, const ImVector< ImVec2 > &selected_points)
void DrawCanvasOutlineWithColor(ImDrawList *draw_list, ImVec2 canvas_p0, ImVec2 scrolling, int x, int y, int w, int h, ImVec4 color, float global_scale)
void DrawCanvasOutline(ImDrawList *draw_list, ImVec2 canvas_p0, ImVec2 scrolling, int x, int y, int w, int h, uint32_t color, float global_scale)
bool LoadROMPaletteGroups(zelda3::GameData *game_data, CanvasPaletteManager &palette_manager)
ImVec2 CalculateCanvasSize(ImVec2 content_region, ImVec2 custom_size, bool use_custom)
ImVec2 CalculateMinimumCanvasSize(ImVec2 content_size, float global_scale, float padding)
float CalculateEffectiveScale(ImVec2 canvas_size, ImVec2 content_size, float global_scale)
int GetTileIdFromPosition(ImVec2 mouse_pos, float tile_size, float scale, int tiles_per_row)
bool ApplyPaletteGroup(gfx::IRenderer *renderer, gfx::Bitmap *bitmap, CanvasPaletteManager &palette_manager, int group_index, int palette_index)
void DrawCanvasGrid(const CanvasRenderContext &ctx, int highlight_tile_id)
ImVec2 CalculatePreferredCanvasSize(ImVec2 content_size, float global_scale, float min_scale)
void DrawCanvasText(ImDrawList *draw_list, ImVec2 canvas_p0, ImVec2 scrolling, const std::string &text, int x, int y, float global_scale)
ImVec2 CalculateScaledCanvasSize(ImVec2 canvas_size, float global_scale)
void DrawCustomHighlight(ImDrawList *draw_list, ImVec2 canvas_p0, ImVec2 scrolling, int highlight_tile_id, float grid_step)
void DrawCanvasGridLines(ImDrawList *draw_list, ImVec2 canvas_p0, ImVec2 canvas_p1, ImVec2 scrolling, float grid_step, float global_scale)
ImVec2 AlignToGrid(ImVec2 pos, float grid_step)
void DrawHexTileLabels(ImDrawList *draw_list, ImVec2 canvas_p0, ImVec2 scrolling, ImVec2 canvas_sz, float grid_step, float global_scale)
float GetGridSpacing() const
float GetToolbarHeight() const
Palette management state for canvas.
std::vector< gfx::SnesPalette > rom_palette_groups
std::vector< std::string > palette_group_names
gfx::PaletteGroupMap palette_groups
Definition game_data.h:95