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