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:
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user