epic: refactor SDL2_Renderer usage to IRenderer and queued texture rendering

- Updated the testing guide to clarify the testing framework's organization and execution methods, improving user understanding.
- Refactored CMakeLists to include new platform-specific files, ensuring proper integration of the rendering backend.
- Modified main application files to utilize the new IRenderer interface, enhancing flexibility in rendering operations.
- Implemented deferred texture management in various components, allowing for more efficient graphics handling and improved performance.
- Introduced new methods for texture creation and updates, streamlining the rendering process across the application.
- Enhanced logging and error handling in the rendering pipeline to facilitate better debugging and diagnostics.
This commit is contained in:
scawful
2025-10-07 17:15:11 -04:00
parent 9e6f538520
commit 6c331f1fd0
101 changed files with 1401 additions and 2677 deletions

View File

@@ -1,14 +1,10 @@
#include "dungeon_canvas_viewer.h"
#include "absl/strings/str_format.h"
#include "app/core/window.h"
#include "app/gfx/arena.h"
#include "app/gfx/snes_palette.h"
#include "app/gui/canvas.h"
#include "app/gui/input.h"
#include "app/rom.h"
#include "util/log.h"
#include "app/zelda3/dungeon/object_drawer.h"
#include "app/zelda3/dungeon/object_renderer.h"
#include "app/zelda3/dungeon/room.h"
#include "app/zelda3/sprite/sprite.h"
@@ -148,6 +144,7 @@ void DungeonCanvasViewer::DrawDungeonCanvas(int room_id) {
object_interaction_.HandleCanvasMouseInput();
object_interaction_.CheckForObjectSelection();
object_interaction_.DrawSelectBox();
object_interaction_.DrawSelectionHighlights(); // Draw selection highlights on top
}
}
@@ -195,7 +192,9 @@ void DungeonCanvasViewer::RenderObjectInCanvas(const zelda3::RoomObject &object,
// Ensure the bitmap is valid and has content
if (object_bitmap.width() > 0 && object_bitmap.height() > 0) {
object_bitmap.SetPalette(palette);
core::Renderer::Get().RenderBitmap(&object_bitmap);
// Queue texture creation for the object bitmap via Arena's deferred system
gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::CREATE, &object_bitmap);
canvas_.DrawBitmap(object_bitmap, canvas_x, canvas_y, 1.0f, 255);
return;
}
@@ -656,7 +655,10 @@ absl::Status DungeonCanvasViewer::UpdateRoomBackgroundLayers(int room_id) {
if (current_palette_id_ < current_palette_group_.size()) {
gfx::Arena::Get().gfx_sheets()[block].SetPaletteWithTransparent(
current_palette_group_[current_palette_id_], 0);
core::Renderer::Get().UpdateBitmap(&gfx::Arena::Get().gfx_sheets()[block]);
// Queue texture update via Arena's deferred system
gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::UPDATE,
&gfx::Arena::Get().gfx_sheets()[block]);
}
}
}
@@ -671,7 +673,10 @@ absl::Status DungeonCanvasViewer::UpdateRoomBackgroundLayers(int room_id) {
if (block >= 0 && block < gfx::Arena::Get().gfx_sheets().size()) {
gfx::Arena::Get().gfx_sheets()[block].SetPaletteWithTransparent(
sprites_aux1_pal_group[current_palette_id_], 0);
core::Renderer::Get().UpdateBitmap(&gfx::Arena::Get().gfx_sheets()[block]);
// Queue texture update via Arena's deferred system
gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::UPDATE,
&gfx::Arena::Get().gfx_sheets()[block]);
}
}
}
@@ -691,14 +696,9 @@ void DungeonCanvasViewer::RenderRoomBackgroundLayers(int room_id) {
if (bg1_bitmap.is_active() && bg1_bitmap.width() > 0 && bg1_bitmap.height() > 0) {
if (!bg1_bitmap.texture()) {
core::Renderer::Get().RenderBitmap(&bg1_bitmap);
}
// DEBUG: Check SDL texture format
Uint32 format;
int access, w, h;
if (SDL_QueryTexture(bg1_bitmap.texture(), &format, &access, &w, &h) == 0) {
const char* format_name_cstr = SDL_GetPixelFormatName(format);
// Queue texture creation for background layer 1 via Arena's deferred system
gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::CREATE, &bg1_bitmap);
}
canvas_.DrawBitmap(bg1_bitmap, 0, 0, 1.0f, 255);
@@ -706,7 +706,9 @@ void DungeonCanvasViewer::RenderRoomBackgroundLayers(int room_id) {
if (bg2_bitmap.is_active() && bg2_bitmap.width() > 0 && bg2_bitmap.height() > 0) {
if (!bg2_bitmap.texture()) {
core::Renderer::Get().RenderBitmap(&bg2_bitmap);
// Queue texture creation for background layer 2 via Arena's deferred system
gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::CREATE, &bg2_bitmap);
}
canvas_.DrawBitmap(bg2_bitmap, 0, 0, 1.0f, 200);
}

View File

@@ -101,7 +101,7 @@ class DungeonCanvasViewer {
int current_active_room_tab_ = 0;
// Object interaction state
bool object_interaction_enabled_ = false;
bool object_interaction_enabled_ = true;
// Palette data
uint64_t current_palette_group_id_ = 0;

View File

@@ -18,8 +18,6 @@
namespace yaze::editor {
using core::Renderer;
using ImGui::BeginTabBar;
using ImGui::BeginTabItem;
using ImGui::BeginTable;
@@ -185,11 +183,15 @@ absl::Status DungeonEditor::Save() {
}
absl::Status DungeonEditor::RefreshGraphics() {
// Update graphics sheet textures via Arena's deferred texture queue
std::for_each_n(
rooms_[current_room_id_].blocks().begin(), 8, [this](int block) {
gfx::Arena::Get().gfx_sheets()[block].SetPaletteWithTransparent(
current_palette_group_[current_palette_id_], 0);
Renderer::Get().UpdateBitmap(&gfx::Arena::Get().gfx_sheets()[block]);
// Queue texture update for the modified graphics sheet
gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::UPDATE,
&gfx::Arena::Get().gfx_sheets()[block]);
});
auto sprites_aux1_pal_group = rom()->palette_group().sprites_aux1;
@@ -198,7 +200,10 @@ absl::Status DungeonEditor::RefreshGraphics() {
[this, &sprites_aux1_pal_group](int block) {
gfx::Arena::Get().gfx_sheets()[block].SetPaletteWithTransparent(
sprites_aux1_pal_group[current_palette_id_], 0);
Renderer::Get().UpdateBitmap(&gfx::Arena::Get().gfx_sheets()[block]);
// Queue texture update for the modified graphics sheet
gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::UPDATE,
&gfx::Arena::Get().gfx_sheets()[block]);
});
return absl::OkStatus();
}

View File

@@ -15,7 +15,9 @@ namespace yaze::editor {
// No table layout needed - all cards are independent
void DungeonEditorV2::Initialize() {
void DungeonEditorV2::Initialize(gfx::IRenderer* renderer, Rom* rom) {
renderer_ = renderer;
rom_ = rom;
// Don't initialize emulator preview yet - ROM might not be loaded
// Will be initialized in Load() instead
@@ -24,6 +26,8 @@ void DungeonEditorV2::Initialize() {
room_window_class_.DockingAllowUnclassed = false; // Room windows dock together
}
void DungeonEditorV2::Initialize() {}
absl::Status DungeonEditorV2::Load() {
if (!rom_ || !rom_->is_loaded()) {
return absl::FailedPreconditionError("ROM not loaded");
@@ -55,13 +59,13 @@ absl::Status DungeonEditorV2::Load() {
object_selector_.set_rooms(&rooms_);
// NOW initialize emulator preview with loaded ROM
object_emulator_preview_.Initialize(rom_);
object_emulator_preview_.Initialize(renderer_, rom_);
// Initialize palette editor with loaded ROM
palette_editor_.Initialize(rom_);
// Initialize unified object editor card
object_editor_card_ = std::make_unique<ObjectEditorCard>(rom_, &canvas_viewer_);
object_editor_card_ = std::make_unique<ObjectEditorCard>(renderer_, rom_, &canvas_viewer_);
// Wire palette changes to trigger room re-renders
palette_editor_.SetOnPaletteChanged([this](int /*palette_id*/) {
@@ -97,7 +101,8 @@ absl::Status DungeonEditorV2::Update() {
"This parent window can be minimized or closed.");
// Render all independent cards (these create their own top-level windows)
object_emulator_preview_.Render();
// NOTE: Emulator preview is now integrated into ObjectEditorCard
// object_emulator_preview_.Render(); // Removed - causing performance issues
DrawLayout();
return absl::OkStatus();
@@ -148,11 +153,7 @@ void DungeonEditorV2::DrawToolset() {
toolbar.AddSeparator();
if (toolbar.AddToggle(ICON_MD_CATEGORY, &show_object_selector_, "Toggle Object Selector (Legacy)")) {
// Toggled
}
if (toolbar.AddToggle(ICON_MD_CONSTRUCTION, &show_object_editor_, "Toggle Object Editor (Unified)")) {
if (toolbar.AddToggle(ICON_MD_CONSTRUCTION, &show_object_editor_, "Toggle Object Editor")) {
// Toggled
}
@@ -186,18 +187,7 @@ void DungeonEditorV2::DrawLayout() {
DrawRoomGraphicsCard();
}
// 4. Legacy Object Selector Card (independent, dockable)
if (show_object_selector_) {
gui::EditorCard object_card(
MakeCardTitle("Object Selector").c_str(),
ICON_MD_CATEGORY, &show_object_selector_);
if (object_card.Begin()) {
object_selector_.Draw();
}
object_card.End();
}
// 4b. Unified Object Editor Card (new, combines selector + preview + interaction)
// 4. Unified Object Editor Card
if (show_object_editor_ && object_editor_card_) {
object_editor_card_->Draw(&show_object_editor_);
}
@@ -234,11 +224,16 @@ void DungeonEditorV2::DrawLayout() {
room_cards_[room_id] = std::make_shared<gui::EditorCard>(
card_name_str.c_str(), ICON_MD_GRID_ON, &open);
room_cards_[room_id]->SetDefaultSize(700, 600);
// Set default position for first room to be docked with main window
if (active_rooms_.Size == 1) {
room_cards_[room_id]->SetPosition(gui::EditorCard::Position::Floating);
}
}
auto& room_card = room_cards_[room_id];
// Use docking class to make room cards dock together
// CRITICAL: Use docking class BEFORE Begin() to make rooms tab together
ImGui::SetNextWindowClass(&room_window_class_);
if (room_card->Begin(&open)) {
@@ -500,15 +495,15 @@ void DungeonEditorV2::DrawRoomMatrixCard() {
MakeCardTitle("Room Matrix").c_str(),
ICON_MD_GRID_VIEW, &show_room_matrix_);
matrix_card.SetDefaultSize(520, 620);
matrix_card.SetDefaultSize(440, 520);
if (matrix_card.Begin()) {
// 16 wide x 19 tall = 304 cells (295 rooms + 9 empty)
// 16 wide x 19 tall = 304 cells (296 rooms + 8 empty)
constexpr int kRoomsPerRow = 16;
constexpr int kRoomsPerCol = 19;
constexpr int kTotalRooms = 0x128; // 296 rooms (0x00-0x127)
constexpr float kRoomCellSize = 28.0f; // Compact cells
constexpr float kCellSpacing = 2.0f;
constexpr float kRoomCellSize = 24.0f; // Smaller cells like ZScream
constexpr float kCellSpacing = 1.0f; // Tighter spacing
ImDrawList* draw_list = ImGui::GetWindowDrawList();
ImVec2 canvas_pos = ImGui::GetCursorScreenPos();
@@ -527,30 +522,34 @@ void DungeonEditorV2::DrawRoomMatrixCard() {
cell_min.y + kRoomCellSize);
if (is_valid_room) {
// ALWAYS use palette-based color (lazy load if needed)
ImU32 bg_color = IM_COL32(60, 60, 70, 255); // Fallback
// Use simple deterministic color based on room ID (no loading needed)
ImU32 bg_color;
// Try to get palette color
uint8_t palette = 0;
if (room_id < static_cast<int>(rooms_.size())) {
// Lazy load room if needed to get palette
if (!rooms_[room_id].IsLoaded()) {
auto status = room_loader_.LoadRoom(room_id, rooms_[room_id]);
if (status.ok()) {
palette = rooms_[room_id].palette;
}
} else {
palette = rooms_[room_id].palette;
}
// Create a color variation based on palette ID
float r = 0.3f + (palette % 4) * 0.15f;
float g = 0.3f + ((palette / 4) % 4) * 0.15f;
float b = 0.4f + ((palette / 16) % 2) * 0.2f;
bg_color = IM_COL32(
static_cast<int>(r * 255),
static_cast<int>(g * 255),
static_cast<int>(b * 255), 255);
// Generate color from room ID - much faster than loading
int hue = (room_id * 37) % 360; // Distribute colors across spectrum
int saturation = 40 + (room_id % 3) * 15;
int brightness = 50 + (room_id % 5) * 10;
// Convert HSV to RGB
float h = hue / 60.0f;
float s = saturation / 100.0f;
float v = brightness / 100.0f;
int i = static_cast<int>(h);
float f = h - i;
int p = static_cast<int>(v * (1 - s) * 255);
int q = static_cast<int>(v * (1 - s * f) * 255);
int t = static_cast<int>(v * (1 - s * (1 - f)) * 255);
int val = static_cast<int>(v * 255);
switch (i % 6) {
case 0: bg_color = IM_COL32(val, t, p, 255); break;
case 1: bg_color = IM_COL32(q, val, p, 255); break;
case 2: bg_color = IM_COL32(p, val, t, 255); break;
case 3: bg_color = IM_COL32(p, q, val, 255); break;
case 4: bg_color = IM_COL32(t, p, val, 255); break;
case 5: bg_color = IM_COL32(val, p, q, 255); break;
default: bg_color = IM_COL32(60, 60, 70, 255); break;
}
// Check if room is currently selected
@@ -604,7 +603,7 @@ void DungeonEditorV2::DrawRoomMatrixCard() {
OnRoomSelected(room_id);
}
// Hover tooltip with room name
// Hover tooltip with room name and status
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
if (room_id < static_cast<int>(std::size(zelda3::kRoomNames))) {
@@ -612,9 +611,8 @@ void DungeonEditorV2::DrawRoomMatrixCard() {
} else {
ImGui::Text("Room %03X", room_id);
}
ImGui::Text("Palette: %02X",
room_id < static_cast<int>(rooms_.size()) ?
rooms_[room_id].palette : 0);
ImGui::Text("Status: %s", is_open ? "Open" : is_current ? "Current" : "Closed");
ImGui::Text("Click to %s", is_open ? "focus" : "open");
ImGui::EndTooltip();
}
} else {

View File

@@ -51,8 +51,9 @@ class DungeonEditorV2 : public Editor {
}
// Editor interface
void Initialize(gfx::IRenderer* renderer, Rom* rom);
void Initialize() override;
absl::Status Load() override;
absl::Status Load();
absl::Status Update() override;
absl::Status Undo() override { return absl::UnimplementedError("Undo"); }
absl::Status Redo() override { return absl::UnimplementedError("Redo"); }
@@ -69,7 +70,7 @@ class DungeonEditorV2 : public Editor {
room_selector_.set_rom(rom);
canvas_viewer_.SetRom(rom);
object_selector_.SetRom(rom);
object_emulator_preview_.Initialize(rom);
object_emulator_preview_.Initialize(renderer_, rom);
}
Rom* rom() const { return rom_; }
@@ -86,6 +87,7 @@ class DungeonEditorV2 : public Editor {
}
private:
gfx::IRenderer* renderer_ = nullptr;
// Simple UI layout
void DrawLayout();
void DrawRoomTab(int room_id);
@@ -117,8 +119,7 @@ class DungeonEditorV2 : public Editor {
bool show_room_matrix_ = false;
bool show_entrances_list_ = false;
bool show_room_graphics_ = false; // Room graphics card
bool show_object_selector_ = true; // Legacy object selector
bool show_object_editor_ = true; // New unified object editor card
bool show_object_editor_ = true; // Unified object editor card
bool show_palette_editor_ = true;
// Palette management

View File

@@ -150,25 +150,59 @@ void DungeonObjectInteraction::SelectObjectsInRect() {
selected_object_indices_.push_back(i);
}
}
}
void DungeonObjectInteraction::DrawSelectionHighlights() {
if (!rooms_ || current_room_id_ < 0 || current_room_id_ >= 296) return;
// Highlight selected objects
if (!selected_object_indices_.empty()) {
for (size_t index : selected_object_indices_) {
if (index < objects.size()) {
const auto& object = objects[index];
auto [canvas_x, canvas_y] =
RoomToCanvasCoordinates(object.x_, object.y_);
// Draw selection highlight
ImDrawList* draw_list = ImGui::GetWindowDrawList();
ImVec2 canvas_pos = canvas_->zero_point();
ImVec2 obj_start(canvas_pos.x + canvas_x - 2,
canvas_pos.y + canvas_y - 2);
ImVec2 obj_end(canvas_pos.x + canvas_x + 18,
canvas_pos.y + canvas_y + 18);
draw_list->AddRect(obj_start, obj_end, IM_COL32(0, 255, 255, 255), 0.0f,
0, 2.0f);
}
auto& room = (*rooms_)[current_room_id_];
const auto& objects = room.GetTileObjects();
// Draw highlights for all selected objects
ImDrawList* draw_list = ImGui::GetWindowDrawList();
ImVec2 canvas_pos = canvas_->zero_point();
for (size_t index : selected_object_indices_) {
if (index < objects.size()) {
const auto& object = objects[index];
auto [canvas_x, canvas_y] = RoomToCanvasCoordinates(object.x_, object.y_);
// Calculate object size for highlight
int obj_width = 8 + (object.size_ & 0x0F) * 4;
int obj_height = 8 + ((object.size_ >> 4) & 0x0F) * 4;
obj_width = std::min(obj_width, 64);
obj_height = std::min(obj_height, 64);
// Draw cyan selection highlight
ImVec2 obj_start(canvas_pos.x + canvas_x - 2,
canvas_pos.y + canvas_y - 2);
ImVec2 obj_end(canvas_pos.x + canvas_x + obj_width + 2,
canvas_pos.y + canvas_y + obj_height + 2);
// Animated selection (pulsing effect)
float pulse = 0.7f + 0.3f * std::sin(static_cast<float>(ImGui::GetTime()) * 4.0f);
draw_list->AddRect(obj_start, obj_end,
IM_COL32(0, static_cast<int>(255 * pulse), 255, 255),
0.0f, 0, 2.5f);
// Draw corner handles for selected objects
constexpr float handle_size = 4.0f;
draw_list->AddRectFilled(
ImVec2(obj_start.x - handle_size/2, obj_start.y - handle_size/2),
ImVec2(obj_start.x + handle_size/2, obj_start.y + handle_size/2),
IM_COL32(0, 255, 255, 255));
draw_list->AddRectFilled(
ImVec2(obj_end.x - handle_size/2, obj_start.y - handle_size/2),
ImVec2(obj_end.x + handle_size/2, obj_start.y + handle_size/2),
IM_COL32(0, 255, 255, 255));
draw_list->AddRectFilled(
ImVec2(obj_start.x - handle_size/2, obj_end.y - handle_size/2),
ImVec2(obj_start.x + handle_size/2, obj_end.y + handle_size/2),
IM_COL32(0, 255, 255, 255));
draw_list->AddRectFilled(
ImVec2(obj_end.x - handle_size/2, obj_end.y - handle_size/2),
ImVec2(obj_end.x + handle_size/2, obj_end.y + handle_size/2),
IM_COL32(0, 255, 255, 255));
}
}
}

View File

@@ -30,6 +30,7 @@ class DungeonObjectInteraction {
// Selection rectangle (like OverworldEditor)
void DrawObjectSelectRect();
void SelectObjectsInRect();
void DrawSelectionHighlights(); // Draw highlights for selected objects
// Drag and select box functionality
void DrawSelectBox();

View File

@@ -90,7 +90,9 @@ void DungeonObjectSelector::DrawObjectRenderer() {
auto preview_bitmap = std::move(preview_result.value());
if (preview_bitmap.width() > 0 && preview_bitmap.height() > 0) {
preview_bitmap.SetPalette(preview_palette_);
core::Renderer::Get().RenderBitmap(&preview_bitmap);
// Queue texture creation via Arena's deferred system
gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::CREATE, &preview_bitmap);
object_canvas_.DrawBitmap(preview_bitmap, preview_x, preview_y, 1.0f, 255);
} else {
// Fallback: Draw primitive shape

View File

@@ -1,14 +1,10 @@
#include "dungeon_renderer.h"
#include "absl/strings/str_format.h"
#include "app/core/window.h"
#include "app/gfx/arena.h"
#include "app/gui/color.h"
namespace yaze::editor {
using core::Renderer;
void DungeonRenderer::RenderObjectInCanvas(const zelda3::RoomObject& object,
const gfx::SnesPalette& palette) {
// Validate ROM is loaded
@@ -55,7 +51,9 @@ void DungeonRenderer::RenderObjectInCanvas(const zelda3::RoomObject& object,
if (object_bitmap.width() > 0 && object_bitmap.height() > 0 &&
object_bitmap.data() != nullptr) {
object_bitmap.SetPalette(palette);
core::Renderer::Get().RenderBitmap(&object_bitmap);
// Queue texture creation for the object bitmap via Arena's deferred system
gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::CREATE, &object_bitmap);
canvas_->DrawBitmap(object_bitmap, canvas_x, canvas_y, 1.0f, 255);
// Cache the successfully rendered bitmap
ObjectRenderCache cache_entry;

View File

@@ -1,15 +1,16 @@
#include "object_editor_card.h"
#include "absl/strings/str_format.h"
#include "app/gfx/backend/irenderer.h"
#include "app/gui/icons.h"
#include "app/gui/ui_helpers.h"
#include "imgui/imgui.h"
namespace yaze::editor {
ObjectEditorCard::ObjectEditorCard(Rom* rom, DungeonCanvasViewer* canvas_viewer)
: rom_(rom), canvas_viewer_(canvas_viewer), object_selector_(rom) {
emulator_preview_.Initialize(rom);
ObjectEditorCard::ObjectEditorCard(gfx::IRenderer* renderer, Rom* rom, DungeonCanvasViewer* canvas_viewer)
: renderer_(renderer), rom_(rom), canvas_viewer_(canvas_viewer), object_selector_(rom) {
emulator_preview_.Initialize(renderer, rom);
}
void ObjectEditorCard::Draw(bool* p_open) {
@@ -93,7 +94,7 @@ void ObjectEditorCard::DrawObjectSelector() {
ImGui::Separator();
// Object list with categories
if (ImGui::BeginChild("##ObjectList", ImVec2(0, -100), true)) {
if (ImGui::BeginChild("##ObjectList", ImVec2(0, 0), true)) {
// Floor objects
if (ImGui::CollapsingHeader(ICON_MD_GRID_ON " Floor Objects",
ImGuiTreeNodeFlags_DefaultOpen)) {
@@ -268,8 +269,10 @@ void ObjectEditorCard::DrawObjectPreviewIcon(int object_id, const ImVec2& size)
void ObjectEditorCard::DrawSelectedObjectInfo() {
ImGui::BeginGroup();
// Show current object for placement
ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f),
ICON_MD_INFO " Current Object:");
ICON_MD_INFO " Current:");
if (has_preview_object_) {
ImGui::SameLine();
@@ -278,14 +281,35 @@ void ObjectEditorCard::DrawSelectedObjectInfo() {
ImGui::Text("Layer: %s",
preview_object_.layer_ == zelda3::RoomObject::BG1 ? "BG1" :
preview_object_.layer_ == zelda3::RoomObject::BG2 ? "BG2" : "BG3");
ImGui::SameLine();
ImGui::Text("Mode: %s",
interaction_mode_ == InteractionMode::Place ? "Place" :
interaction_mode_ == InteractionMode::Select ? "Select" :
interaction_mode_ == InteractionMode::Delete ? "Delete" : "None");
} else {
ImGui::SameLine();
ImGui::TextDisabled("None selected");
ImGui::TextDisabled("None");
}
// Show selection count
auto& interaction = canvas_viewer_->object_interaction();
const auto& selected = interaction.GetSelectedObjectIndices();
ImGui::SameLine();
ImGui::Text("|");
ImGui::SameLine();
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.4f, 1.0f),
ICON_MD_CHECKLIST " Selected: %zu", selected.size());
ImGui::SameLine();
ImGui::Text("|");
ImGui::SameLine();
ImGui::Text("Mode: %s",
interaction_mode_ == InteractionMode::Place ? ICON_MD_ADD_BOX " Place" :
interaction_mode_ == InteractionMode::Select ? ICON_MD_CHECK_BOX " Select" :
interaction_mode_ == InteractionMode::Delete ? ICON_MD_DELETE " Delete" : "None");
// Show quick actions for selections
if (!selected.empty()) {
ImGui::SameLine();
if (ImGui::SmallButton(ICON_MD_CLEAR " Clear")) {
interaction.ClearSelection();
}
}
ImGui::EndGroup();

View File

@@ -5,6 +5,7 @@
#include <unordered_map>
#include "app/editor/dungeon/dungeon_canvas_viewer.h"
#include "app/gfx/backend/irenderer.h"
#include "app/gui/canvas.h"
#include "app/editor/dungeon/dungeon_object_selector.h"
#include "app/gui/editor_layout.h"
@@ -27,7 +28,7 @@ namespace editor {
*/
class ObjectEditorCard {
public:
ObjectEditorCard(Rom* rom, DungeonCanvasViewer* canvas_viewer);
ObjectEditorCard(gfx::IRenderer* renderer, Rom* rom, DungeonCanvasViewer* canvas_viewer);
// Main update function
void Draw(bool* p_open);
@@ -59,7 +60,7 @@ class ObjectEditorCard {
// UI state
int selected_tab_ = 0;
bool show_emulator_preview_ = true;
bool show_emulator_preview_ = false; // Disabled by default for performance
bool show_object_list_ = true;
bool show_interaction_controls_ = true;
@@ -75,6 +76,7 @@ class ObjectEditorCard {
// Selected object for placement
zelda3::RoomObject preview_object_{0, 0, 0, 0, 0};
bool has_preview_object_ = false;
gfx::IRenderer* renderer_;
};
} // namespace editor