feat: Enhance DungeonCanvasViewer with Debugging Features and Canvas Configuration

- Added detailed comments to clarify the canvas coordinate system and scaling model for dungeon rendering.
- Implemented persistent debug overlays for room and texture information, allowing real-time visibility of room properties and texture states.
- Introduced a context menu with options for debugging, including toggling visibility of room info, texture info, and object bounds.
- Updated canvas drawing methods to ensure correct handling of unsized dimensions and scaling, improving rendering accuracy.
- Refactored coordinate conversion functions to return unsized pixel coordinates, preventing double-scaling issues.
This commit is contained in:
scawful
2025-10-10 00:45:59 -04:00
parent 8481cd9366
commit 9f0b503ada
5 changed files with 323 additions and 61 deletions

View File

@@ -31,6 +31,31 @@ void DungeonCanvasViewer::DrawDungeonCanvas(int room_id) {
}
ImGui::BeginGroup();
// CRITICAL: Canvas coordinate system for dungeons
// The canvas system uses a two-stage scaling model:
// 1. Canvas size: UNSCALED content dimensions (512x512 for dungeon rooms)
// 2. Viewport size: canvas_size * global_scale (handles zoom)
// 3. Grid lines: grid_step * global_scale (auto-scales with zoom)
// 4. Bitmaps: drawn with scale = global_scale (matches viewport)
constexpr int kRoomPixelWidth = 512; // 64 tiles * 8 pixels (UNSCALED)
constexpr int kRoomPixelHeight = 512;
constexpr int kDungeonTileSize = 8; // Dungeon tiles are 8x8 pixels
// Configure canvas for dungeon display
canvas_.SetCanvasSize(ImVec2(kRoomPixelWidth, kRoomPixelHeight));
canvas_.SetGridSize(gui::CanvasGridSize::k8x8); // Match dungeon tile size
// DEBUG: Log canvas configuration
static int debug_frame_count = 0;
if (debug_frame_count++ % 60 == 0) { // Log once per second (assuming 60fps)
LOG_DEBUG("[DungeonCanvas]", "Canvas config: size=(%.0f,%.0f) scale=%.2f grid=%.0f",
canvas_.width(), canvas_.height(), canvas_.global_scale(), canvas_.custom_step());
LOG_DEBUG("[DungeonCanvas]", "Canvas viewport: p0=(%.0f,%.0f) p1=(%.0f,%.0f)",
canvas_.zero_point().x, canvas_.zero_point().y,
canvas_.zero_point().x + canvas_.width() * canvas_.global_scale(),
canvas_.zero_point().y + canvas_.height() * canvas_.global_scale());
}
if (rooms_) {
auto& room = (*rooms_)[room_id];
@@ -173,13 +198,24 @@ void DungeonCanvasViewer::DrawDungeonCanvas(int room_id) {
ImGui::EndGroup();
canvas_.DrawBackground();
// CRITICAL: Draw canvas with explicit size to ensure viewport matches content
// Pass the unscaled room size directly to DrawBackground
canvas_.DrawBackground(ImVec2(kRoomPixelWidth, kRoomPixelHeight));
// DEBUG: Log canvas state after DrawBackground
if (debug_frame_count % 60 == 1) {
LOG_DEBUG("[DungeonCanvas]", "After DrawBackground: canvas_sz=(%.0f,%.0f) canvas_p0=(%.0f,%.0f) canvas_p1=(%.0f,%.0f)",
canvas_.canvas_size().x, canvas_.canvas_size().y,
canvas_.zero_point().x, canvas_.zero_point().y,
canvas_.zero_point().x + canvas_.canvas_size().x, canvas_.zero_point().y + canvas_.canvas_size().y);
}
// Add dungeon-specific context menu items
canvas_.ClearContextMenuItems();
if (rooms_ && rom_->is_loaded()) {
auto& room = (*rooms_)[room_id];
auto& layer_settings = GetRoomLayerSettings(room_id);
// Add object placement option
canvas_.AddContextMenuItem({
@@ -226,10 +262,160 @@ void DungeonCanvasViewer::DrawDungeonCanvas(int room_id) {
},
"Ctrl+R"
});
// === DEBUG MENU ===
gui::Canvas::ContextMenuItem debug_menu;
debug_menu.label = ICON_MD_BUG_REPORT " Debug";
// Show room info
debug_menu.subitems.push_back({
ICON_MD_INFO " Show Room Info",
[this, room_id]() {
show_room_debug_info_ = !show_room_debug_info_;
}
});
// Show texture info
debug_menu.subitems.push_back({
ICON_MD_IMAGE " Show Texture Debug",
[this]() {
show_texture_debug_ = !show_texture_debug_;
}
});
// Show object bounds
debug_menu.subitems.push_back({
ICON_MD_CROP_SQUARE " Show Object Bounds",
[this]() {
show_object_bounds_ = !show_object_bounds_;
}
});
// Show layer info
debug_menu.subitems.push_back({
ICON_MD_LAYERS " Show Layer Info",
[this]() {
show_layer_info_ = !show_layer_info_;
}
});
// Force reload room
debug_menu.subitems.push_back({
ICON_MD_REFRESH " Force Reload",
[&room, room_id]() {
room.LoadObjects();
room.LoadRoomGraphics(room.blockset);
room.RenderRoomGraphics();
}
});
// Log room state
debug_menu.subitems.push_back({
ICON_MD_PRINT " Log Room State",
[&room, room_id]() {
LOG_DEBUG("DungeonDebug", "=== Room %03X Debug ===", room_id);
LOG_DEBUG("DungeonDebug", "Blockset: %d, Palette: %d, Layout: %d",
room.blockset, room.palette, room.layout);
LOG_DEBUG("DungeonDebug", "Objects: %zu, Sprites: %zu",
room.GetTileObjects().size(), room.GetSprites().size());
LOG_DEBUG("DungeonDebug", "BG1: %dx%d, BG2: %dx%d",
room.bg1_buffer().bitmap().width(), room.bg1_buffer().bitmap().height(),
room.bg2_buffer().bitmap().width(), room.bg2_buffer().bitmap().height());
}
});
canvas_.AddContextMenuItem(debug_menu);
}
canvas_.DrawContextMenu();
// Draw persistent debug overlays
if (show_room_debug_info_ && rooms_ && rom_->is_loaded()) {
auto& room = (*rooms_)[room_id];
ImGui::SetNextWindowPos(ImVec2(canvas_.zero_point().x + 10, canvas_.zero_point().y + 10), ImGuiCond_FirstUseEver);
ImGui::SetNextWindowSize(ImVec2(300, 0), ImGuiCond_FirstUseEver);
if (ImGui::Begin("Room Debug Info", &show_room_debug_info_, ImGuiWindowFlags_NoCollapse)) {
ImGui::Text("Room: 0x%03X (%d)", room_id, room_id);
ImGui::Separator();
ImGui::Text("Graphics");
ImGui::Text(" Blockset: 0x%02X", room.blockset);
ImGui::Text(" Palette: 0x%02X", room.palette);
ImGui::Text(" Layout: 0x%02X", room.layout);
ImGui::Text(" Spriteset: 0x%02X", room.spriteset);
ImGui::Separator();
ImGui::Text("Content");
ImGui::Text(" Objects: %zu", room.GetTileObjects().size());
ImGui::Text(" Sprites: %zu", room.GetSprites().size());
ImGui::Separator();
ImGui::Text("Buffers");
auto& bg1 = room.bg1_buffer().bitmap();
auto& bg2 = room.bg2_buffer().bitmap();
ImGui::Text(" BG1: %dx%d %s", bg1.width(), bg1.height(),
bg1.texture() ? "(has texture)" : "(NO TEXTURE)");
ImGui::Text(" BG2: %dx%d %s", bg2.width(), bg2.height(),
bg2.texture() ? "(has texture)" : "(NO TEXTURE)");
ImGui::Separator();
ImGui::Text("Layers");
auto& layer_settings = GetRoomLayerSettings(room_id);
ImGui::Checkbox("BG1 Visible", &layer_settings.bg1_visible);
ImGui::Checkbox("BG2 Visible", &layer_settings.bg2_visible);
ImGui::SliderInt("BG2 Type", &layer_settings.bg2_layer_type, 0, 4);
}
ImGui::End();
}
if (show_texture_debug_ && rooms_ && rom_->is_loaded()) {
ImGui::SetNextWindowPos(ImVec2(canvas_.zero_point().x + 320, canvas_.zero_point().y + 10), ImGuiCond_FirstUseEver);
ImGui::SetNextWindowSize(ImVec2(250, 0), ImGuiCond_FirstUseEver);
if (ImGui::Begin("Texture Debug", &show_texture_debug_, ImGuiWindowFlags_NoCollapse)) {
auto& room = (*rooms_)[room_id];
auto& bg1 = room.bg1_buffer().bitmap();
auto& bg2 = room.bg2_buffer().bitmap();
ImGui::Text("BG1 Bitmap");
ImGui::Text(" Size: %dx%d", bg1.width(), bg1.height());
ImGui::Text(" Active: %s", bg1.is_active() ? "YES" : "NO");
ImGui::Text(" Texture: 0x%p", bg1.texture());
ImGui::Text(" Modified: %s", bg1.modified() ? "YES" : "NO");
if (bg1.texture()) {
ImGui::Text(" Preview:");
ImGui::Image((ImTextureID)(intptr_t)bg1.texture(), ImVec2(128, 128));
}
ImGui::Separator();
ImGui::Text("BG2 Bitmap");
ImGui::Text(" Size: %dx%d", bg2.width(), bg2.height());
ImGui::Text(" Active: %s", bg2.is_active() ? "YES" : "NO");
ImGui::Text(" Texture: 0x%p", bg2.texture());
ImGui::Text(" Modified: %s", bg2.modified() ? "YES" : "NO");
if (bg2.texture()) {
ImGui::Text(" Preview:");
ImGui::Image((ImTextureID)(intptr_t)bg2.texture(), ImVec2(128, 128));
}
}
ImGui::End();
}
if (show_layer_info_) {
ImGui::SetNextWindowPos(ImVec2(canvas_.zero_point().x + 580, canvas_.zero_point().y + 10), ImGuiCond_FirstUseEver);
ImGui::SetNextWindowSize(ImVec2(200, 0), ImGuiCond_FirstUseEver);
if (ImGui::Begin("Layer Info", &show_layer_info_, ImGuiWindowFlags_NoCollapse)) {
ImGui::Text("Canvas Scale: %.2f", canvas_.global_scale());
ImGui::Text("Canvas Size: %.0fx%.0f", canvas_.width(), canvas_.height());
auto& layer_settings = GetRoomLayerSettings(room_id);
ImGui::Separator();
ImGui::Text("Layer Visibility:");
ImGui::Text(" BG1: %s", layer_settings.bg1_visible ? "VISIBLE" : "hidden");
ImGui::Text(" BG2: %s", layer_settings.bg2_visible ? "VISIBLE" : "hidden");
ImGui::Text("BG2 Type: %d", layer_settings.bg2_layer_type);
const char* bg2_type_names[] = {"Normal", "Translucent", "Addition", "Dark", "Off"};
ImGui::Text(" (%s)", bg2_type_names[std::min(layer_settings.bg2_layer_type, 4)]);
}
ImGui::End();
}
if (rooms_ && rom_->is_loaded()) {
auto& room = (*rooms_)[room_id];
@@ -259,14 +445,6 @@ void DungeonCanvasViewer::DrawDungeonCanvas(int room_id) {
// This already includes objects rendered by ObjectDrawer in Room::RenderObjectsToBackground()
DrawRoomBackgroundLayers(room_id);
// Draw room layout (structural elements like walls, pits)
// This provides context for object placement
DrawRoomLayout(room);
// VISUALIZATION: Draw object position rectangles (for debugging)
// This shows where objects are placed regardless of whether graphics render
DrawObjectPositionOutlines(room);
// Render sprites as simple 16x16 squares with labels
// (Sprites are not part of the background buffers)
RenderSprites(room);
@@ -281,6 +459,21 @@ void DungeonCanvasViewer::DrawDungeonCanvas(int room_id) {
}
}
// Draw layout overlays on top of background bitmap
if (rooms_ && rom_->is_loaded()) {
auto& room = (*rooms_)[room_id];
// Draw room layout (structural elements like walls, pits)
// This provides context for object placement
DrawRoomLayout(room);
// VISUALIZATION: Draw object position rectangles (for debugging)
// This shows where objects are placed regardless of whether graphics render
if (show_object_bounds_) {
DrawObjectPositionOutlines(room);
}
}
canvas_.DrawGrid();
canvas_.DrawOverlay();
@@ -364,37 +557,38 @@ void DungeonCanvasViewer::RenderSprites(const zelda3::Room& room) {
// Coordinate conversion helper functions
std::pair<int, int> DungeonCanvasViewer::RoomToCanvasCoordinates(int room_x,
int room_y) const {
// Convert room coordinates (tile units) to canvas coordinates (pixels)
// Convert room coordinates (tile units) to UNSCALED canvas pixel coordinates
// Dungeon tiles are 8x8 pixels (not 16x16!)
// Account for canvas scaling and offset
float scale = canvas_.global_scale();
int offset_x = static_cast<int>(canvas_.drawn_tile_position().x);
int offset_y = static_cast<int>(canvas_.drawn_tile_position().y);
// IMPORTANT: Return UNSCALED coordinates - Canvas drawing functions apply scale internally
// Do NOT multiply by scale here or we get double-scaling!
return {static_cast<int>((room_x * 8 + offset_x) * scale),
static_cast<int>((room_y * 8 + offset_y) * scale)};
// Simple conversion: tile units → pixel units (no scale, no offset)
return {room_x * 8, room_y * 8};
}
std::pair<int, int> DungeonCanvasViewer::CanvasToRoomCoordinates(int canvas_x,
int canvas_y) const {
// Convert canvas coordinates (pixels) to room coordinates (tile units)
// Dungeon tiles are 8x8 pixels (not 16x16!)
// Account for canvas scaling and offset
float scale = canvas_.global_scale();
int offset_x = static_cast<int>(canvas_.drawn_tile_position().x);
int offset_y = static_cast<int>(canvas_.drawn_tile_position().y);
// Convert canvas screen coordinates (pixels) to room coordinates (tile units)
// Input: Screen-space coordinates (affected by zoom/scale)
// Output: Logical tile coordinates (0-63 for each axis)
// IMPORTANT: Mouse coordinates are in screen space, must undo scale first
float scale = canvas_.global_scale();
if (scale <= 0.0f) scale = 1.0f; // Prevent division by zero
return {static_cast<int>((canvas_x / scale - offset_x) / 8),
static_cast<int>((canvas_y / scale - offset_y) / 8)};
// Step 1: Convert screen space → logical pixel space
int logical_x = static_cast<int>(canvas_x / scale);
int logical_y = static_cast<int>(canvas_y / scale);
// Step 2: Convert logical pixels → tile units (8 pixels per tile)
return {logical_x / 8, logical_y / 8};
}
bool DungeonCanvasViewer::IsWithinCanvasBounds(int canvas_x, int canvas_y,
int margin) const {
// Check if coordinates are within canvas bounds with optional margin
auto canvas_width = canvas_.width() * canvas_.global_scale();
auto canvas_height = canvas_.height() * canvas_.global_scale();
auto canvas_width = canvas_.width();
auto canvas_height = canvas_.height();
return (canvas_x >= -margin && canvas_y >= -margin &&
canvas_x <= canvas_width + margin &&
canvas_y <= canvas_height + margin);
@@ -462,35 +656,33 @@ void DungeonCanvasViewer::DrawRoomLayout(zelda3::Room& room) {
LOG_DEBUG("[DrawRoomLayout]", "Layout elements: %zu walls, %zu floors, %zu pits, %zu water, %zu doors",
walls.size(), floors.size(), pits.size(), water.size(), doors.size());
// Get canvas scale for proper sizing
float scale = canvas_.global_scale();
int tile_size = static_cast<int>(8 * scale); // 8x8 pixels scaled
// IMPORTANT: Use UNSCALED dimensions - canvas drawing functions apply scale internally
constexpr int kTileSize = 8; // Dungeon tiles are 8x8 pixels (UNSCALED)
// Draw walls (dark gray, semi-transparent) - immovable structure
for (const auto& wall : walls) {
auto [x, y] = RoomToCanvasCoordinates(wall.x(), wall.y());
canvas_.DrawRect(x, y, tile_size, tile_size, ImVec4(0.2f, 0.2f, 0.2f, 0.5f));
canvas_.DrawRect(x, y, kTileSize, kTileSize, ImVec4(0.2f, 0.2f, 0.2f, 0.5f));
}
// Draw pits (orange warning) - damage zones
for (const auto& pit : pits) {
auto [x, y] = RoomToCanvasCoordinates(pit.x(), pit.y());
canvas_.DrawRect(x, y, tile_size, tile_size, ImVec4(1.0f, 0.4f, 0.0f, 0.6f));
canvas_.DrawRect(x, y, kTileSize, kTileSize, ImVec4(1.0f, 0.4f, 0.0f, 0.6f));
}
// Draw water (blue, semi-transparent)
for (const auto& water_tile : water) {
auto [x, y] = RoomToCanvasCoordinates(water_tile.x(), water_tile.y());
canvas_.DrawRect(x, y, tile_size, tile_size, ImVec4(0.2f, 0.4f, 0.8f, 0.5f));
canvas_.DrawRect(x, y, kTileSize, kTileSize, ImVec4(0.2f, 0.4f, 0.8f, 0.5f));
}
// Draw doors (purple) - connection points
for (const auto& door : doors) {
auto [x, y] = RoomToCanvasCoordinates(door.x(), door.y());
canvas_.DrawRect(x, y, tile_size, tile_size, ImVec4(0.8f, 0.2f, 0.8f, 0.7f));
if (scale >= 1.0f) { // Only show label if zoomed in enough
canvas_.DrawText("DOOR", x + 2, y + 2);
}
canvas_.DrawRect(x, y, kTileSize, kTileSize, ImVec4(0.8f, 0.2f, 0.8f, 0.7f));
// Text is drawn in logical space, canvas scales it
canvas_.DrawText("DOOR", x + 2, y + 2);
}
}
@@ -501,14 +693,11 @@ void DungeonCanvasViewer::DrawObjectPositionOutlines(const zelda3::Room& room) {
const auto& objects = room.GetTileObjects();
// Get canvas scale for proper sizing
float scale = canvas_.global_scale();
for (const auto& obj : objects) {
// Convert object position (tile coordinates) to canvas pixel coordinates
// Convert object position (tile coordinates) to canvas pixel coordinates (UNSCALED)
auto [canvas_x, canvas_y] = RoomToCanvasCoordinates(obj.x(), obj.y());
// Calculate object dimensions based on type and size
// Calculate object dimensions based on type and size (UNSCALED logical pixels)
int width = 8; // Default 8x8 pixels
int height = 8;
@@ -517,15 +706,12 @@ void DungeonCanvasViewer::DrawObjectPositionOutlines(const zelda3::Room& room) {
int size_h = (obj.size() & 0x0F);
int size_v = (obj.size() >> 4) & 0x0F;
// Objects are typically (size+1) tiles wide/tall
// Objects are typically (size+1) tiles wide/tall (8 pixels per tile)
width = (size_h + 1) * 8;
height = (size_v + 1) * 8;
// Apply canvas scale
width = static_cast<int>(width * scale);
height = static_cast<int>(height * scale);
// Clamp to reasonable sizes
// IMPORTANT: Do NOT apply canvas scale here - DrawRect handles it
// Clamp to reasonable sizes (in logical space)
width = std::min(width, 512);
height = std::min(height, 512);

View File

@@ -5,7 +5,6 @@
#include "app/gui/canvas.h"
#include "app/rom.h"
#include "app/zelda3/dungeon/object_renderer.h"
#include "app/zelda3/dungeon/room.h"
#include "app/gfx/snes_palette.h"
#include "dungeon_object_interaction.h"
@@ -155,6 +154,12 @@ class DungeonCanvasViewer {
};
std::vector<ObjectRenderCache> object_render_cache_;
uint64_t last_palette_hash_ = 0;
// Debug state (persistent between frames for debug windows)
bool show_room_debug_info_ = false;
bool show_texture_debug_ = false;
bool show_object_bounds_ = false;
bool show_layer_info_ = false;
};
} // namespace editor

View File

@@ -82,20 +82,14 @@ Canvas::Canvas(gfx::IRenderer* renderer, const std::string& id, ImVec2 canvas_si
Canvas::~Canvas() = default;
using ImGui::BeginMenu;
using ImGui::EndMenu;
using ImGui::GetContentRegionAvail;
using ImGui::GetCursorScreenPos;
using ImGui::GetIO;
using ImGui::GetMouseDragDelta;
using ImGui::GetWindowDrawList;
using ImGui::IsItemActive;
using ImGui::IsItemHovered;
using ImGui::IsMouseClicked;
using ImGui::IsMouseDragging;
using ImGui::MenuItem;
using ImGui::OpenPopupOnItemClick;
using ImGui::Selectable;
using ImGui::Text;
constexpr uint32_t kRectangleColor = IM_COL32(32, 32, 32, 255);
@@ -353,11 +347,14 @@ void Canvas::End() {
DrawGrid();
}
DrawOverlay();
// Render any persistent popups from context menu actions
RenderPersistentPopups();
}
// ==================== Legacy Interface ====================
void Canvas::UpdateColorPainter(gfx::IRenderer* renderer, gfx::Bitmap& bitmap, const ImVec4& color,
void Canvas::UpdateColorPainter(gfx::IRenderer* /*renderer*/, gfx::Bitmap& bitmap, const ImVec4& color,
const std::function<void()>& event,
int tile_size, float scale) {
config_.global_scale = scale;
@@ -602,6 +599,59 @@ void Canvas::ClearContextMenuItems() {
context_menu_items_.clear();
}
void Canvas::OpenPersistentPopup(const std::string& popup_id, std::function<void()> render_callback) {
// Check if popup already exists
for (auto& popup : active_popups_) {
if (popup.popup_id == popup_id) {
popup.is_open = true;
popup.render_callback = std::move(render_callback);
ImGui::OpenPopup(popup_id.c_str());
return;
}
}
// Add new popup
PopupState new_popup;
new_popup.popup_id = popup_id;
new_popup.is_open = true;
new_popup.render_callback = std::move(render_callback);
active_popups_.push_back(new_popup);
ImGui::OpenPopup(popup_id.c_str());
}
void Canvas::ClosePersistentPopup(const std::string& popup_id) {
for (auto& popup : active_popups_) {
if (popup.popup_id == popup_id) {
popup.is_open = false;
ImGui::CloseCurrentPopup();
return;
}
}
}
void Canvas::RenderPersistentPopups() {
// Render all active popups
auto it = active_popups_.begin();
while (it != active_popups_.end()) {
if (it->is_open && it->render_callback) {
// Call the render callback which should handle BeginPopup/EndPopup
it->render_callback();
// If popup was closed by user, mark it for removal
if (!ImGui::IsPopupOpen(it->popup_id.c_str())) {
it->is_open = false;
}
}
// Remove closed popups
if (!it->is_open) {
it = active_popups_.erase(it);
} else {
++it;
}
}
}
void Canvas::SetZoomToFit(const gfx::Bitmap& bitmap) {
if (!bitmap.is_active())
return;
@@ -990,7 +1040,7 @@ void Canvas::DrawSelectRect(int current_map, int tile_size, float scale) {
}
}
void Canvas::DrawBitmap(Bitmap& bitmap, int border_offset, float scale) {
void Canvas::DrawBitmap(Bitmap& bitmap, int /*border_offset*/, float scale) {
if (!bitmap.is_active()) {
return;
}
@@ -1014,12 +1064,17 @@ void Canvas::DrawBitmap(Bitmap& bitmap, int x_offset, int y_offset, float scale,
bitmap_ = &bitmap;
// Update content size for table integration
// CRITICAL: Store UNSCALED bitmap size as content - scale is applied during rendering
config_.content_size = ImVec2(bitmap.width(), bitmap.height());
// Calculate the actual rendered size including scale and offsets
// CRITICAL: Use scale parameter (NOT global_scale_) for per-bitmap scaling
ImVec2 rendered_size(bitmap.width() * scale, bitmap.height() * scale);
ImVec2 total_size(x_offset + rendered_size.x, y_offset + rendered_size.y);
// CRITICAL FIX: Draw bitmap WITHOUT additional global_scale multiplication
// The scale parameter already contains the correct scale factor
// The scrolling should NOT be scaled - it's already in screen space
draw_list_->AddImage(
(ImTextureID)(intptr_t)bitmap.texture(),
ImVec2(canvas_p0_.x + x_offset + scrolling_.x,
@@ -1257,7 +1312,7 @@ void Canvas::DrawRect(int x, int y, int w, int h, ImVec4 color) {
color, config_.global_scale);
}
void Canvas::DrawText(std::string text, int x, int y) {
void Canvas::DrawText(const std::string& text, int x, int y) {
CanvasUtils::DrawCanvasText(draw_list_, canvas_p0_, scrolling_, text, x, y,
config_.global_scale);
}
@@ -1350,6 +1405,9 @@ void Canvas::DrawOverlay() {
// Use high-level utility function with local points (synchronized from interaction handler)
CanvasUtils::DrawCanvasOverlay(ctx, points_, selected_points_);
// Render any persistent popups from context menu actions
RenderPersistentPopups();
}
void Canvas::DrawLayeredElements() {
@@ -1797,7 +1855,7 @@ void Canvas::ShowBppConversionDialog() {
if (bitmap_) {
bpp_conversion_dialog_->Show(
*bitmap_, bitmap_->palette(),
[this](gfx::BppFormat format, bool preserve_palette) {
[this](gfx::BppFormat format, bool /*preserve_palette*/) {
ConvertBitmapFormat(format);
});
}

View File

@@ -197,6 +197,11 @@ class Canvas {
void ClearContextMenuItems();
void SetContextMenuEnabled(bool enabled) { context_menu_enabled_ = enabled; }
// Persistent popup management for context menu actions
void OpenPersistentPopup(const std::string& popup_id, std::function<void()> render_callback);
void ClosePersistentPopup(const std::string& popup_id);
void RenderPersistentPopups();
// Enhanced view and edit operations
void ShowAdvancedCanvasProperties();
void ShowScalingControls();
@@ -272,7 +277,7 @@ class Canvas {
void DrawRect(int x, int y, int w, int h, ImVec4 color);
void DrawText(std::string text, int x, int y);
void DrawText(const std::string& text, int x, int y);
void DrawGridLines(float grid_step);
void DrawInfoGrid(float grid_step = 64.0f, int tile_id_offset = 8,
@@ -418,6 +423,14 @@ class Canvas {
// Context menu system
std::vector<ContextMenuItem> context_menu_items_;
bool context_menu_enabled_ = true;
// Persistent popup state for context menu actions
struct PopupState {
std::string popup_id;
bool is_open = false;
std::function<void()> render_callback;
};
std::vector<PopupState> active_popups_;
// Legacy members (to be gradually replaced)
int current_labels_ = 0;

View File

@@ -105,8 +105,8 @@ class RoomLayout {
private:
Rom* rom_ = nullptr;
std::vector<RoomLayoutObject> objects_;
uint8_t width_ = 16; // Default room width in tiles
uint8_t height_ = 11; // Default room height in tiles
uint8_t width_ = 64; // Dungeon room width in tiles (512 pixels / 8)
uint8_t height_ = 64; // Dungeon room height in tiles (512 pixels / 8)
// Parse layout data from ROM
absl::Status ParseLayoutData(const std::vector<uint8_t>& data);