fix: apply clang-format to all source files
Fixes formatting violations that were causing CI failures. Applied clang-format-14 to ensure consistent code formatting across the codebase. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -3,12 +3,12 @@
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "app/gfx/types/snes_palette.h"
|
||||
#include "app/gui/canvas/canvas.h"
|
||||
#include "app/rom.h"
|
||||
#include "zelda3/dungeon/room.h"
|
||||
#include "app/gfx/types/snes_palette.h"
|
||||
#include "dungeon_object_interaction.h"
|
||||
#include "imgui/imgui.h"
|
||||
#include "zelda3/dungeon/room.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace editor {
|
||||
@@ -29,10 +29,8 @@ class DungeonCanvasViewer {
|
||||
// DrawDungeonTabView() removed - using EditorCard system instead
|
||||
void DrawDungeonCanvas(int room_id);
|
||||
void Draw(int room_id);
|
||||
|
||||
void SetRom(Rom* rom) {
|
||||
rom_ = rom;
|
||||
}
|
||||
|
||||
void SetRom(Rom* rom) { rom_ = rom; }
|
||||
Rom* rom() const { return rom_; }
|
||||
|
||||
// Room data access
|
||||
@@ -42,70 +40,80 @@ class DungeonCanvasViewer {
|
||||
void set_current_active_room_tab(int tab) { current_active_room_tab_ = tab; }
|
||||
|
||||
// Palette access
|
||||
void set_current_palette_group_id(uint64_t id) { current_palette_group_id_ = id; }
|
||||
void set_current_palette_group_id(uint64_t id) {
|
||||
current_palette_group_id_ = id;
|
||||
}
|
||||
void SetCurrentPaletteId(uint64_t id) { current_palette_id_ = id; }
|
||||
void SetCurrentPaletteGroup(const gfx::PaletteGroup& group) { current_palette_group_ = group; }
|
||||
void SetCurrentPaletteGroup(const gfx::PaletteGroup& group) {
|
||||
current_palette_group_ = group;
|
||||
}
|
||||
|
||||
// Canvas access
|
||||
gui::Canvas& canvas() { return canvas_; }
|
||||
const gui::Canvas& canvas() const { return canvas_; }
|
||||
|
||||
|
||||
// Object interaction access
|
||||
DungeonObjectInteraction& object_interaction() { return object_interaction_; }
|
||||
|
||||
|
||||
// Enable/disable object interaction mode
|
||||
void SetObjectInteractionEnabled(bool enabled) { object_interaction_enabled_ = enabled; }
|
||||
bool IsObjectInteractionEnabled() const { return object_interaction_enabled_; }
|
||||
|
||||
void SetObjectInteractionEnabled(bool enabled) {
|
||||
object_interaction_enabled_ = enabled;
|
||||
}
|
||||
bool IsObjectInteractionEnabled() const {
|
||||
return object_interaction_enabled_;
|
||||
}
|
||||
|
||||
// Layer visibility controls (per-room)
|
||||
void SetBG1Visible(int room_id, bool visible) {
|
||||
GetRoomLayerSettings(room_id).bg1_visible = visible;
|
||||
void SetBG1Visible(int room_id, bool visible) {
|
||||
GetRoomLayerSettings(room_id).bg1_visible = visible;
|
||||
}
|
||||
void SetBG2Visible(int room_id, bool visible) {
|
||||
GetRoomLayerSettings(room_id).bg2_visible = visible;
|
||||
void SetBG2Visible(int room_id, bool visible) {
|
||||
GetRoomLayerSettings(room_id).bg2_visible = visible;
|
||||
}
|
||||
bool IsBG1Visible(int room_id) const {
|
||||
bool IsBG1Visible(int room_id) const {
|
||||
auto it = room_layer_settings_.find(room_id);
|
||||
return it != room_layer_settings_.end() ? it->second.bg1_visible : true;
|
||||
}
|
||||
bool IsBG2Visible(int room_id) const {
|
||||
bool IsBG2Visible(int room_id) const {
|
||||
auto it = room_layer_settings_.find(room_id);
|
||||
return it != room_layer_settings_.end() ? it->second.bg2_visible : true;
|
||||
}
|
||||
|
||||
|
||||
// BG2 layer type controls (per-room)
|
||||
void SetBG2LayerType(int room_id, int type) {
|
||||
GetRoomLayerSettings(room_id).bg2_layer_type = type;
|
||||
void SetBG2LayerType(int room_id, int type) {
|
||||
GetRoomLayerSettings(room_id).bg2_layer_type = type;
|
||||
}
|
||||
int GetBG2LayerType(int room_id) const {
|
||||
int GetBG2LayerType(int room_id) const {
|
||||
auto it = room_layer_settings_.find(room_id);
|
||||
return it != room_layer_settings_.end() ? it->second.bg2_layer_type : 0;
|
||||
}
|
||||
|
||||
|
||||
// Set the object to be placed
|
||||
void SetPreviewObject(const zelda3::RoomObject& object) {
|
||||
object_interaction_.SetPreviewObject(object, true);
|
||||
}
|
||||
void ClearPreviewObject() {
|
||||
object_interaction_.SetPreviewObject(zelda3::RoomObject{0, 0, 0, 0, 0}, false);
|
||||
object_interaction_.SetPreviewObject(zelda3::RoomObject{0, 0, 0, 0, 0},
|
||||
false);
|
||||
}
|
||||
|
||||
private:
|
||||
void DisplayObjectInfo(const zelda3::RoomObject &object, int canvas_x,
|
||||
void DisplayObjectInfo(const zelda3::RoomObject& object, int canvas_x,
|
||||
int canvas_y);
|
||||
void RenderSprites(const zelda3::Room& room);
|
||||
|
||||
|
||||
// Coordinate conversion helpers
|
||||
std::pair<int, int> RoomToCanvasCoordinates(int room_x, int room_y) const;
|
||||
std::pair<int, int> CanvasToRoomCoordinates(int canvas_x, int canvas_y) const;
|
||||
bool IsWithinCanvasBounds(int canvas_x, int canvas_y, int margin = 32) const;
|
||||
|
||||
|
||||
// Object dimension calculation
|
||||
void CalculateWallDimensions(const zelda3::RoomObject& object, int& width, int& height);
|
||||
|
||||
void CalculateWallDimensions(const zelda3::RoomObject& object, int& width,
|
||||
int& height);
|
||||
|
||||
// Visualization
|
||||
void DrawObjectPositionOutlines(const zelda3::Room& room);
|
||||
|
||||
|
||||
// Room graphics management
|
||||
// Load: Read from ROM, Render: Process pixels, Draw: Display on canvas
|
||||
absl::Status LoadAndRenderRoomGraphics(int room_id);
|
||||
@@ -115,16 +123,16 @@ class DungeonCanvasViewer {
|
||||
gui::Canvas canvas_{"##DungeonCanvas", ImVec2(0x200, 0x200)};
|
||||
// ObjectRenderer removed - use ObjectDrawer for rendering (production system)
|
||||
DungeonObjectInteraction object_interaction_;
|
||||
|
||||
|
||||
// Room data
|
||||
std::array<zelda3::Room, 0x128>* rooms_ = nullptr;
|
||||
// Used by overworld editor for double-click entrance → open dungeon room
|
||||
ImVector<int> active_rooms_;
|
||||
int current_active_room_tab_ = 0;
|
||||
|
||||
|
||||
// Object interaction state
|
||||
bool object_interaction_enabled_ = true;
|
||||
|
||||
|
||||
// Per-room layer visibility settings
|
||||
struct RoomLayerSettings {
|
||||
bool bg1_visible = true;
|
||||
@@ -132,12 +140,12 @@ class DungeonCanvasViewer {
|
||||
int bg2_layer_type = 0; // 0=Normal, 1=Translucent, 2=Addition, etc.
|
||||
};
|
||||
std::map<int, RoomLayerSettings> room_layer_settings_;
|
||||
|
||||
|
||||
// Helper to get settings for a room (creates default if not exists)
|
||||
RoomLayerSettings& GetRoomLayerSettings(int room_id) {
|
||||
return room_layer_settings_[room_id];
|
||||
}
|
||||
|
||||
|
||||
// Palette data
|
||||
uint64_t current_palette_group_id_ = 0;
|
||||
uint64_t current_palette_id_ = 0;
|
||||
@@ -153,13 +161,13 @@ class DungeonCanvasViewer {
|
||||
};
|
||||
std::vector<ObjectRenderCache> object_render_cache_;
|
||||
uint64_t last_palette_hash_ = 0;
|
||||
|
||||
|
||||
// Debug state flags
|
||||
bool show_room_debug_info_ = false;
|
||||
bool show_texture_debug_ = false;
|
||||
bool show_object_bounds_ = false;
|
||||
bool show_layer_info_ = false;
|
||||
int layout_override_ = -1; // -1 for no override
|
||||
int layout_override_ = -1; // -1 for no override
|
||||
|
||||
// Object outline filtering toggles
|
||||
struct ObjectOutlineToggles {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,18 +8,18 @@
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "app/editor/editor.h"
|
||||
#include "app/gfx/types/snes_palette.h"
|
||||
#include "app/rom.h"
|
||||
#include "dungeon_room_selector.h"
|
||||
#include "dungeon_canvas_viewer.h"
|
||||
#include "dungeon_object_selector.h"
|
||||
#include "dungeon_room_loader.h"
|
||||
#include "object_editor_card.h"
|
||||
#include "zelda3/dungeon/room.h"
|
||||
#include "zelda3/dungeon/room_entrance.h"
|
||||
#include "app/gui/app/editor_layout.h"
|
||||
#include "app/gui/widgets/dungeon_object_emulator_preview.h"
|
||||
#include "app/gui/widgets/palette_editor_widget.h"
|
||||
#include "app/rom.h"
|
||||
#include "dungeon_canvas_viewer.h"
|
||||
#include "dungeon_object_selector.h"
|
||||
#include "dungeon_room_loader.h"
|
||||
#include "dungeon_room_selector.h"
|
||||
#include "imgui/imgui.h"
|
||||
#include "object_editor_card.h"
|
||||
#include "zelda3/dungeon/room.h"
|
||||
#include "zelda3/dungeon/room_entrance.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace editor {
|
||||
@@ -81,20 +81,23 @@ class DungeonEditorV2 : public Editor {
|
||||
// ROM state
|
||||
bool IsRomLoaded() const override { return rom_ && rom_->is_loaded(); }
|
||||
std::string GetRomStatus() const override {
|
||||
if (!rom_) return "No ROM loaded";
|
||||
if (!rom_->is_loaded()) return "ROM failed to load";
|
||||
if (!rom_)
|
||||
return "No ROM loaded";
|
||||
if (!rom_->is_loaded())
|
||||
return "ROM failed to load";
|
||||
return absl::StrFormat("ROM loaded: %s", rom_->title());
|
||||
}
|
||||
|
||||
// Card visibility flags - Public for command-line flag access
|
||||
bool show_room_selector_ = false; // Room selector/list card
|
||||
bool show_room_matrix_ = false; // Dungeon matrix layout
|
||||
bool show_entrances_list_ = false; // Entrance list card (renamed from entrances_matrix_)
|
||||
bool show_room_graphics_ = false; // Room graphics card
|
||||
bool show_object_editor_ = false; // Object editor card
|
||||
bool show_palette_editor_ = false; // Palette editor card
|
||||
bool show_debug_controls_ = false; // Debug controls card
|
||||
bool show_control_panel_ = true; // Control panel (visible by default)
|
||||
bool show_room_selector_ = false; // Room selector/list card
|
||||
bool show_room_matrix_ = false; // Dungeon matrix layout
|
||||
bool show_entrances_list_ =
|
||||
false; // Entrance list card (renamed from entrances_matrix_)
|
||||
bool show_room_graphics_ = false; // Room graphics card
|
||||
bool show_object_editor_ = false; // Object editor card
|
||||
bool show_palette_editor_ = false; // Palette editor card
|
||||
bool show_debug_controls_ = false; // Debug controls card
|
||||
bool show_control_panel_ = true; // Control panel (visible by default)
|
||||
|
||||
private:
|
||||
gfx::IRenderer* renderer_ = nullptr;
|
||||
@@ -106,10 +109,10 @@ class DungeonEditorV2 : public Editor {
|
||||
void DrawEntrancesListCard();
|
||||
void DrawRoomGraphicsCard();
|
||||
void DrawDebugControlsCard();
|
||||
|
||||
|
||||
// Texture processing (critical for rendering)
|
||||
void ProcessDeferredTextures();
|
||||
|
||||
|
||||
// Room selection callback
|
||||
void OnRoomSelected(int room_id);
|
||||
void OnEntranceSelected(int entrance_id);
|
||||
@@ -118,23 +121,23 @@ class DungeonEditorV2 : public Editor {
|
||||
Rom* rom_;
|
||||
std::array<zelda3::Room, 0x128> rooms_;
|
||||
std::array<zelda3::RoomEntrance, 0x8C> entrances_;
|
||||
|
||||
|
||||
// Current selection state
|
||||
int current_entrance_id_ = 0;
|
||||
|
||||
|
||||
// Active room tabs and card tracking for jump-to
|
||||
ImVector<int> active_rooms_;
|
||||
std::unordered_map<int, std::shared_ptr<gui::EditorCard>> room_cards_;
|
||||
int current_room_id_ = 0;
|
||||
|
||||
|
||||
bool control_panel_minimized_ = false;
|
||||
|
||||
|
||||
// Palette management
|
||||
gfx::SnesPalette current_palette_;
|
||||
gfx::PaletteGroup current_palette_group_;
|
||||
uint64_t current_palette_id_ = 0;
|
||||
uint64_t current_palette_group_id_ = 0;
|
||||
|
||||
|
||||
// Components - these do all the work
|
||||
DungeonRoomLoader room_loader_;
|
||||
DungeonRoomSelector room_selector_;
|
||||
@@ -142,10 +145,11 @@ class DungeonEditorV2 : public Editor {
|
||||
DungeonObjectSelector object_selector_;
|
||||
gui::DungeonObjectEmulatorPreview object_emulator_preview_;
|
||||
gui::PaletteEditorWidget palette_editor_;
|
||||
std::unique_ptr<ObjectEditorCard> object_editor_card_; // Unified object editor
|
||||
|
||||
std::unique_ptr<ObjectEditorCard>
|
||||
object_editor_card_; // Unified object editor
|
||||
|
||||
bool is_loaded_ = false;
|
||||
|
||||
|
||||
// Docking class for room windows to dock together
|
||||
ImGuiWindowClass room_window_class_;
|
||||
};
|
||||
@@ -154,4 +158,3 @@ class DungeonEditorV2 : public Editor {
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_APP_EDITOR_DUNGEON_EDITOR_V2_H
|
||||
|
||||
|
||||
@@ -8,21 +8,21 @@ namespace yaze::editor {
|
||||
|
||||
void DungeonObjectInteraction::HandleCanvasMouseInput() {
|
||||
const ImGuiIO& io = ImGui::GetIO();
|
||||
|
||||
|
||||
// Check if mouse is over the canvas
|
||||
if (!canvas_->IsMouseHovering()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Get mouse position relative to canvas
|
||||
ImVec2 mouse_pos = io.MousePos;
|
||||
ImVec2 canvas_pos = canvas_->zero_point();
|
||||
ImVec2 canvas_size = canvas_->canvas_size();
|
||||
|
||||
|
||||
// Convert to canvas coordinates
|
||||
ImVec2 canvas_mouse_pos =
|
||||
ImVec2(mouse_pos.x - canvas_pos.x, mouse_pos.y - canvas_pos.y);
|
||||
|
||||
|
||||
// Handle mouse clicks
|
||||
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
|
||||
if (ImGui::IsKeyDown(ImGuiKey_LeftCtrl) ||
|
||||
@@ -48,18 +48,18 @@ void DungeonObjectInteraction::HandleCanvasMouseInput() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Handle mouse drag
|
||||
if (is_selecting_ && ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
|
||||
select_current_pos_ = canvas_mouse_pos;
|
||||
UpdateSelectedObjects();
|
||||
}
|
||||
|
||||
|
||||
if (is_dragging_ && ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
|
||||
drag_current_pos_ = canvas_mouse_pos;
|
||||
DrawDragPreview();
|
||||
}
|
||||
|
||||
|
||||
// Handle mouse release
|
||||
if (ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
|
||||
if (is_selecting_) {
|
||||
@@ -69,28 +69,31 @@ void DungeonObjectInteraction::HandleCanvasMouseInput() {
|
||||
if (is_dragging_) {
|
||||
is_dragging_ = false;
|
||||
// Apply drag transformation to selected objects
|
||||
if (!selected_object_indices_.empty() && rooms_ && current_room_id_ >= 0 && current_room_id_ < 296) {
|
||||
if (!selected_object_indices_.empty() && rooms_ &&
|
||||
current_room_id_ >= 0 && current_room_id_ < 296) {
|
||||
auto& room = (*rooms_)[current_room_id_];
|
||||
ImVec2 drag_delta = ImVec2(drag_current_pos_.x - drag_start_pos_.x,
|
||||
drag_current_pos_.y - drag_start_pos_.y);
|
||||
|
||||
|
||||
// Convert pixel delta to tile delta
|
||||
int tile_delta_x = static_cast<int>(drag_delta.x) / 8;
|
||||
int tile_delta_y = static_cast<int>(drag_delta.y) / 8;
|
||||
|
||||
|
||||
// Move all selected objects
|
||||
auto& objects = room.GetTileObjects();
|
||||
for (size_t index : selected_object_indices_) {
|
||||
if (index < objects.size()) {
|
||||
objects[index].x_ += tile_delta_x;
|
||||
objects[index].y_ += tile_delta_y;
|
||||
|
||||
|
||||
// Clamp to room bounds (64x64 tiles)
|
||||
objects[index].x_ = std::clamp(static_cast<int>(objects[index].x_), 0, 63);
|
||||
objects[index].y_ = std::clamp(static_cast<int>(objects[index].y_), 0, 63);
|
||||
objects[index].x_ =
|
||||
std::clamp(static_cast<int>(objects[index].x_), 0, 63);
|
||||
objects[index].y_ =
|
||||
std::clamp(static_cast<int>(objects[index].y_), 0, 63);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Trigger cache invalidation and re-render
|
||||
if (cache_invalidation_callback_) {
|
||||
cache_invalidation_callback_();
|
||||
@@ -103,7 +106,7 @@ void DungeonObjectInteraction::HandleCanvasMouseInput() {
|
||||
void DungeonObjectInteraction::CheckForObjectSelection() {
|
||||
// Draw object selection rectangle similar to OverworldEditor
|
||||
DrawObjectSelectRect();
|
||||
|
||||
|
||||
// Handle object selection when rectangle is active
|
||||
if (object_select_active_) {
|
||||
SelectObjectsInRect();
|
||||
@@ -111,16 +114,17 @@ void DungeonObjectInteraction::CheckForObjectSelection() {
|
||||
}
|
||||
|
||||
void DungeonObjectInteraction::DrawObjectSelectRect() {
|
||||
if (!canvas_->IsMouseHovering()) return;
|
||||
|
||||
if (!canvas_->IsMouseHovering())
|
||||
return;
|
||||
|
||||
const ImGuiIO& io = ImGui::GetIO();
|
||||
const ImVec2 canvas_pos = canvas_->zero_point();
|
||||
const ImVec2 mouse_pos =
|
||||
ImVec2(io.MousePos.x - canvas_pos.x, io.MousePos.y - canvas_pos.y);
|
||||
|
||||
|
||||
static bool dragging = false;
|
||||
static ImVec2 drag_start_pos;
|
||||
|
||||
|
||||
// Right click to start object selection
|
||||
if (ImGui::IsMouseClicked(ImGuiMouseButton_Right) && !object_loaded_) {
|
||||
drag_start_pos = mouse_pos;
|
||||
@@ -129,24 +133,24 @@ void DungeonObjectInteraction::DrawObjectSelectRect() {
|
||||
object_select_active_ = false;
|
||||
dragging = false;
|
||||
}
|
||||
|
||||
|
||||
// Right drag to create selection rectangle
|
||||
if (ImGui::IsMouseDragging(ImGuiMouseButton_Right) && !object_loaded_) {
|
||||
object_select_end_ = mouse_pos;
|
||||
dragging = true;
|
||||
|
||||
|
||||
// Draw selection rectangle
|
||||
ImVec2 start =
|
||||
ImVec2(canvas_pos.x + std::min(drag_start_pos.x, mouse_pos.x),
|
||||
canvas_pos.y + std::min(drag_start_pos.y, mouse_pos.y));
|
||||
ImVec2 end = ImVec2(canvas_pos.x + std::max(drag_start_pos.x, mouse_pos.x),
|
||||
canvas_pos.y + std::max(drag_start_pos.y, mouse_pos.y));
|
||||
|
||||
|
||||
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
||||
draw_list->AddRect(start, end, IM_COL32(255, 255, 0, 255), 0.0f, 0, 2.0f);
|
||||
draw_list->AddRectFilled(start, end, IM_COL32(255, 255, 0, 32));
|
||||
}
|
||||
|
||||
|
||||
// Complete selection on mouse release
|
||||
if (dragging && !ImGui::IsMouseDown(ImGuiMouseButton_Right)) {
|
||||
dragging = false;
|
||||
@@ -156,11 +160,12 @@ void DungeonObjectInteraction::DrawObjectSelectRect() {
|
||||
}
|
||||
|
||||
void DungeonObjectInteraction::SelectObjectsInRect() {
|
||||
if (!rooms_ || current_room_id_ < 0 || current_room_id_ >= 296) return;
|
||||
|
||||
if (!rooms_ || current_room_id_ < 0 || current_room_id_ >= 296)
|
||||
return;
|
||||
|
||||
auto& room = (*rooms_)[current_room_id_];
|
||||
selected_object_indices_.clear();
|
||||
|
||||
|
||||
// Calculate selection bounds in room coordinates
|
||||
auto [start_room_x, start_room_y] = CanvasToRoomCoordinates(
|
||||
static_cast<int>(std::min(object_select_start_.x, object_select_end_.x)),
|
||||
@@ -168,7 +173,7 @@ void DungeonObjectInteraction::SelectObjectsInRect() {
|
||||
auto [end_room_x, end_room_y] = CanvasToRoomCoordinates(
|
||||
static_cast<int>(std::max(object_select_start_.x, object_select_end_.x)),
|
||||
static_cast<int>(std::max(object_select_start_.y, object_select_end_.y)));
|
||||
|
||||
|
||||
// Find objects within selection rectangle
|
||||
const auto& objects = room.GetTileObjects();
|
||||
for (size_t i = 0; i < objects.size(); ++i) {
|
||||
@@ -181,79 +186,83 @@ void DungeonObjectInteraction::SelectObjectsInRect() {
|
||||
}
|
||||
|
||||
void DungeonObjectInteraction::DrawSelectionHighlights() {
|
||||
if (!rooms_ || current_room_id_ < 0 || current_room_id_ >= 296) return;
|
||||
|
||||
if (!rooms_ || current_room_id_ < 0 || current_room_id_ >= 296)
|
||||
return;
|
||||
|
||||
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);
|
||||
|
||||
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),
|
||||
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),
|
||||
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),
|
||||
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),
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DungeonObjectInteraction::PlaceObjectAtPosition(int room_x, int room_y) {
|
||||
if (!object_loaded_ || preview_object_.id_ < 0 || !rooms_) return;
|
||||
|
||||
if (current_room_id_ < 0 || current_room_id_ >= 296) return;
|
||||
|
||||
if (!object_loaded_ || preview_object_.id_ < 0 || !rooms_)
|
||||
return;
|
||||
|
||||
if (current_room_id_ < 0 || current_room_id_ >= 296)
|
||||
return;
|
||||
|
||||
// Create new object at the specified position
|
||||
auto new_object = preview_object_;
|
||||
new_object.x_ = room_x;
|
||||
new_object.y_ = room_y;
|
||||
|
||||
|
||||
// Add object to room
|
||||
auto& room = (*rooms_)[current_room_id_];
|
||||
room.AddTileObject(new_object);
|
||||
|
||||
|
||||
// Notify callback if set
|
||||
if (object_placed_callback_) {
|
||||
object_placed_callback_(new_object);
|
||||
}
|
||||
|
||||
|
||||
// Trigger cache invalidation
|
||||
if (cache_invalidation_callback_) {
|
||||
cache_invalidation_callback_();
|
||||
@@ -261,11 +270,12 @@ void DungeonObjectInteraction::PlaceObjectAtPosition(int room_x, int room_y) {
|
||||
}
|
||||
|
||||
void DungeonObjectInteraction::DrawSelectBox() {
|
||||
if (!is_selecting_) return;
|
||||
|
||||
if (!is_selecting_)
|
||||
return;
|
||||
|
||||
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
||||
ImVec2 canvas_pos = canvas_->zero_point();
|
||||
|
||||
|
||||
// Calculate select box bounds
|
||||
ImVec2 start = ImVec2(
|
||||
canvas_pos.x + std::min(select_start_pos_.x, select_current_pos_.x),
|
||||
@@ -273,58 +283,65 @@ void DungeonObjectInteraction::DrawSelectBox() {
|
||||
ImVec2 end = ImVec2(
|
||||
canvas_pos.x + std::max(select_start_pos_.x, select_current_pos_.x),
|
||||
canvas_pos.y + std::max(select_start_pos_.y, select_current_pos_.y));
|
||||
|
||||
|
||||
// Draw selection box
|
||||
draw_list->AddRect(start, end, IM_COL32(255, 255, 0, 255), 0.0f, 0, 2.0f);
|
||||
draw_list->AddRectFilled(start, end, IM_COL32(255, 255, 0, 32));
|
||||
}
|
||||
|
||||
void DungeonObjectInteraction::DrawDragPreview() {
|
||||
if (!is_dragging_ || selected_object_indices_.empty() || !rooms_) return;
|
||||
if (current_room_id_ < 0 || current_room_id_ >= 296) return;
|
||||
|
||||
if (!is_dragging_ || selected_object_indices_.empty() || !rooms_)
|
||||
return;
|
||||
if (current_room_id_ < 0 || current_room_id_ >= 296)
|
||||
return;
|
||||
|
||||
// Draw drag preview for selected objects
|
||||
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
||||
ImVec2 canvas_pos = canvas_->zero_point();
|
||||
ImVec2 drag_delta = ImVec2(drag_current_pos_.x - drag_start_pos_.x,
|
||||
drag_current_pos_.y - drag_start_pos_.y);
|
||||
|
||||
|
||||
auto& room = (*rooms_)[current_room_id_];
|
||||
const auto& objects = room.GetTileObjects();
|
||||
|
||||
|
||||
// Draw preview of where objects would be moved
|
||||
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
|
||||
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 semi-transparent preview at new position
|
||||
ImVec2 preview_start(canvas_pos.x + canvas_x + drag_delta.x,
|
||||
canvas_pos.y + canvas_y + drag_delta.y);
|
||||
ImVec2 preview_end(preview_start.x + obj_width, preview_start.y + obj_height);
|
||||
|
||||
canvas_pos.y + canvas_y + drag_delta.y);
|
||||
ImVec2 preview_end(preview_start.x + obj_width,
|
||||
preview_start.y + obj_height);
|
||||
|
||||
// Draw ghosted object
|
||||
draw_list->AddRectFilled(preview_start, preview_end, IM_COL32(0, 255, 255, 64));
|
||||
draw_list->AddRect(preview_start, preview_end, IM_COL32(0, 255, 255, 255), 0.0f, 0, 1.5f);
|
||||
draw_list->AddRectFilled(preview_start, preview_end,
|
||||
IM_COL32(0, 255, 255, 64));
|
||||
draw_list->AddRect(preview_start, preview_end, IM_COL32(0, 255, 255, 255),
|
||||
0.0f, 0, 1.5f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DungeonObjectInteraction::UpdateSelectedObjects() {
|
||||
if (!is_selecting_ || !rooms_) return;
|
||||
|
||||
if (!is_selecting_ || !rooms_)
|
||||
return;
|
||||
|
||||
selected_objects_.clear();
|
||||
|
||||
if (current_room_id_ < 0 || current_room_id_ >= 296) return;
|
||||
|
||||
|
||||
if (current_room_id_ < 0 || current_room_id_ >= 296)
|
||||
return;
|
||||
|
||||
auto& room = (*rooms_)[current_room_id_];
|
||||
|
||||
|
||||
// Check each object in the room
|
||||
for (const auto& object : room.GetTileObjects()) {
|
||||
if (IsObjectInSelectBox(object)) {
|
||||
@@ -335,49 +352,55 @@ void DungeonObjectInteraction::UpdateSelectedObjects() {
|
||||
|
||||
bool DungeonObjectInteraction::IsObjectInSelectBox(
|
||||
const zelda3::RoomObject& object) const {
|
||||
if (!is_selecting_) return false;
|
||||
|
||||
if (!is_selecting_)
|
||||
return false;
|
||||
|
||||
// Convert object position to canvas coordinates
|
||||
auto [canvas_x, canvas_y] = RoomToCanvasCoordinates(object.x_, object.y_);
|
||||
|
||||
|
||||
// Calculate select box bounds
|
||||
float min_x = std::min(select_start_pos_.x, select_current_pos_.x);
|
||||
float max_x = std::max(select_start_pos_.x, select_current_pos_.x);
|
||||
float min_y = std::min(select_start_pos_.y, select_current_pos_.y);
|
||||
float max_y = std::max(select_start_pos_.y, select_current_pos_.y);
|
||||
|
||||
|
||||
// Check if object is within select box
|
||||
return (canvas_x >= min_x && canvas_x <= max_x && canvas_y >= min_y &&
|
||||
canvas_y <= max_y);
|
||||
}
|
||||
|
||||
std::pair<int, int> DungeonObjectInteraction::RoomToCanvasCoordinates(int room_x, int room_y) const {
|
||||
std::pair<int, int> DungeonObjectInteraction::RoomToCanvasCoordinates(
|
||||
int room_x, int room_y) const {
|
||||
// Dungeon tiles are 8x8 pixels, convert room coordinates (tiles) to pixels
|
||||
return {room_x * 8, room_y * 8};
|
||||
}
|
||||
|
||||
std::pair<int, int> DungeonObjectInteraction::CanvasToRoomCoordinates(int canvas_x, int canvas_y) const {
|
||||
std::pair<int, int> DungeonObjectInteraction::CanvasToRoomCoordinates(
|
||||
int canvas_x, int canvas_y) const {
|
||||
// Convert canvas pixels back to room coordinates (tiles)
|
||||
return {canvas_x / 8, canvas_y / 8};
|
||||
}
|
||||
|
||||
bool DungeonObjectInteraction::IsWithinCanvasBounds(int canvas_x, int canvas_y, int margin) const {
|
||||
bool DungeonObjectInteraction::IsWithinCanvasBounds(int canvas_x, int canvas_y,
|
||||
int margin) const {
|
||||
auto canvas_size = canvas_->canvas_size();
|
||||
auto global_scale = canvas_->global_scale();
|
||||
int scaled_width = static_cast<int>(canvas_size.x * global_scale);
|
||||
int scaled_height = static_cast<int>(canvas_size.y * global_scale);
|
||||
|
||||
|
||||
return (canvas_x >= -margin && canvas_y >= -margin &&
|
||||
canvas_x <= scaled_width + margin &&
|
||||
canvas_y <= scaled_height + margin);
|
||||
}
|
||||
|
||||
void DungeonObjectInteraction::SetCurrentRoom(std::array<zelda3::Room, 0x128>* rooms, int room_id) {
|
||||
void DungeonObjectInteraction::SetCurrentRoom(
|
||||
std::array<zelda3::Room, 0x128>* rooms, int room_id) {
|
||||
rooms_ = rooms;
|
||||
current_room_id_ = room_id;
|
||||
}
|
||||
|
||||
void DungeonObjectInteraction::SetPreviewObject(const zelda3::RoomObject& object, bool loaded) {
|
||||
void DungeonObjectInteraction::SetPreviewObject(
|
||||
const zelda3::RoomObject& object, bool loaded) {
|
||||
preview_object_ = object;
|
||||
object_loaded_ = loaded;
|
||||
}
|
||||
@@ -390,13 +413,14 @@ void DungeonObjectInteraction::ClearSelection() {
|
||||
}
|
||||
|
||||
void DungeonObjectInteraction::ShowContextMenu() {
|
||||
if (!canvas_->IsMouseHovering()) return;
|
||||
|
||||
if (!canvas_->IsMouseHovering())
|
||||
return;
|
||||
|
||||
// Show context menu on right-click when not dragging
|
||||
if (ImGui::IsMouseClicked(ImGuiMouseButton_Right) && !is_dragging_) {
|
||||
ImGui::OpenPopup("DungeonObjectContextMenu");
|
||||
}
|
||||
|
||||
|
||||
if (ImGui::BeginPopup("DungeonObjectContextMenu")) {
|
||||
// Show different options based on current state
|
||||
if (!selected_object_indices_.empty()) {
|
||||
@@ -408,14 +432,14 @@ void DungeonObjectInteraction::ShowContextMenu() {
|
||||
}
|
||||
ImGui::Separator();
|
||||
}
|
||||
|
||||
|
||||
if (has_clipboard_data_) {
|
||||
if (ImGui::MenuItem("Paste Objects", "Ctrl+V")) {
|
||||
HandlePasteObjects();
|
||||
}
|
||||
ImGui::Separator();
|
||||
}
|
||||
|
||||
|
||||
if (object_loaded_) {
|
||||
ImGui::Text("Placing: Object 0x%02X", preview_object_.id_);
|
||||
if (ImGui::MenuItem("Cancel Placement", "Esc")) {
|
||||
@@ -425,29 +449,31 @@ void DungeonObjectInteraction::ShowContextMenu() {
|
||||
ImGui::Text("Right-click + drag to select");
|
||||
ImGui::Text("Left-click + drag to move");
|
||||
}
|
||||
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
void DungeonObjectInteraction::HandleDeleteSelected() {
|
||||
if (selected_object_indices_.empty() || !rooms_) return;
|
||||
if (current_room_id_ < 0 || current_room_id_ >= 296) return;
|
||||
|
||||
if (selected_object_indices_.empty() || !rooms_)
|
||||
return;
|
||||
if (current_room_id_ < 0 || current_room_id_ >= 296)
|
||||
return;
|
||||
|
||||
auto& room = (*rooms_)[current_room_id_];
|
||||
|
||||
|
||||
// Sort indices in descending order to avoid index shifts during deletion
|
||||
std::vector<size_t> sorted_indices = selected_object_indices_;
|
||||
std::sort(sorted_indices.rbegin(), sorted_indices.rend());
|
||||
|
||||
|
||||
// Delete selected objects using Room's RemoveTileObject method
|
||||
for (size_t index : sorted_indices) {
|
||||
room.RemoveTileObject(index);
|
||||
}
|
||||
|
||||
|
||||
// Clear selection
|
||||
ClearSelection();
|
||||
|
||||
|
||||
// Trigger cache invalidation and re-render
|
||||
if (cache_invalidation_callback_) {
|
||||
cache_invalidation_callback_();
|
||||
@@ -455,12 +481,14 @@ void DungeonObjectInteraction::HandleDeleteSelected() {
|
||||
}
|
||||
|
||||
void DungeonObjectInteraction::HandleCopySelected() {
|
||||
if (selected_object_indices_.empty() || !rooms_) return;
|
||||
if (current_room_id_ < 0 || current_room_id_ >= 296) return;
|
||||
|
||||
if (selected_object_indices_.empty() || !rooms_)
|
||||
return;
|
||||
if (current_room_id_ < 0 || current_room_id_ >= 296)
|
||||
return;
|
||||
|
||||
auto& room = (*rooms_)[current_room_id_];
|
||||
const auto& objects = room.GetTileObjects();
|
||||
|
||||
|
||||
// Copy selected objects to clipboard
|
||||
clipboard_.clear();
|
||||
for (size_t index : selected_object_indices_) {
|
||||
@@ -468,44 +496,46 @@ void DungeonObjectInteraction::HandleCopySelected() {
|
||||
clipboard_.push_back(objects[index]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
has_clipboard_data_ = !clipboard_.empty();
|
||||
}
|
||||
|
||||
void DungeonObjectInteraction::HandlePasteObjects() {
|
||||
if (!has_clipboard_data_ || !rooms_) return;
|
||||
if (current_room_id_ < 0 || current_room_id_ >= 296) return;
|
||||
|
||||
if (!has_clipboard_data_ || !rooms_)
|
||||
return;
|
||||
if (current_room_id_ < 0 || current_room_id_ >= 296)
|
||||
return;
|
||||
|
||||
auto& room = (*rooms_)[current_room_id_];
|
||||
|
||||
|
||||
// Get mouse position for paste location
|
||||
const ImGuiIO& io = ImGui::GetIO();
|
||||
ImVec2 mouse_pos = io.MousePos;
|
||||
ImVec2 canvas_pos = canvas_->zero_point();
|
||||
ImVec2 canvas_mouse_pos =
|
||||
ImVec2(mouse_pos.x - canvas_pos.x, mouse_pos.y - canvas_pos.y);
|
||||
auto [paste_x, paste_y] = CanvasToRoomCoordinates(
|
||||
static_cast<int>(canvas_mouse_pos.x),
|
||||
static_cast<int>(canvas_mouse_pos.y));
|
||||
|
||||
auto [paste_x, paste_y] =
|
||||
CanvasToRoomCoordinates(static_cast<int>(canvas_mouse_pos.x),
|
||||
static_cast<int>(canvas_mouse_pos.y));
|
||||
|
||||
// Calculate offset from first object in clipboard
|
||||
if (!clipboard_.empty()) {
|
||||
int offset_x = paste_x - clipboard_[0].x_;
|
||||
int offset_y = paste_y - clipboard_[0].y_;
|
||||
|
||||
|
||||
// Paste all objects with offset
|
||||
for (const auto& obj : clipboard_) {
|
||||
auto new_obj = obj;
|
||||
new_obj.x_ = obj.x_ + offset_x;
|
||||
new_obj.y_ = obj.y_ + offset_y;
|
||||
|
||||
|
||||
// Clamp to room bounds
|
||||
new_obj.x_ = std::clamp(static_cast<int>(new_obj.x_), 0, 63);
|
||||
new_obj.y_ = std::clamp(static_cast<int>(new_obj.y_), 0, 63);
|
||||
|
||||
|
||||
room.AddTileObject(new_obj);
|
||||
}
|
||||
|
||||
|
||||
// Trigger cache invalidation and re-render
|
||||
if (cache_invalidation_callback_) {
|
||||
cache_invalidation_callback_();
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#ifndef YAZE_APP_EDITOR_DUNGEON_DUNGEON_OBJECT_INTERACTION_H
|
||||
#define YAZE_APP_EDITOR_DUNGEON_DUNGEON_OBJECT_INTERACTION_H
|
||||
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
#include "imgui/imgui.h"
|
||||
#include "app/gui/canvas/canvas.h"
|
||||
#include "imgui/imgui.h"
|
||||
#include "zelda3/dungeon/room.h"
|
||||
#include "zelda3/dungeon/room_object.h"
|
||||
|
||||
@@ -21,45 +21,48 @@ namespace editor {
|
||||
class DungeonObjectInteraction {
|
||||
public:
|
||||
explicit DungeonObjectInteraction(gui::Canvas* canvas) : canvas_(canvas) {}
|
||||
|
||||
|
||||
// Main interaction handling
|
||||
void HandleCanvasMouseInput();
|
||||
void CheckForObjectSelection();
|
||||
void PlaceObjectAtPosition(int room_x, int room_y);
|
||||
|
||||
|
||||
// Selection rectangle (like OverworldEditor)
|
||||
void DrawObjectSelectRect();
|
||||
void SelectObjectsInRect();
|
||||
void DrawSelectionHighlights(); // Draw highlights for selected objects
|
||||
|
||||
|
||||
// Drag and select box functionality
|
||||
void DrawSelectBox();
|
||||
void DrawDragPreview();
|
||||
void UpdateSelectedObjects();
|
||||
bool IsObjectInSelectBox(const zelda3::RoomObject& object) const;
|
||||
|
||||
|
||||
// Coordinate conversion
|
||||
std::pair<int, int> RoomToCanvasCoordinates(int room_x, int room_y) const;
|
||||
std::pair<int, int> CanvasToRoomCoordinates(int canvas_x, int canvas_y) const;
|
||||
bool IsWithinCanvasBounds(int canvas_x, int canvas_y, int margin = 32) const;
|
||||
|
||||
|
||||
// State management
|
||||
void SetCurrentRoom(std::array<zelda3::Room, 0x128>* rooms, int room_id);
|
||||
void SetPreviewObject(const zelda3::RoomObject& object, bool loaded);
|
||||
|
||||
|
||||
// Selection state
|
||||
const std::vector<size_t>& GetSelectedObjectIndices() const { return selected_object_indices_; }
|
||||
const std::vector<size_t>& GetSelectedObjectIndices() const {
|
||||
return selected_object_indices_;
|
||||
}
|
||||
bool IsObjectSelectActive() const { return object_select_active_; }
|
||||
void ClearSelection();
|
||||
|
||||
|
||||
// Context menu
|
||||
void ShowContextMenu();
|
||||
void HandleDeleteSelected();
|
||||
void HandleCopySelected();
|
||||
void HandlePasteObjects();
|
||||
|
||||
|
||||
// Callbacks
|
||||
void SetObjectPlacedCallback(std::function<void(const zelda3::RoomObject&)> callback) {
|
||||
void SetObjectPlacedCallback(
|
||||
std::function<void(const zelda3::RoomObject&)> callback) {
|
||||
object_placed_callback_ = callback;
|
||||
}
|
||||
void SetCacheInvalidationCallback(std::function<void()> callback) {
|
||||
@@ -70,11 +73,11 @@ class DungeonObjectInteraction {
|
||||
gui::Canvas* canvas_;
|
||||
std::array<zelda3::Room, 0x128>* rooms_ = nullptr;
|
||||
int current_room_id_ = 0;
|
||||
|
||||
|
||||
// Preview object state
|
||||
zelda3::RoomObject preview_object_{0, 0, 0, 0, 0};
|
||||
bool object_loaded_ = false;
|
||||
|
||||
|
||||
// Drag and select infrastructure
|
||||
bool is_dragging_ = false;
|
||||
bool is_selecting_ = false;
|
||||
@@ -83,17 +86,17 @@ class DungeonObjectInteraction {
|
||||
ImVec2 select_start_pos_;
|
||||
ImVec2 select_current_pos_;
|
||||
std::vector<int> selected_objects_;
|
||||
|
||||
|
||||
// Object selection rectangle (like OverworldEditor)
|
||||
bool object_select_active_ = false;
|
||||
ImVec2 object_select_start_;
|
||||
ImVec2 object_select_end_;
|
||||
std::vector<size_t> selected_object_indices_;
|
||||
|
||||
|
||||
// Callbacks
|
||||
std::function<void(const zelda3::RoomObject&)> object_placed_callback_;
|
||||
std::function<void()> cache_invalidation_callback_;
|
||||
|
||||
|
||||
// Clipboard for copy/paste
|
||||
std::vector<zelda3::RoomObject> clipboard_;
|
||||
bool has_clipboard_data_ = false;
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
#include "dungeon_object_selector.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <cstring>
|
||||
#include <iterator>
|
||||
|
||||
#include "app/platform/window.h"
|
||||
#include "app/gfx/resource/arena.h"
|
||||
#include "app/gfx/types/snes_palette.h"
|
||||
#include "app/gui/canvas/canvas.h"
|
||||
#include "app/gui/widgets/asset_browser.h"
|
||||
#include "app/platform/window.h"
|
||||
#include "app/rom.h"
|
||||
#include "zelda3/dungeon/room.h"
|
||||
#include "imgui/imgui.h"
|
||||
#include "zelda3/dungeon/dungeon_editor_system.h"
|
||||
#include "zelda3/dungeon/dungeon_object_editor.h"
|
||||
#include "imgui/imgui.h"
|
||||
#include "zelda3/dungeon/room.h"
|
||||
|
||||
namespace yaze::editor {
|
||||
|
||||
@@ -26,7 +26,7 @@ using ImGui::Separator;
|
||||
void DungeonObjectSelector::DrawTileSelector() {
|
||||
if (ImGui::BeginTabBar("##TabBar", ImGuiTabBarFlags_FittingPolicyScroll)) {
|
||||
if (ImGui::BeginTabItem("Room Graphics")) {
|
||||
if (ImGuiID child_id = ImGui::GetID((void *)(intptr_t)3);
|
||||
if (ImGuiID child_id = ImGui::GetID((void*)(intptr_t)3);
|
||||
BeginChild(child_id, ImGui::GetContentRegionAvail(), true,
|
||||
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
|
||||
DrawRoomGraphics();
|
||||
@@ -45,17 +45,25 @@ void DungeonObjectSelector::DrawTileSelector() {
|
||||
|
||||
void DungeonObjectSelector::DrawObjectRenderer() {
|
||||
// Use AssetBrowser for better object selection
|
||||
if (ImGui::BeginTable("DungeonObjectEditorTable", 2, ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV, ImVec2(0, 0))) {
|
||||
ImGui::TableSetupColumn("Object Browser", ImGuiTableColumnFlags_WidthFixed, 400);
|
||||
ImGui::TableSetupColumn("Preview Canvas", ImGuiTableColumnFlags_WidthStretch);
|
||||
if (ImGui::BeginTable(
|
||||
"DungeonObjectEditorTable", 2,
|
||||
ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable |
|
||||
ImGuiTableFlags_Hideable | ImGuiTableFlags_BordersOuter |
|
||||
ImGuiTableFlags_BordersV,
|
||||
ImVec2(0, 0))) {
|
||||
ImGui::TableSetupColumn("Object Browser", ImGuiTableColumnFlags_WidthFixed,
|
||||
400);
|
||||
ImGui::TableSetupColumn("Preview Canvas",
|
||||
ImGuiTableColumnFlags_WidthStretch);
|
||||
ImGui::TableHeadersRow();
|
||||
|
||||
// Left column: AssetBrowser for object selection
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::BeginChild("AssetBrowser", ImVec2(0, 0), true, ImGuiWindowFlags_AlwaysVerticalScrollbar);
|
||||
|
||||
ImGui::BeginChild("AssetBrowser", ImVec2(0, 0), true,
|
||||
ImGuiWindowFlags_AlwaysVerticalScrollbar);
|
||||
|
||||
DrawObjectAssetBrowser();
|
||||
|
||||
|
||||
ImGui::EndChild();
|
||||
|
||||
// Right column: Preview and placement controls
|
||||
@@ -67,13 +75,13 @@ void DungeonObjectSelector::DrawObjectRenderer() {
|
||||
static int place_x = 0, place_y = 0;
|
||||
ImGui::InputInt("X Position", &place_x);
|
||||
ImGui::InputInt("Y Position", &place_y);
|
||||
|
||||
|
||||
if (ImGui::Button("Place Object") && object_loaded_) {
|
||||
PlaceObjectAtPosition(place_x, place_y);
|
||||
}
|
||||
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
|
||||
// Preview canvas
|
||||
object_canvas_.DrawBackground(ImVec2(256 + 1, 0x10 * 0x40 + 1));
|
||||
object_canvas_.DrawContextMenu();
|
||||
@@ -101,19 +109,19 @@ void DungeonObjectSelector::DrawObjectRenderer() {
|
||||
ImGui::Text("Position: (%d, %d)", preview_object_.x_, preview_object_.y_);
|
||||
ImGui::Text("Size: 0x%02X", preview_object_.size_);
|
||||
ImGui::Text("Layer: %d", static_cast<int>(preview_object_.layer_));
|
||||
|
||||
|
||||
// Add object placement controls
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Placement Controls:");
|
||||
static int place_x = 0, place_y = 0;
|
||||
ImGui::InputInt("X Position", &place_x);
|
||||
ImGui::InputInt("Y Position", &place_y);
|
||||
|
||||
|
||||
if (ImGui::Button("Place Object")) {
|
||||
// TODO: Implement object placement in the main canvas
|
||||
ImGui::Text("Object placed at (%d, %d)", place_x, place_y);
|
||||
}
|
||||
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
}
|
||||
@@ -121,129 +129,156 @@ void DungeonObjectSelector::DrawObjectRenderer() {
|
||||
void DungeonObjectSelector::DrawObjectBrowser() {
|
||||
static int selected_object_type = 0;
|
||||
static int selected_object_id = 0;
|
||||
|
||||
|
||||
// Object type selector
|
||||
const char* object_types[] = {"Type 1 (0x00-0xFF)", "Type 2 (0x100-0x1FF)", "Type 3 (0x200+)"};
|
||||
const char* object_types[] = {"Type 1 (0x00-0xFF)", "Type 2 (0x100-0x1FF)",
|
||||
"Type 3 (0x200+)"};
|
||||
if (ImGui::Combo("Object Type", &selected_object_type, object_types, 3)) {
|
||||
selected_object_id = 0; // Reset selection when changing type
|
||||
selected_object_id = 0; // Reset selection when changing type
|
||||
}
|
||||
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
|
||||
// Object list with previews - optimized for 300px column width
|
||||
const int preview_size = 48; // Larger 48x48 pixel preview for better visibility
|
||||
const int items_per_row = 5; // 5 items per row to fit in 300px column
|
||||
|
||||
const int preview_size =
|
||||
48; // Larger 48x48 pixel preview for better visibility
|
||||
const int items_per_row = 5; // 5 items per row to fit in 300px column
|
||||
|
||||
if (rom_ && rom_->is_loaded()) {
|
||||
auto palette = rom_->palette_group().dungeon_main[current_palette_group_id_];
|
||||
|
||||
auto palette =
|
||||
rom_->palette_group().dungeon_main[current_palette_group_id_];
|
||||
|
||||
// Determine object range based on type
|
||||
int start_id, end_id;
|
||||
switch (selected_object_type) {
|
||||
case 0: start_id = 0x00; end_id = 0xFF; break;
|
||||
case 1: start_id = 0x100; end_id = 0x1FF; break;
|
||||
case 2: start_id = 0x200; end_id = 0x2FF; break;
|
||||
default: start_id = 0x00; end_id = 0xFF; break;
|
||||
case 0:
|
||||
start_id = 0x00;
|
||||
end_id = 0xFF;
|
||||
break;
|
||||
case 1:
|
||||
start_id = 0x100;
|
||||
end_id = 0x1FF;
|
||||
break;
|
||||
case 2:
|
||||
start_id = 0x200;
|
||||
end_id = 0x2FF;
|
||||
break;
|
||||
default:
|
||||
start_id = 0x00;
|
||||
end_id = 0xFF;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
// Create a grid layout for object previews
|
||||
int current_row = 0;
|
||||
int current_col = 0;
|
||||
|
||||
for (int obj_id = start_id; obj_id <= end_id && obj_id <= start_id + 63; ++obj_id) { // Limit to 64 objects for performance
|
||||
|
||||
for (int obj_id = start_id; obj_id <= end_id && obj_id <= start_id + 63;
|
||||
++obj_id) { // Limit to 64 objects for performance
|
||||
// Create object for preview
|
||||
auto test_object = zelda3::RoomObject(obj_id, 0, 0, 0x12, 0);
|
||||
test_object.set_rom(rom_);
|
||||
test_object.EnsureTilesLoaded();
|
||||
|
||||
|
||||
// Calculate position in grid - better sizing for 300px column
|
||||
float available_width = ImGui::GetContentRegionAvail().x;
|
||||
float spacing = ImGui::GetStyle().ItemSpacing.x;
|
||||
float item_width = (available_width - (items_per_row - 1) * spacing) / items_per_row;
|
||||
float item_height = preview_size + 30; // Preview + text (reduced padding)
|
||||
|
||||
float item_width =
|
||||
(available_width - (items_per_row - 1) * spacing) / items_per_row;
|
||||
float item_height =
|
||||
preview_size + 30; // Preview + text (reduced padding)
|
||||
|
||||
ImGui::PushID(obj_id);
|
||||
|
||||
|
||||
// Create a selectable button with preview
|
||||
bool is_selected = (selected_object_id == obj_id);
|
||||
if (ImGui::Selectable("", is_selected, ImGuiSelectableFlags_None, ImVec2(item_width, item_height))) {
|
||||
if (ImGui::Selectable("", is_selected, ImGuiSelectableFlags_None,
|
||||
ImVec2(item_width, item_height))) {
|
||||
selected_object_id = obj_id;
|
||||
|
||||
|
||||
// Update preview object
|
||||
preview_object_ = test_object;
|
||||
preview_palette_ = palette;
|
||||
object_loaded_ = true;
|
||||
|
||||
|
||||
// Notify the main editor that an object was selected
|
||||
if (object_selected_callback_) {
|
||||
object_selected_callback_(preview_object_);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Draw preview image
|
||||
ImVec2 cursor_pos = ImGui::GetCursorScreenPos();
|
||||
ImVec2 preview_pos = ImVec2(cursor_pos.x + (item_width - preview_size) / 2,
|
||||
cursor_pos.y - item_height + 5);
|
||||
|
||||
ImVec2 preview_pos =
|
||||
ImVec2(cursor_pos.x + (item_width - preview_size) / 2,
|
||||
cursor_pos.y - item_height + 5);
|
||||
|
||||
// Draw simplified primitive preview for object selector
|
||||
ImGui::SetCursorScreenPos(preview_pos);
|
||||
|
||||
|
||||
// Draw object as colored rectangle with ID
|
||||
ImU32 object_color = GetObjectTypeColor(obj_id);
|
||||
ImGui::GetWindowDrawList()->AddRectFilled(
|
||||
preview_pos,
|
||||
ImVec2(preview_pos.x + preview_size, preview_pos.y + preview_size),
|
||||
object_color);
|
||||
|
||||
preview_pos,
|
||||
ImVec2(preview_pos.x + preview_size, preview_pos.y + preview_size),
|
||||
object_color);
|
||||
|
||||
// Draw border
|
||||
ImGui::GetWindowDrawList()->AddRect(
|
||||
preview_pos,
|
||||
ImVec2(preview_pos.x + preview_size, preview_pos.y + preview_size),
|
||||
IM_COL32(0, 0, 0, 255), 0.0f, 0, 2.0f);
|
||||
|
||||
preview_pos,
|
||||
ImVec2(preview_pos.x + preview_size, preview_pos.y + preview_size),
|
||||
IM_COL32(0, 0, 0, 255), 0.0f, 0, 2.0f);
|
||||
|
||||
// Draw object type symbol in center
|
||||
std::string symbol = GetObjectTypeSymbol(obj_id);
|
||||
ImVec2 text_size = ImGui::CalcTextSize(symbol.c_str());
|
||||
ImVec2 text_pos = ImVec2(
|
||||
preview_pos.x + (preview_size - text_size.x) / 2,
|
||||
preview_pos.y + (preview_size - text_size.y) / 2);
|
||||
|
||||
ImVec2 text_pos =
|
||||
ImVec2(preview_pos.x + (preview_size - text_size.x) / 2,
|
||||
preview_pos.y + (preview_size - text_size.y) / 2);
|
||||
|
||||
ImGui::GetWindowDrawList()->AddText(
|
||||
text_pos, IM_COL32(255, 255, 255, 255), symbol.c_str());
|
||||
|
||||
text_pos, IM_COL32(255, 255, 255, 255), symbol.c_str());
|
||||
|
||||
// Draw object ID below preview
|
||||
ImGui::SetCursorScreenPos(ImVec2(preview_pos.x, preview_pos.y + preview_size + 2));
|
||||
ImGui::SetCursorScreenPos(
|
||||
ImVec2(preview_pos.x, preview_pos.y + preview_size + 2));
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 255));
|
||||
ImGui::Text("0x%02X", obj_id);
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
|
||||
// Try to get object name
|
||||
std::string object_name = "Unknown";
|
||||
if (obj_id < 0x100) { // Type1RoomObjectNames has 248 elements (0-247, 0x00-0xF7)
|
||||
if (obj_id <
|
||||
0x100) { // Type1RoomObjectNames has 248 elements (0-247, 0x00-0xF7)
|
||||
if (obj_id < std::size(zelda3::Type1RoomObjectNames)) {
|
||||
const char* name_ptr = zelda3::Type1RoomObjectNames[obj_id];
|
||||
if (name_ptr != nullptr) {
|
||||
object_name = std::string(name_ptr);
|
||||
}
|
||||
}
|
||||
} else if (obj_id < 0x140) { // Type2RoomObjectNames has 64 elements (0x100-0x13F)
|
||||
} else if (obj_id <
|
||||
0x140) { // Type2RoomObjectNames has 64 elements (0x100-0x13F)
|
||||
int type2_index = obj_id - 0x100;
|
||||
if (type2_index >= 0 && type2_index < std::size(zelda3::Type2RoomObjectNames)) {
|
||||
if (type2_index >= 0 &&
|
||||
type2_index < std::size(zelda3::Type2RoomObjectNames)) {
|
||||
const char* name_ptr = zelda3::Type2RoomObjectNames[type2_index];
|
||||
if (name_ptr != nullptr) {
|
||||
object_name = std::string(name_ptr);
|
||||
}
|
||||
}
|
||||
} else if (obj_id < 0x1C0) { // Type3RoomObjectNames has 128 elements (0x140-0x1BF)
|
||||
} else if (
|
||||
obj_id <
|
||||
0x1C0) { // Type3RoomObjectNames has 128 elements (0x140-0x1BF)
|
||||
int type3_index = obj_id - 0x140;
|
||||
if (type3_index >= 0 && type3_index < std::size(zelda3::Type3RoomObjectNames)) {
|
||||
if (type3_index >= 0 &&
|
||||
type3_index < std::size(zelda3::Type3RoomObjectNames)) {
|
||||
const char* name_ptr = zelda3::Type3RoomObjectNames[type3_index];
|
||||
if (name_ptr != nullptr) {
|
||||
object_name = std::string(name_ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Draw object name with better sizing
|
||||
ImGui::SetCursorScreenPos(ImVec2(cursor_pos.x + 2, cursor_pos.y - 8));
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(200, 200, 200, 255));
|
||||
@@ -253,9 +288,9 @@ void DungeonObjectSelector::DrawObjectBrowser() {
|
||||
}
|
||||
ImGui::Text("%s", object_name.c_str());
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
|
||||
ImGui::PopID();
|
||||
|
||||
|
||||
// Move to next position
|
||||
current_col++;
|
||||
if (current_col >= items_per_row) {
|
||||
@@ -269,9 +304,9 @@ void DungeonObjectSelector::DrawObjectBrowser() {
|
||||
} else {
|
||||
ImGui::Text("ROM not loaded");
|
||||
}
|
||||
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
|
||||
// Selected object info
|
||||
if (object_loaded_) {
|
||||
ImGui::Text("Selected: 0x%03X", selected_object_id);
|
||||
@@ -287,19 +322,19 @@ void DungeonObjectSelector::Draw() {
|
||||
DrawObjectRenderer();
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
|
||||
// Room Graphics tab - 8 bitmaps viewer
|
||||
if (ImGui::BeginTabItem("Room Graphics")) {
|
||||
DrawRoomGraphics();
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
|
||||
// Object Editor tab - experimental editor
|
||||
if (ImGui::BeginTabItem("Object Editor")) {
|
||||
DrawIntegratedEditingPanels();
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
}
|
||||
@@ -309,46 +344,48 @@ void DungeonObjectSelector::DrawRoomGraphics() {
|
||||
room_gfx_canvas_.DrawBackground();
|
||||
room_gfx_canvas_.DrawContextMenu();
|
||||
room_gfx_canvas_.DrawTileSelector(32);
|
||||
|
||||
|
||||
if (rom_ && rom_->is_loaded() && rooms_) {
|
||||
int active_room_id = current_room_id_;
|
||||
auto& room = (*rooms_)[active_room_id];
|
||||
auto blocks = room.blocks();
|
||||
|
||||
|
||||
// Load graphics for this room if not already loaded
|
||||
if (blocks.empty()) {
|
||||
room.LoadRoomGraphics(room.blockset);
|
||||
blocks = room.blocks();
|
||||
}
|
||||
|
||||
|
||||
int current_block = 0;
|
||||
const int max_blocks_per_row = 2; // 2 blocks per row for 300px column
|
||||
const int block_width = 128; // Reduced size to fit column
|
||||
const int block_height = 32; // Reduced height
|
||||
|
||||
const int max_blocks_per_row = 2; // 2 blocks per row for 300px column
|
||||
const int block_width = 128; // Reduced size to fit column
|
||||
const int block_height = 32; // Reduced height
|
||||
|
||||
for (int block : blocks) {
|
||||
if (current_block >= 16) break; // Only show first 16 blocks
|
||||
|
||||
if (current_block >= 16)
|
||||
break; // Only show first 16 blocks
|
||||
|
||||
// Ensure the graphics sheet is loaded and has a valid texture
|
||||
if (block < gfx::Arena::Get().gfx_sheets().size()) {
|
||||
auto& gfx_sheet = gfx::Arena::Get().gfx_sheets()[block];
|
||||
|
||||
|
||||
// Calculate position in a grid layout instead of horizontal concatenation
|
||||
int row = current_block / max_blocks_per_row;
|
||||
int col = current_block % max_blocks_per_row;
|
||||
|
||||
|
||||
int x = room_gfx_canvas_.zero_point().x + 2 + (col * block_width);
|
||||
int y = room_gfx_canvas_.zero_point().y + 2 + (row * block_height);
|
||||
|
||||
|
||||
// Ensure we don't exceed canvas bounds
|
||||
if (x + block_width <= room_gfx_canvas_.zero_point().x + room_gfx_canvas_.width() &&
|
||||
y + block_height <= room_gfx_canvas_.zero_point().y + room_gfx_canvas_.height()) {
|
||||
|
||||
if (x + block_width <=
|
||||
room_gfx_canvas_.zero_point().x + room_gfx_canvas_.width() &&
|
||||
y + block_height <=
|
||||
room_gfx_canvas_.zero_point().y + room_gfx_canvas_.height()) {
|
||||
|
||||
// Only draw if the texture is valid
|
||||
if (gfx_sheet.texture() != 0) {
|
||||
room_gfx_canvas_.draw_list()->AddImage(
|
||||
(ImTextureID)(intptr_t)gfx_sheet.texture(),
|
||||
ImVec2(x, y),
|
||||
(ImTextureID)(intptr_t)gfx_sheet.texture(), ImVec2(x, y),
|
||||
ImVec2(x + block_width, y + block_height));
|
||||
}
|
||||
}
|
||||
@@ -421,13 +458,14 @@ void DungeonObjectSelector::DrawCompactObjectEditor() {
|
||||
}
|
||||
|
||||
auto& editor = *object_editor_;
|
||||
|
||||
|
||||
ImGui::Text("Object Editor");
|
||||
Separator();
|
||||
|
||||
// Display current editing mode
|
||||
auto mode = editor.GetMode();
|
||||
const char *mode_names[] = {"Select", "Insert", "Delete", "Edit", "Layer", "Preview"};
|
||||
const char* mode_names[] = {"Select", "Insert", "Delete",
|
||||
"Edit", "Layer", "Preview"};
|
||||
ImGui::Text("Mode: %s", mode_names[static_cast<int>(mode)]);
|
||||
|
||||
// Compact mode selection
|
||||
@@ -486,58 +524,60 @@ void DungeonObjectSelector::DrawCompactObjectEditor() {
|
||||
ImU32 DungeonObjectSelector::GetObjectTypeColor(int object_id) {
|
||||
// Color-code objects based on their type and function
|
||||
if (object_id >= 0x10 && object_id <= 0x1F) {
|
||||
return IM_COL32(128, 128, 128, 255); // Gray for walls
|
||||
return IM_COL32(128, 128, 128, 255); // Gray for walls
|
||||
} else if (object_id >= 0x20 && object_id <= 0x2F) {
|
||||
return IM_COL32(139, 69, 19, 255); // Brown for floors
|
||||
return IM_COL32(139, 69, 19, 255); // Brown for floors
|
||||
} else if (object_id == 0xF9 || object_id == 0xFA) {
|
||||
return IM_COL32(255, 215, 0, 255); // Gold for chests
|
||||
return IM_COL32(255, 215, 0, 255); // Gold for chests
|
||||
} else if (object_id >= 0x17 && object_id <= 0x1E) {
|
||||
return IM_COL32(139, 69, 19, 255); // Brown for doors
|
||||
return IM_COL32(139, 69, 19, 255); // Brown for doors
|
||||
} else if (object_id == 0x2F || object_id == 0x2B) {
|
||||
return IM_COL32(160, 82, 45, 255); // Saddle brown for pots
|
||||
return IM_COL32(160, 82, 45, 255); // Saddle brown for pots
|
||||
} else if (object_id >= 0x138 && object_id <= 0x13B) {
|
||||
return IM_COL32(255, 255, 0, 255); // Yellow for stairs
|
||||
return IM_COL32(255, 255, 0, 255); // Yellow for stairs
|
||||
} else if (object_id >= 0x30 && object_id <= 0x3F) {
|
||||
return IM_COL32(105, 105, 105, 255); // Dim gray for decorations
|
||||
return IM_COL32(105, 105, 105, 255); // Dim gray for decorations
|
||||
} else {
|
||||
return IM_COL32(96, 96, 96, 255); // Default gray
|
||||
return IM_COL32(96, 96, 96, 255); // Default gray
|
||||
}
|
||||
}
|
||||
|
||||
std::string DungeonObjectSelector::GetObjectTypeSymbol(int object_id) {
|
||||
// Return symbol representing object type
|
||||
if (object_id >= 0x10 && object_id <= 0x1F) {
|
||||
return "■"; // Wall
|
||||
return "■"; // Wall
|
||||
} else if (object_id >= 0x20 && object_id <= 0x2F) {
|
||||
return "□"; // Floor
|
||||
return "□"; // Floor
|
||||
} else if (object_id == 0xF9 || object_id == 0xFA) {
|
||||
return "⬛"; // Chest
|
||||
return "⬛"; // Chest
|
||||
} else if (object_id >= 0x17 && object_id <= 0x1E) {
|
||||
return "◊"; // Door
|
||||
return "◊"; // Door
|
||||
} else if (object_id == 0x2F || object_id == 0x2B) {
|
||||
return "●"; // Pot
|
||||
return "●"; // Pot
|
||||
} else if (object_id >= 0x138 && object_id <= 0x13B) {
|
||||
return "▲"; // Stairs
|
||||
return "▲"; // Stairs
|
||||
} else if (object_id >= 0x30 && object_id <= 0x3F) {
|
||||
return "◆"; // Decoration
|
||||
return "◆"; // Decoration
|
||||
} else {
|
||||
return "?"; // Unknown
|
||||
return "?"; // Unknown
|
||||
}
|
||||
}
|
||||
|
||||
void DungeonObjectSelector::RenderObjectPrimitive(const zelda3::RoomObject& object, int x, int y) {
|
||||
void DungeonObjectSelector::RenderObjectPrimitive(
|
||||
const zelda3::RoomObject& object, int x, int y) {
|
||||
// Render object as primitive shape on canvas
|
||||
ImU32 color = GetObjectTypeColor(object.id_);
|
||||
|
||||
|
||||
// Calculate object size with proper wall length handling
|
||||
int obj_width, obj_height;
|
||||
CalculateObjectDimensions(object, obj_width, obj_height);
|
||||
|
||||
|
||||
// Draw object rectangle
|
||||
ImVec4 color_vec = ImGui::ColorConvertU32ToFloat4(color);
|
||||
object_canvas_.DrawRect(x, y, obj_width, obj_height, color_vec);
|
||||
object_canvas_.DrawRect(x, y, obj_width, obj_height, ImVec4(0.0f, 0.0f, 0.0f, 1.0f));
|
||||
|
||||
object_canvas_.DrawRect(x, y, obj_width, obj_height,
|
||||
ImVec4(0.0f, 0.0f, 0.0f, 1.0f));
|
||||
|
||||
// Draw object ID as text
|
||||
std::string obj_text = absl::StrFormat("0x%X", object.id_);
|
||||
object_canvas_.DrawText(obj_text, x + obj_width + 2, y + 4);
|
||||
@@ -545,144 +585,151 @@ void DungeonObjectSelector::RenderObjectPrimitive(const zelda3::RoomObject& obje
|
||||
|
||||
void DungeonObjectSelector::DrawObjectAssetBrowser() {
|
||||
ImGui::SeparatorText("Dungeon Objects");
|
||||
|
||||
|
||||
// Debug info
|
||||
ImGui::Text("Asset Browser Debug: Available width: %.1f", ImGui::GetContentRegionAvail().x);
|
||||
|
||||
ImGui::Text("Asset Browser Debug: Available width: %.1f",
|
||||
ImGui::GetContentRegionAvail().x);
|
||||
|
||||
// Object type filter
|
||||
static int object_type_filter = 0;
|
||||
const char* object_types[] = {"All", "Walls", "Floors", "Chests", "Doors", "Decorations", "Stairs"};
|
||||
const char* object_types[] = {"All", "Walls", "Floors", "Chests",
|
||||
"Doors", "Decorations", "Stairs"};
|
||||
if (ImGui::Combo("Object Type", &object_type_filter, object_types, 7)) {
|
||||
// Filter will be applied in the loop below
|
||||
}
|
||||
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
|
||||
// Create asset browser-style grid
|
||||
const float item_size = 64.0f;
|
||||
const float item_spacing = 8.0f;
|
||||
const int columns = std::max(1, static_cast<int>((ImGui::GetContentRegionAvail().x - item_spacing) / (item_size + item_spacing)));
|
||||
|
||||
const int columns = std::max(
|
||||
1, static_cast<int>((ImGui::GetContentRegionAvail().x - item_spacing) /
|
||||
(item_size + item_spacing)));
|
||||
|
||||
ImGui::Text("Columns: %d, Item size: %.1f", columns, item_size);
|
||||
|
||||
|
||||
int current_column = 0;
|
||||
int items_drawn = 0;
|
||||
|
||||
|
||||
// Draw object grid based on filter
|
||||
for (int obj_id = 0; obj_id <= 0xFF && items_drawn < 100; ++obj_id) {
|
||||
// Apply object type filter
|
||||
if (object_type_filter > 0 && !MatchesObjectFilter(obj_id, object_type_filter)) {
|
||||
if (object_type_filter > 0 &&
|
||||
!MatchesObjectFilter(obj_id, object_type_filter)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if (current_column > 0) {
|
||||
ImGui::SameLine();
|
||||
}
|
||||
|
||||
|
||||
ImGui::PushID(obj_id);
|
||||
|
||||
|
||||
// Create selectable button for object
|
||||
bool is_selected = (selected_object_id_ == obj_id);
|
||||
ImVec2 button_size(item_size, item_size);
|
||||
|
||||
if (ImGui::Selectable("", is_selected, ImGuiSelectableFlags_None, button_size)) {
|
||||
|
||||
if (ImGui::Selectable("", is_selected, ImGuiSelectableFlags_None,
|
||||
button_size)) {
|
||||
selected_object_id_ = obj_id;
|
||||
|
||||
|
||||
// Create and update preview object
|
||||
preview_object_ = zelda3::RoomObject(obj_id, 0, 0, 0x12, 0);
|
||||
preview_object_.set_rom(rom_);
|
||||
if (rom_) {
|
||||
auto palette = rom_->palette_group().dungeon_main[current_palette_group_id_];
|
||||
auto palette =
|
||||
rom_->palette_group().dungeon_main[current_palette_group_id_];
|
||||
preview_palette_ = palette;
|
||||
}
|
||||
object_loaded_ = true;
|
||||
|
||||
|
||||
// Notify callback
|
||||
if (object_selected_callback_) {
|
||||
object_selected_callback_(preview_object_);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Draw object preview on the button
|
||||
ImVec2 button_pos = ImGui::GetItemRectMin();
|
||||
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
||||
|
||||
|
||||
// Draw object as colored rectangle with symbol
|
||||
ImU32 obj_color = GetObjectTypeColor(obj_id);
|
||||
draw_list->AddRectFilled(button_pos,
|
||||
ImVec2(button_pos.x + item_size, button_pos.y + item_size),
|
||||
obj_color);
|
||||
|
||||
draw_list->AddRectFilled(
|
||||
button_pos, ImVec2(button_pos.x + item_size, button_pos.y + item_size),
|
||||
obj_color);
|
||||
|
||||
// Draw border
|
||||
ImU32 border_color = is_selected ? IM_COL32(255, 255, 0, 255) : IM_COL32(0, 0, 0, 255);
|
||||
draw_list->AddRect(button_pos,
|
||||
ImVec2(button_pos.x + item_size, button_pos.y + item_size),
|
||||
border_color, 0.0f, 0, is_selected ? 3.0f : 1.0f);
|
||||
|
||||
ImU32 border_color =
|
||||
is_selected ? IM_COL32(255, 255, 0, 255) : IM_COL32(0, 0, 0, 255);
|
||||
draw_list->AddRect(
|
||||
button_pos, ImVec2(button_pos.x + item_size, button_pos.y + item_size),
|
||||
border_color, 0.0f, 0, is_selected ? 3.0f : 1.0f);
|
||||
|
||||
// Draw object symbol
|
||||
std::string symbol = GetObjectTypeSymbol(obj_id);
|
||||
ImVec2 text_size = ImGui::CalcTextSize(symbol.c_str());
|
||||
ImVec2 text_pos = ImVec2(
|
||||
button_pos.x + (item_size - text_size.x) / 2,
|
||||
button_pos.y + (item_size - text_size.y) / 2);
|
||||
ImVec2 text_pos = ImVec2(button_pos.x + (item_size - text_size.x) / 2,
|
||||
button_pos.y + (item_size - text_size.y) / 2);
|
||||
draw_list->AddText(text_pos, IM_COL32(255, 255, 255, 255), symbol.c_str());
|
||||
|
||||
|
||||
// Draw object ID at bottom
|
||||
std::string id_text = absl::StrFormat("%02X", obj_id);
|
||||
ImVec2 id_size = ImGui::CalcTextSize(id_text.c_str());
|
||||
ImVec2 id_pos = ImVec2(
|
||||
button_pos.x + (item_size - id_size.x) / 2,
|
||||
button_pos.y + item_size - id_size.y - 2);
|
||||
ImVec2 id_pos = ImVec2(button_pos.x + (item_size - id_size.x) / 2,
|
||||
button_pos.y + item_size - id_size.y - 2);
|
||||
draw_list->AddText(id_pos, IM_COL32(255, 255, 255, 255), id_text.c_str());
|
||||
|
||||
|
||||
ImGui::PopID();
|
||||
|
||||
|
||||
current_column = (current_column + 1) % columns;
|
||||
if (current_column == 0) {
|
||||
// Force new line
|
||||
}
|
||||
|
||||
|
||||
items_drawn++;
|
||||
}
|
||||
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Items drawn: %d", items_drawn);
|
||||
}
|
||||
|
||||
bool DungeonObjectSelector::MatchesObjectFilter(int obj_id, int filter_type) {
|
||||
switch (filter_type) {
|
||||
case 1: // Walls
|
||||
case 1: // Walls
|
||||
return obj_id >= 0x10 && obj_id <= 0x1F;
|
||||
case 2: // Floors
|
||||
case 2: // Floors
|
||||
return obj_id >= 0x20 && obj_id <= 0x2F;
|
||||
case 3: // Chests
|
||||
case 3: // Chests
|
||||
return obj_id == 0xF9 || obj_id == 0xFA;
|
||||
case 4: // Doors
|
||||
case 4: // Doors
|
||||
return obj_id >= 0x17 && obj_id <= 0x1E;
|
||||
case 5: // Decorations
|
||||
case 5: // Decorations
|
||||
return obj_id >= 0x30 && obj_id <= 0x3F;
|
||||
case 6: // Stairs
|
||||
case 6: // Stairs
|
||||
return obj_id >= 0x138 && obj_id <= 0x13B;
|
||||
default: // All
|
||||
default: // All
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void DungeonObjectSelector::CalculateObjectDimensions(const zelda3::RoomObject& object, int& width, int& height) {
|
||||
void DungeonObjectSelector::CalculateObjectDimensions(
|
||||
const zelda3::RoomObject& object, int& width, int& height) {
|
||||
// Default base size
|
||||
width = 16;
|
||||
height = 16;
|
||||
|
||||
|
||||
// For walls, use the size field to determine length
|
||||
if (object.id_ >= 0x10 && object.id_ <= 0x1F) {
|
||||
// Wall objects: size determines length and orientation
|
||||
uint8_t size_x = object.size_ & 0x0F;
|
||||
uint8_t size_y = (object.size_ >> 4) & 0x0F;
|
||||
|
||||
|
||||
// Walls can be horizontal or vertical based on size parameters
|
||||
if (size_x > size_y) {
|
||||
// Horizontal wall
|
||||
width = 16 + size_x * 16; // Each unit adds 16 pixels
|
||||
width = 16 + size_x * 16; // Each unit adds 16 pixels
|
||||
height = 16;
|
||||
} else if (size_y > size_x) {
|
||||
// Vertical wall
|
||||
@@ -698,7 +745,7 @@ void DungeonObjectSelector::CalculateObjectDimensions(const zelda3::RoomObject&
|
||||
width = 16 + (object.size_ & 0x0F) * 8;
|
||||
height = 16 + ((object.size_ >> 4) & 0x0F) * 8;
|
||||
}
|
||||
|
||||
|
||||
// Clamp to reasonable limits
|
||||
width = std::min(width, 256);
|
||||
height = std::min(height, 256);
|
||||
@@ -708,12 +755,12 @@ void DungeonObjectSelector::PlaceObjectAtPosition(int x, int y) {
|
||||
if (!object_loaded_ || !object_placement_callback_) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Create object with specified position
|
||||
auto placed_object = preview_object_;
|
||||
placed_object.set_x(static_cast<uint8_t>(x));
|
||||
placed_object.set_y(static_cast<uint8_t>(y));
|
||||
|
||||
|
||||
// Call placement callback
|
||||
object_placement_callback_(placed_object);
|
||||
}
|
||||
@@ -725,7 +772,7 @@ void DungeonObjectSelector::DrawCompactSpriteEditor() {
|
||||
}
|
||||
|
||||
auto& system = **dungeon_editor_system_;
|
||||
|
||||
|
||||
ImGui::Text("Sprite Editor");
|
||||
Separator();
|
||||
|
||||
@@ -740,7 +787,7 @@ void DungeonObjectSelector::DrawCompactSpriteEditor() {
|
||||
// Show first few sprites in compact format
|
||||
int display_count = std::min(3, static_cast<int>(sprites.size()));
|
||||
for (int i = 0; i < display_count; ++i) {
|
||||
const auto &sprite = sprites[i];
|
||||
const auto& sprite = sprites[i];
|
||||
ImGui::Text("ID:%d Type:%d (%d,%d)", sprite.sprite_id,
|
||||
static_cast<int>(sprite.type), sprite.x, sprite.y);
|
||||
}
|
||||
@@ -785,7 +832,7 @@ void DungeonObjectSelector::DrawCompactItemEditor() {
|
||||
}
|
||||
|
||||
auto& system = **dungeon_editor_system_;
|
||||
|
||||
|
||||
ImGui::Text("Item Editor");
|
||||
Separator();
|
||||
|
||||
@@ -800,7 +847,7 @@ void DungeonObjectSelector::DrawCompactItemEditor() {
|
||||
// Show first few items in compact format
|
||||
int display_count = std::min(3, static_cast<int>(items.size()));
|
||||
for (int i = 0; i < display_count; ++i) {
|
||||
const auto &item = items[i];
|
||||
const auto& item = items[i];
|
||||
ImGui::Text("ID:%d Type:%d (%d,%d)", item.item_id,
|
||||
static_cast<int>(item.type), item.x, item.y);
|
||||
}
|
||||
@@ -846,7 +893,7 @@ void DungeonObjectSelector::DrawCompactEntranceEditor() {
|
||||
}
|
||||
|
||||
auto& system = **dungeon_editor_system_;
|
||||
|
||||
|
||||
ImGui::Text("Entrance Editor");
|
||||
Separator();
|
||||
|
||||
@@ -858,7 +905,7 @@ void DungeonObjectSelector::DrawCompactEntranceEditor() {
|
||||
auto entrances = entrances_result.value();
|
||||
ImGui::Text("Entrances: %zu", entrances.size());
|
||||
|
||||
for (const auto &entrance : entrances) {
|
||||
for (const auto& entrance : entrances) {
|
||||
ImGui::Text("ID:%d -> Room:%d (%d,%d)", entrance.entrance_id,
|
||||
entrance.target_room_id, entrance.target_x,
|
||||
entrance.target_y);
|
||||
@@ -884,7 +931,8 @@ void DungeonObjectSelector::DrawCompactEntranceEditor() {
|
||||
ImGui::InputInt("Target Y", &target_y);
|
||||
|
||||
if (ImGui::Button("Connect")) {
|
||||
auto status = system.ConnectRooms(current_room, target_room_id, source_x, source_y, target_x, target_y);
|
||||
auto status = system.ConnectRooms(current_room, target_room_id, source_x,
|
||||
source_y, target_x, target_y);
|
||||
if (!status.ok()) {
|
||||
ImGui::Text("Error connecting rooms");
|
||||
}
|
||||
@@ -898,7 +946,7 @@ void DungeonObjectSelector::DrawCompactDoorEditor() {
|
||||
}
|
||||
|
||||
auto& system = **dungeon_editor_system_;
|
||||
|
||||
|
||||
ImGui::Text("Door Editor");
|
||||
Separator();
|
||||
|
||||
@@ -910,7 +958,7 @@ void DungeonObjectSelector::DrawCompactDoorEditor() {
|
||||
auto doors = doors_result.value();
|
||||
ImGui::Text("Doors: %zu", doors.size());
|
||||
|
||||
for (const auto &door : doors) {
|
||||
for (const auto& door : doors) {
|
||||
ImGui::Text("ID:%d (%d,%d) -> Room:%d", door.door_id, door.x, door.y,
|
||||
door.target_room_id);
|
||||
}
|
||||
@@ -959,7 +1007,7 @@ void DungeonObjectSelector::DrawCompactChestEditor() {
|
||||
}
|
||||
|
||||
auto& system = **dungeon_editor_system_;
|
||||
|
||||
|
||||
ImGui::Text("Chest Editor");
|
||||
Separator();
|
||||
|
||||
@@ -971,7 +1019,7 @@ void DungeonObjectSelector::DrawCompactChestEditor() {
|
||||
auto chests = chests_result.value();
|
||||
ImGui::Text("Chests: %zu", chests.size());
|
||||
|
||||
for (const auto &chest : chests) {
|
||||
for (const auto& chest : chests) {
|
||||
ImGui::Text("ID:%d (%d,%d) Item:%d", chest.chest_id, chest.x, chest.y,
|
||||
chest.item_id);
|
||||
}
|
||||
@@ -1016,16 +1064,16 @@ void DungeonObjectSelector::DrawCompactPropertiesEditor() {
|
||||
}
|
||||
|
||||
auto& system = **dungeon_editor_system_;
|
||||
|
||||
|
||||
ImGui::Text("Room Properties");
|
||||
Separator();
|
||||
|
||||
auto current_room = system.GetCurrentRoom();
|
||||
auto properties_result = system.GetRoomProperties(current_room);
|
||||
|
||||
|
||||
if (properties_result.ok()) {
|
||||
auto properties = properties_result.value();
|
||||
|
||||
|
||||
static char room_name[128] = {0};
|
||||
static int dungeon_id = 0;
|
||||
static int floor_level = 0;
|
||||
@@ -1060,7 +1108,7 @@ void DungeonObjectSelector::DrawCompactPropertiesEditor() {
|
||||
new_properties.is_boss_room = is_boss_room;
|
||||
new_properties.is_save_room = is_save_room;
|
||||
new_properties.music_id = music_id;
|
||||
|
||||
|
||||
auto status = system.SetRoomProperties(current_room, new_properties);
|
||||
if (!status.ok()) {
|
||||
ImGui::Text("Error saving properties");
|
||||
@@ -1073,7 +1121,7 @@ void DungeonObjectSelector::DrawCompactPropertiesEditor() {
|
||||
// Dungeon settings summary
|
||||
Separator();
|
||||
ImGui::Text("Dungeon Settings");
|
||||
|
||||
|
||||
auto dungeon_settings_result = system.GetDungeonSettings();
|
||||
if (dungeon_settings_result.ok()) {
|
||||
auto settings = dungeon_settings_result.value();
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
#include "app/gui/canvas/canvas.h"
|
||||
#include "app/rom.h"
|
||||
// object_renderer.h removed - using ObjectDrawer for production rendering
|
||||
#include "zelda3/dungeon/dungeon_object_editor.h"
|
||||
#include "zelda3/dungeon/dungeon_editor_system.h"
|
||||
#include "app/gfx/types/snes_palette.h"
|
||||
#include "imgui/imgui.h"
|
||||
#include "zelda3/dungeon/dungeon_editor_system.h"
|
||||
#include "zelda3/dungeon/dungeon_object_editor.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace editor {
|
||||
@@ -23,20 +23,17 @@ class DungeonObjectSelector {
|
||||
void DrawObjectRenderer();
|
||||
void DrawIntegratedEditingPanels();
|
||||
void Draw();
|
||||
|
||||
void set_rom(Rom* rom) {
|
||||
rom_ = rom;
|
||||
}
|
||||
void SetRom(Rom* rom) {
|
||||
rom_ = rom;
|
||||
}
|
||||
|
||||
void set_rom(Rom* rom) { rom_ = rom; }
|
||||
void SetRom(Rom* rom) { rom_ = rom; }
|
||||
Rom* rom() const { return rom_; }
|
||||
|
||||
// Editor system access
|
||||
void set_dungeon_editor_system(std::unique_ptr<zelda3::DungeonEditorSystem>* system) {
|
||||
dungeon_editor_system_ = system;
|
||||
void set_dungeon_editor_system(
|
||||
std::unique_ptr<zelda3::DungeonEditorSystem>* system) {
|
||||
dungeon_editor_system_ = system;
|
||||
}
|
||||
void set_object_editor(std::unique_ptr<zelda3::DungeonObjectEditor>* editor) {
|
||||
void set_object_editor(std::unique_ptr<zelda3::DungeonObjectEditor>* editor) {
|
||||
object_editor_ = editor ? editor->get() : nullptr;
|
||||
}
|
||||
|
||||
@@ -45,19 +42,27 @@ class DungeonObjectSelector {
|
||||
void set_current_room_id(int room_id) { current_room_id_ = room_id; }
|
||||
|
||||
// Palette access
|
||||
void set_current_palette_group_id(uint64_t id) { current_palette_group_id_ = id; }
|
||||
void SetCurrentPaletteGroup(const gfx::PaletteGroup& palette_group) { current_palette_group_ = palette_group; }
|
||||
void SetCurrentPaletteId(uint64_t palette_id) { current_palette_id_ = palette_id; }
|
||||
|
||||
void set_current_palette_group_id(uint64_t id) {
|
||||
current_palette_group_id_ = id;
|
||||
}
|
||||
void SetCurrentPaletteGroup(const gfx::PaletteGroup& palette_group) {
|
||||
current_palette_group_ = palette_group;
|
||||
}
|
||||
void SetCurrentPaletteId(uint64_t palette_id) {
|
||||
current_palette_id_ = palette_id;
|
||||
}
|
||||
|
||||
// Object selection callbacks
|
||||
void SetObjectSelectedCallback(std::function<void(const zelda3::RoomObject&)> callback) {
|
||||
void SetObjectSelectedCallback(
|
||||
std::function<void(const zelda3::RoomObject&)> callback) {
|
||||
object_selected_callback_ = callback;
|
||||
}
|
||||
|
||||
void SetObjectPlacementCallback(std::function<void(const zelda3::RoomObject&)> callback) {
|
||||
|
||||
void SetObjectPlacementCallback(
|
||||
std::function<void(const zelda3::RoomObject&)> callback) {
|
||||
object_placement_callback_ = callback;
|
||||
}
|
||||
|
||||
|
||||
// Get current preview object for placement
|
||||
const zelda3::RoomObject& GetPreviewObject() const { return preview_object_; }
|
||||
bool IsObjectLoaded() const { return object_loaded_; }
|
||||
@@ -67,16 +72,17 @@ class DungeonObjectSelector {
|
||||
void DrawObjectBrowser();
|
||||
void DrawCompactObjectEditor();
|
||||
void DrawCompactSpriteEditor();
|
||||
|
||||
|
||||
// Helper methods for primitive object rendering
|
||||
ImU32 GetObjectTypeColor(int object_id);
|
||||
std::string GetObjectTypeSymbol(int object_id);
|
||||
void RenderObjectPrimitive(const zelda3::RoomObject& object, int x, int y);
|
||||
|
||||
|
||||
// AssetBrowser-style object selection
|
||||
void DrawObjectAssetBrowser();
|
||||
bool MatchesObjectFilter(int obj_id, int filter_type);
|
||||
void CalculateObjectDimensions(const zelda3::RoomObject& object, int& width, int& height);
|
||||
void CalculateObjectDimensions(const zelda3::RoomObject& object, int& width,
|
||||
int& height);
|
||||
void PlaceObjectAtPosition(int x, int y);
|
||||
void DrawCompactItemEditor();
|
||||
void DrawCompactEntranceEditor();
|
||||
@@ -85,32 +91,34 @@ class DungeonObjectSelector {
|
||||
void DrawCompactPropertiesEditor();
|
||||
|
||||
Rom* rom_ = nullptr;
|
||||
gui::Canvas room_gfx_canvas_{"##RoomGfxCanvas", ImVec2(0x100 + 1, 0x10 * 0x40 + 1)};
|
||||
gui::Canvas room_gfx_canvas_{"##RoomGfxCanvas",
|
||||
ImVec2(0x100 + 1, 0x10 * 0x40 + 1)};
|
||||
gui::Canvas object_canvas_;
|
||||
// ObjectRenderer removed - using ObjectDrawer in Room::RenderObjectsToBackground()
|
||||
|
||||
|
||||
// Editor systems
|
||||
std::unique_ptr<zelda3::DungeonEditorSystem>* dungeon_editor_system_ = nullptr;
|
||||
std::unique_ptr<zelda3::DungeonEditorSystem>* dungeon_editor_system_ =
|
||||
nullptr;
|
||||
zelda3::DungeonObjectEditor* object_editor_ = nullptr;
|
||||
|
||||
|
||||
// Room data
|
||||
std::array<zelda3::Room, 0x128>* rooms_ = nullptr;
|
||||
int current_room_id_ = 0;
|
||||
|
||||
|
||||
// Palette data
|
||||
uint64_t current_palette_group_id_ = 0;
|
||||
uint64_t current_palette_id_ = 0;
|
||||
gfx::PaletteGroup current_palette_group_;
|
||||
|
||||
|
||||
// Object preview system
|
||||
zelda3::RoomObject preview_object_{0, 0, 0, 0, 0};
|
||||
gfx::SnesPalette preview_palette_;
|
||||
bool object_loaded_ = false;
|
||||
|
||||
|
||||
// Callback for object selection
|
||||
std::function<void(const zelda3::RoomObject&)> object_selected_callback_;
|
||||
std::function<void(const zelda3::RoomObject&)> object_placement_callback_;
|
||||
|
||||
|
||||
// Object selection state
|
||||
int selected_object_id_ = -1;
|
||||
};
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
#include "dungeon_room_loader.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <future>
|
||||
#include <thread>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
#include "app/gfx/debug/performance/performance_profiler.h"
|
||||
#include "app/gfx/types/snes_palette.h"
|
||||
#include "zelda3/dungeon/room.h"
|
||||
#include "util/log.h"
|
||||
#include "zelda3/dungeon/room.h"
|
||||
|
||||
namespace yaze::editor {
|
||||
|
||||
@@ -27,55 +27,60 @@ absl::Status DungeonRoomLoader::LoadRoom(int room_id, zelda3::Room& room) {
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status DungeonRoomLoader::LoadAllRooms(std::array<zelda3::Room, 0x128>& rooms) {
|
||||
absl::Status DungeonRoomLoader::LoadAllRooms(
|
||||
std::array<zelda3::Room, 0x128>& rooms) {
|
||||
if (!rom_ || !rom_->is_loaded()) {
|
||||
return absl::FailedPreconditionError("ROM not loaded");
|
||||
}
|
||||
|
||||
constexpr int kTotalRooms = 0x100 + 40; // 296 rooms
|
||||
constexpr int kMaxConcurrency = 8; // Reasonable thread limit for room loading
|
||||
|
||||
|
||||
constexpr int kTotalRooms = 0x100 + 40; // 296 rooms
|
||||
constexpr int kMaxConcurrency =
|
||||
8; // Reasonable thread limit for room loading
|
||||
|
||||
// Determine optimal number of threads
|
||||
const int max_concurrency = std::min(kMaxConcurrency,
|
||||
static_cast<int>(std::thread::hardware_concurrency()));
|
||||
const int rooms_per_thread = (kTotalRooms + max_concurrency - 1) / max_concurrency;
|
||||
|
||||
LOG_DEBUG("Dungeon", "Loading %d dungeon rooms using %d threads (%d rooms per thread)",
|
||||
kTotalRooms, max_concurrency, rooms_per_thread);
|
||||
|
||||
const int max_concurrency = std::min(
|
||||
kMaxConcurrency, static_cast<int>(std::thread::hardware_concurrency()));
|
||||
const int rooms_per_thread =
|
||||
(kTotalRooms + max_concurrency - 1) / max_concurrency;
|
||||
|
||||
LOG_DEBUG("Dungeon",
|
||||
"Loading %d dungeon rooms using %d threads (%d rooms per thread)",
|
||||
kTotalRooms, max_concurrency, rooms_per_thread);
|
||||
|
||||
// Thread-safe data structures for collecting results
|
||||
std::mutex results_mutex;
|
||||
std::vector<std::pair<int, zelda3::RoomSize>> room_size_results;
|
||||
std::vector<std::pair<int, ImVec4>> room_palette_results;
|
||||
|
||||
|
||||
// Process rooms in parallel batches
|
||||
std::vector<std::future<absl::Status>> futures;
|
||||
|
||||
|
||||
for (int thread_id = 0; thread_id < max_concurrency; ++thread_id) {
|
||||
auto task = [this, &rooms, thread_id, rooms_per_thread, &results_mutex,
|
||||
&room_size_results, &room_palette_results, kTotalRooms]() -> absl::Status {
|
||||
auto task = [this, &rooms, thread_id, rooms_per_thread, &results_mutex,
|
||||
&room_size_results, &room_palette_results,
|
||||
kTotalRooms]() -> absl::Status {
|
||||
const int start_room = thread_id * rooms_per_thread;
|
||||
const int end_room = std::min(start_room + rooms_per_thread, kTotalRooms);
|
||||
|
||||
|
||||
auto dungeon_man_pal_group = rom_->palette_group().dungeon_main;
|
||||
|
||||
|
||||
for (int i = start_room; i < end_room; ++i) {
|
||||
// Load room data (this is the expensive operation)
|
||||
rooms[i] = zelda3::LoadRoomFromRom(rom_, i);
|
||||
|
||||
|
||||
// Calculate room size
|
||||
auto room_size = zelda3::CalculateRoomSize(rom_, i);
|
||||
|
||||
|
||||
// Load room objects
|
||||
rooms[i].LoadObjects();
|
||||
|
||||
|
||||
// Process palette
|
||||
auto dungeon_palette_ptr = rom_->paletteset_ids[rooms[i].palette][0];
|
||||
auto palette_id = rom_->ReadWord(0xDEC4B + dungeon_palette_ptr);
|
||||
if (palette_id.status() == absl::OkStatus()) {
|
||||
int p_id = palette_id.value() / 180;
|
||||
auto color = dungeon_man_pal_group[p_id][3];
|
||||
|
||||
|
||||
// Thread-safe collection of results
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(results_mutex);
|
||||
@@ -84,28 +89,28 @@ absl::Status DungeonRoomLoader::LoadAllRooms(std::array<zelda3::Room, 0x128>& ro
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return absl::OkStatus();
|
||||
};
|
||||
|
||||
|
||||
futures.emplace_back(std::async(std::launch::async, task));
|
||||
}
|
||||
|
||||
|
||||
// Wait for all threads to complete
|
||||
for (auto& future : futures) {
|
||||
RETURN_IF_ERROR(future.get());
|
||||
}
|
||||
|
||||
|
||||
// Process collected results on main thread
|
||||
{
|
||||
gfx::ScopedTimer postprocess_timer("DungeonRoomLoader::PostProcessResults");
|
||||
|
||||
|
||||
// Sort results by room ID for consistent ordering
|
||||
std::sort(room_size_results.begin(), room_size_results.end(),
|
||||
std::sort(room_size_results.begin(), room_size_results.end(),
|
||||
[](const auto& a, const auto& b) { return a.first < b.first; });
|
||||
std::sort(room_palette_results.begin(), room_palette_results.end(),
|
||||
[](const auto& a, const auto& b) { return a.first < b.first; });
|
||||
|
||||
|
||||
// Process room size results
|
||||
for (const auto& [room_id, room_size] : room_size_results) {
|
||||
room_size_pointers_.push_back(room_size.room_size_pointer);
|
||||
@@ -114,22 +119,23 @@ absl::Status DungeonRoomLoader::LoadAllRooms(std::array<zelda3::Room, 0x128>& ro
|
||||
room_size_addresses_[room_id] = room_size.room_size_pointer;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Process palette results
|
||||
for (const auto& [palette_id, color] : room_palette_results) {
|
||||
room_palette_[palette_id] = color;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
LoadDungeonRoomSize();
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status DungeonRoomLoader::LoadRoomEntrances(std::array<zelda3::RoomEntrance, 0x8C>& entrances) {
|
||||
absl::Status DungeonRoomLoader::LoadRoomEntrances(
|
||||
std::array<zelda3::RoomEntrance, 0x8C>& entrances) {
|
||||
if (!rom_ || !rom_->is_loaded()) {
|
||||
return absl::FailedPreconditionError("ROM not loaded");
|
||||
}
|
||||
|
||||
|
||||
// Load entrances
|
||||
for (int i = 0; i < 0x07; ++i) {
|
||||
entrances[i] = zelda3::RoomEntrance(rom_, i, true);
|
||||
@@ -138,7 +144,7 @@ absl::Status DungeonRoomLoader::LoadRoomEntrances(std::array<zelda3::RoomEntranc
|
||||
for (int i = 0; i < 0x85; ++i) {
|
||||
entrances[i + 0x07] = zelda3::RoomEntrance(rom_, i, false);
|
||||
}
|
||||
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
@@ -182,29 +188,30 @@ absl::Status DungeonRoomLoader::LoadAndRenderRoomGraphics(zelda3::Room& room) {
|
||||
if (!rom_ || !rom_->is_loaded()) {
|
||||
return absl::FailedPreconditionError("ROM not loaded");
|
||||
}
|
||||
|
||||
|
||||
// Load room graphics with proper blockset
|
||||
room.LoadRoomGraphics(room.blockset);
|
||||
|
||||
|
||||
// Render the room graphics to the graphics arena
|
||||
room.RenderRoomGraphics();
|
||||
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status DungeonRoomLoader::ReloadAllRoomGraphics(std::array<zelda3::Room, 0x128>& rooms) {
|
||||
absl::Status DungeonRoomLoader::ReloadAllRoomGraphics(
|
||||
std::array<zelda3::Room, 0x128>& rooms) {
|
||||
if (!rom_ || !rom_->is_loaded()) {
|
||||
return absl::FailedPreconditionError("ROM not loaded");
|
||||
}
|
||||
|
||||
|
||||
// Reload graphics for all rooms
|
||||
for (auto& room : rooms) {
|
||||
auto status = LoadAndRenderRoomGraphics(room);
|
||||
if (!status.ok()) {
|
||||
continue; // Log error but continue with other rooms
|
||||
continue; // Log error but continue with other rooms
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#ifndef YAZE_APP_EDITOR_DUNGEON_DUNGEON_ROOM_LOADER_H
|
||||
#define YAZE_APP_EDITOR_DUNGEON_DUNGEON_ROOM_LOADER_H
|
||||
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "app/rom.h"
|
||||
@@ -21,29 +21,36 @@ namespace editor {
|
||||
class DungeonRoomLoader {
|
||||
public:
|
||||
explicit DungeonRoomLoader(Rom* rom) : rom_(rom) {}
|
||||
|
||||
|
||||
// Room loading
|
||||
absl::Status LoadRoom(int room_id, zelda3::Room& room);
|
||||
absl::Status LoadAllRooms(std::array<zelda3::Room, 0x128>& rooms);
|
||||
absl::Status LoadRoomEntrances(std::array<zelda3::RoomEntrance, 0x8C>& entrances);
|
||||
|
||||
absl::Status LoadRoomEntrances(
|
||||
std::array<zelda3::RoomEntrance, 0x8C>& entrances);
|
||||
|
||||
// Room size management
|
||||
void LoadDungeonRoomSize();
|
||||
uint64_t GetTotalRoomSize() const { return total_room_size_; }
|
||||
|
||||
|
||||
// Room graphics
|
||||
absl::Status LoadAndRenderRoomGraphics(zelda3::Room& room);
|
||||
absl::Status ReloadAllRoomGraphics(std::array<zelda3::Room, 0x128>& rooms);
|
||||
|
||||
|
||||
// Data access
|
||||
const std::vector<int64_t>& GetRoomSizePointers() const { return room_size_pointers_; }
|
||||
const std::vector<int64_t>& GetRoomSizePointers() const {
|
||||
return room_size_pointers_;
|
||||
}
|
||||
const std::vector<int64_t>& GetRoomSizes() const { return room_sizes_; }
|
||||
const std::unordered_map<int, int>& GetRoomSizeAddresses() const { return room_size_addresses_; }
|
||||
const std::unordered_map<int, ImVec4>& GetRoomPalette() const { return room_palette_; }
|
||||
const std::unordered_map<int, int>& GetRoomSizeAddresses() const {
|
||||
return room_size_addresses_;
|
||||
}
|
||||
const std::unordered_map<int, ImVec4>& GetRoomPalette() const {
|
||||
return room_palette_;
|
||||
}
|
||||
|
||||
private:
|
||||
Rom* rom_;
|
||||
|
||||
|
||||
std::vector<int64_t> room_size_pointers_;
|
||||
std::vector<int64_t> room_sizes_;
|
||||
std::unordered_map<int, int> room_size_addresses_;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#include "dungeon_room_selector.h"
|
||||
|
||||
#include "app/gui/core/input.h"
|
||||
#include "zelda3/dungeon/room.h"
|
||||
#include "zelda3/dungeon/room_entrance.h"
|
||||
#include "imgui/imgui.h"
|
||||
#include "util/hex.h"
|
||||
#include "zelda3/dungeon/room.h"
|
||||
#include "zelda3/dungeon/room_entrance.h"
|
||||
|
||||
namespace yaze::editor {
|
||||
|
||||
@@ -34,7 +34,7 @@ void DungeonRoomSelector::DrawRoomSelector() {
|
||||
|
||||
gui::InputHexWord("Room ID", ¤t_room_id_, 50.f, true);
|
||||
|
||||
if (ImGuiID child_id = ImGui::GetID((void *)(intptr_t)9);
|
||||
if (ImGuiID child_id = ImGui::GetID((void*)(intptr_t)9);
|
||||
BeginChild(child_id, ImGui::GetContentRegionAvail(), true,
|
||||
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
|
||||
int i = 0;
|
||||
@@ -125,8 +125,8 @@ void DungeonRoomSelector::DrawEntranceSelector() {
|
||||
entrance_name = std::string(zelda3::kEntranceNames[i]);
|
||||
}
|
||||
rom_->resource_label()->SelectableLabelWithNameEdit(
|
||||
current_entrance_id_ == i, "Dungeon Entrance Names",
|
||||
util::HexByte(i), entrance_name);
|
||||
current_entrance_id_ == i, "Dungeon Entrance Names", util::HexByte(i),
|
||||
entrance_name);
|
||||
|
||||
if (ImGui::IsItemClicked()) {
|
||||
current_entrance_id_ = i;
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
#define YAZE_APP_EDITOR_DUNGEON_DUNGEON_ROOM_SELECTOR_H
|
||||
|
||||
#include <functional>
|
||||
#include "imgui/imgui.h"
|
||||
#include "app/rom.h"
|
||||
#include "zelda3/dungeon/room_entrance.h"
|
||||
#include "imgui/imgui.h"
|
||||
#include "zelda3/dungeon/room.h"
|
||||
#include "zelda3/dungeon/room_entrance.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace editor {
|
||||
@@ -20,29 +20,33 @@ class DungeonRoomSelector {
|
||||
void Draw();
|
||||
void DrawRoomSelector();
|
||||
void DrawEntranceSelector();
|
||||
|
||||
|
||||
void set_rom(Rom* rom) { rom_ = rom; }
|
||||
Rom* rom() const { return rom_; }
|
||||
|
||||
// Room selection
|
||||
void set_current_room_id(uint16_t room_id) { current_room_id_ = room_id; }
|
||||
int current_room_id() const { return current_room_id_; }
|
||||
|
||||
|
||||
void set_active_rooms(const ImVector<int>& rooms) { active_rooms_ = rooms; }
|
||||
const ImVector<int>& active_rooms() const { return active_rooms_; }
|
||||
ImVector<int>& mutable_active_rooms() { return active_rooms_; }
|
||||
|
||||
// Entrance selection
|
||||
void set_current_entrance_id(int entrance_id) { current_entrance_id_ = entrance_id; }
|
||||
void set_current_entrance_id(int entrance_id) {
|
||||
current_entrance_id_ = entrance_id;
|
||||
}
|
||||
int current_entrance_id() const { return current_entrance_id_; }
|
||||
|
||||
// Room data access
|
||||
void set_rooms(std::array<zelda3::Room, 0x128>* rooms) { rooms_ = rooms; }
|
||||
void set_entrances(std::array<zelda3::RoomEntrance, 0x8C>* entrances) { entrances_ = entrances; }
|
||||
void set_entrances(std::array<zelda3::RoomEntrance, 0x8C>* entrances) {
|
||||
entrances_ = entrances;
|
||||
}
|
||||
|
||||
// Callback for room selection events
|
||||
void set_room_selected_callback(std::function<void(int)> callback) {
|
||||
room_selected_callback_ = callback;
|
||||
void set_room_selected_callback(std::function<void(int)> callback) {
|
||||
room_selected_callback_ = callback;
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -50,10 +54,10 @@ class DungeonRoomSelector {
|
||||
uint16_t current_room_id_ = 0;
|
||||
int current_entrance_id_ = 0;
|
||||
ImVector<int> active_rooms_;
|
||||
|
||||
|
||||
std::array<zelda3::Room, 0x128>* rooms_ = nullptr;
|
||||
std::array<zelda3::RoomEntrance, 0x8C>* entrances_ = nullptr;
|
||||
|
||||
|
||||
// Callback for room selection events
|
||||
std::function<void(int)> room_selected_callback_;
|
||||
};
|
||||
|
||||
@@ -17,7 +17,8 @@ using ImGui::TableSetupColumn;
|
||||
using ImGui::Text;
|
||||
|
||||
void DungeonToolset::Draw() {
|
||||
if (BeginTable("DWToolset", 16, ImGuiTableFlags_SizingFixedFit, ImVec2(0, 0))) {
|
||||
if (BeginTable("DWToolset", 16, ImGuiTableFlags_SizingFixedFit,
|
||||
ImVec2(0, 0))) {
|
||||
static std::array<const char*, 16> tool_names = {
|
||||
"Undo", "Redo", "Separator", "All", "BG1", "BG2",
|
||||
"BG3", "Separator", "Object", "Sprite", "Item", "Entrance",
|
||||
@@ -28,13 +29,15 @@ void DungeonToolset::Draw() {
|
||||
// Undo button
|
||||
TableNextColumn();
|
||||
if (Button(ICON_MD_UNDO)) {
|
||||
if (undo_callback_) undo_callback_();
|
||||
if (undo_callback_)
|
||||
undo_callback_();
|
||||
}
|
||||
|
||||
// Redo button
|
||||
TableNextColumn();
|
||||
if (Button(ICON_MD_REDO)) {
|
||||
if (redo_callback_) redo_callback_();
|
||||
if (redo_callback_)
|
||||
redo_callback_();
|
||||
}
|
||||
|
||||
// Separator
|
||||
@@ -138,14 +141,17 @@ void DungeonToolset::Draw() {
|
||||
// Palette button
|
||||
TableNextColumn();
|
||||
if (Button(ICON_MD_PALETTE)) {
|
||||
if (palette_toggle_callback_) palette_toggle_callback_();
|
||||
if (palette_toggle_callback_)
|
||||
palette_toggle_callback_();
|
||||
}
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Instructions: Click to place objects, Ctrl+Click to select, drag to move");
|
||||
ImGui::Text(
|
||||
"Instructions: Click to place objects, Ctrl+Click to select, drag to "
|
||||
"move");
|
||||
}
|
||||
|
||||
} // namespace yaze::editor
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#ifndef YAZE_APP_EDITOR_DUNGEON_DUNGEON_TOOLSET_H
|
||||
#define YAZE_APP_EDITOR_DUNGEON_DUNGEON_TOOLSET_H
|
||||
|
||||
#include <functional>
|
||||
#include <array>
|
||||
#include <functional>
|
||||
|
||||
#include "imgui/imgui.h"
|
||||
|
||||
@@ -24,9 +24,9 @@ class DungeonToolset {
|
||||
kBackground3,
|
||||
kBackgroundAny,
|
||||
};
|
||||
|
||||
enum PlacementType {
|
||||
kNoType,
|
||||
|
||||
enum PlacementType {
|
||||
kNoType,
|
||||
kObject, // Object editing mode
|
||||
kSprite, // Sprite editing mode
|
||||
kItem, // Item placement mode
|
||||
@@ -37,26 +37,32 @@ class DungeonToolset {
|
||||
};
|
||||
|
||||
DungeonToolset() = default;
|
||||
|
||||
|
||||
void Draw();
|
||||
|
||||
|
||||
// Getters
|
||||
BackgroundType background_type() const { return background_type_; }
|
||||
PlacementType placement_type() const { return placement_type_; }
|
||||
|
||||
|
||||
// Setters
|
||||
void set_background_type(BackgroundType type) { background_type_ = type; }
|
||||
void set_placement_type(PlacementType type) { placement_type_ = type; }
|
||||
|
||||
|
||||
// Callbacks
|
||||
void SetUndoCallback(std::function<void()> callback) { undo_callback_ = callback; }
|
||||
void SetRedoCallback(std::function<void()> callback) { redo_callback_ = callback; }
|
||||
void SetPaletteToggleCallback(std::function<void()> callback) { palette_toggle_callback_ = callback; }
|
||||
void SetUndoCallback(std::function<void()> callback) {
|
||||
undo_callback_ = callback;
|
||||
}
|
||||
void SetRedoCallback(std::function<void()> callback) {
|
||||
redo_callback_ = callback;
|
||||
}
|
||||
void SetPaletteToggleCallback(std::function<void()> callback) {
|
||||
palette_toggle_callback_ = callback;
|
||||
}
|
||||
|
||||
private:
|
||||
BackgroundType background_type_ = kBackgroundAny;
|
||||
PlacementType placement_type_ = kNoType;
|
||||
|
||||
|
||||
// Callbacks for editor actions
|
||||
std::function<void()> undo_callback_;
|
||||
std::function<void()> redo_callback_;
|
||||
|
||||
@@ -4,11 +4,12 @@
|
||||
|
||||
namespace yaze::editor {
|
||||
|
||||
void DungeonUsageTracker::CalculateUsageStats(const std::array<zelda3::Room, 0x128>& rooms) {
|
||||
void DungeonUsageTracker::CalculateUsageStats(
|
||||
const std::array<zelda3::Room, 0x128>& rooms) {
|
||||
blockset_usage_.clear();
|
||||
spriteset_usage_.clear();
|
||||
palette_usage_.clear();
|
||||
|
||||
|
||||
for (const auto& room : rooms) {
|
||||
if (blockset_usage_.find(room.blockset) == blockset_usage_.end()) {
|
||||
blockset_usage_[room.blockset] = 1;
|
||||
@@ -34,29 +35,29 @@ void DungeonUsageTracker::DrawUsageStats() {
|
||||
if (ImGui::Button("Refresh")) {
|
||||
ClearUsageStats();
|
||||
}
|
||||
|
||||
|
||||
ImGui::Text("Usage Statistics");
|
||||
ImGui::Separator();
|
||||
|
||||
|
||||
ImGui::Text("Blocksets: %zu used", blockset_usage_.size());
|
||||
ImGui::Text("Spritesets: %zu used", spriteset_usage_.size());
|
||||
ImGui::Text("Palettes: %zu used", palette_usage_.size());
|
||||
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
|
||||
// Detailed usage breakdown
|
||||
if (ImGui::CollapsingHeader("Blockset Usage")) {
|
||||
for (const auto& [blockset, count] : blockset_usage_) {
|
||||
ImGui::Text("Blockset 0x%02X: %d rooms", blockset, count);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (ImGui::CollapsingHeader("Spriteset Usage")) {
|
||||
for (const auto& [spriteset, count] : spriteset_usage_) {
|
||||
ImGui::Text("Spriteset 0x%02X: %d rooms", spriteset, count);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (ImGui::CollapsingHeader("Palette Usage")) {
|
||||
for (const auto& [palette, count] : palette_usage_) {
|
||||
ImGui::Text("Palette 0x%02X: %d rooms", palette, count);
|
||||
@@ -69,8 +70,9 @@ void DungeonUsageTracker::DrawUsageGrid() {
|
||||
ImGui::Text("Usage grid visualization not yet implemented");
|
||||
}
|
||||
|
||||
void DungeonUsageTracker::RenderSetUsage(const absl::flat_hash_map<uint16_t, int>& usage_map,
|
||||
uint16_t& selected_set, int spriteset_offset) {
|
||||
void DungeonUsageTracker::RenderSetUsage(
|
||||
const absl::flat_hash_map<uint16_t, int>& usage_map, uint16_t& selected_set,
|
||||
int spriteset_offset) {
|
||||
// TODO: Implement set usage rendering
|
||||
ImGui::Text("Set usage rendering not yet implemented");
|
||||
}
|
||||
|
||||
@@ -16,28 +16,36 @@ namespace editor {
|
||||
class DungeonUsageTracker {
|
||||
public:
|
||||
DungeonUsageTracker() = default;
|
||||
|
||||
|
||||
// Statistics calculation
|
||||
void CalculateUsageStats(const std::array<zelda3::Room, 0x128>& rooms);
|
||||
void DrawUsageStats();
|
||||
void DrawUsageGrid();
|
||||
void RenderSetUsage(const absl::flat_hash_map<uint16_t, int>& usage_map,
|
||||
uint16_t& selected_set, int spriteset_offset = 0x00);
|
||||
|
||||
|
||||
// Data access
|
||||
const absl::flat_hash_map<uint16_t, int>& GetBlocksetUsage() const { return blockset_usage_; }
|
||||
const absl::flat_hash_map<uint16_t, int>& GetSpritesetUsage() const { return spriteset_usage_; }
|
||||
const absl::flat_hash_map<uint16_t, int>& GetPaletteUsage() const { return palette_usage_; }
|
||||
|
||||
const absl::flat_hash_map<uint16_t, int>& GetBlocksetUsage() const {
|
||||
return blockset_usage_;
|
||||
}
|
||||
const absl::flat_hash_map<uint16_t, int>& GetSpritesetUsage() const {
|
||||
return spriteset_usage_;
|
||||
}
|
||||
const absl::flat_hash_map<uint16_t, int>& GetPaletteUsage() const {
|
||||
return palette_usage_;
|
||||
}
|
||||
|
||||
// Selection state
|
||||
uint16_t GetSelectedBlockset() const { return selected_blockset_; }
|
||||
uint16_t GetSelectedSpriteset() const { return selected_spriteset_; }
|
||||
uint16_t GetSelectedPalette() const { return selected_palette_; }
|
||||
|
||||
|
||||
void SetSelectedBlockset(uint16_t blockset) { selected_blockset_ = blockset; }
|
||||
void SetSelectedSpriteset(uint16_t spriteset) { selected_spriteset_ = spriteset; }
|
||||
void SetSelectedSpriteset(uint16_t spriteset) {
|
||||
selected_spriteset_ = spriteset;
|
||||
}
|
||||
void SetSelectedPalette(uint16_t palette) { selected_palette_ = palette; }
|
||||
|
||||
|
||||
// Clear data
|
||||
void ClearUsageStats();
|
||||
|
||||
|
||||
@@ -8,8 +8,12 @@
|
||||
|
||||
namespace yaze::editor {
|
||||
|
||||
ObjectEditorCard::ObjectEditorCard(gfx::IRenderer* renderer, Rom* rom, DungeonCanvasViewer* canvas_viewer)
|
||||
: renderer_(renderer), rom_(rom), canvas_viewer_(canvas_viewer), object_selector_(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);
|
||||
}
|
||||
|
||||
@@ -17,20 +21,22 @@ void ObjectEditorCard::Draw(bool* p_open) {
|
||||
gui::EditorCard card("Object Editor", ICON_MD_CONSTRUCTION, p_open);
|
||||
card.SetDefaultSize(450, 750);
|
||||
card.SetPosition(gui::EditorCard::Position::Right);
|
||||
|
||||
|
||||
if (card.Begin(p_open)) {
|
||||
// Interaction mode controls at top (moved from tab)
|
||||
ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "Mode:");
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::RadioButton("None", interaction_mode_ == InteractionMode::None)) {
|
||||
|
||||
if (ImGui::RadioButton("None",
|
||||
interaction_mode_ == InteractionMode::None)) {
|
||||
interaction_mode_ = InteractionMode::None;
|
||||
canvas_viewer_->SetObjectInteractionEnabled(false);
|
||||
canvas_viewer_->ClearPreviewObject();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::RadioButton("Place", interaction_mode_ == InteractionMode::Place)) {
|
||||
|
||||
if (ImGui::RadioButton("Place",
|
||||
interaction_mode_ == InteractionMode::Place)) {
|
||||
interaction_mode_ = InteractionMode::Place;
|
||||
canvas_viewer_->SetObjectInteractionEnabled(true);
|
||||
if (has_preview_object_) {
|
||||
@@ -38,40 +44,42 @@ void ObjectEditorCard::Draw(bool* p_open) {
|
||||
}
|
||||
}
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::RadioButton("Select", interaction_mode_ == InteractionMode::Select)) {
|
||||
|
||||
if (ImGui::RadioButton("Select",
|
||||
interaction_mode_ == InteractionMode::Select)) {
|
||||
interaction_mode_ = InteractionMode::Select;
|
||||
canvas_viewer_->SetObjectInteractionEnabled(true);
|
||||
canvas_viewer_->ClearPreviewObject();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::RadioButton("Delete", interaction_mode_ == InteractionMode::Delete)) {
|
||||
|
||||
if (ImGui::RadioButton("Delete",
|
||||
interaction_mode_ == InteractionMode::Delete)) {
|
||||
interaction_mode_ = InteractionMode::Delete;
|
||||
canvas_viewer_->SetObjectInteractionEnabled(true);
|
||||
canvas_viewer_->ClearPreviewObject();
|
||||
}
|
||||
|
||||
|
||||
// Current object info
|
||||
DrawSelectedObjectInfo();
|
||||
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
|
||||
// Tabbed interface for Browser and Preview
|
||||
if (ImGui::BeginTabBar("##ObjectEditorTabs", ImGuiTabBarFlags_None)) {
|
||||
|
||||
|
||||
// Tab 1: Object Browser
|
||||
if (ImGui::BeginTabItem(ICON_MD_LIST " Browser")) {
|
||||
DrawObjectSelector();
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
|
||||
// Tab 2: Emulator Preview (enhanced)
|
||||
if (ImGui::BeginTabItem(ICON_MD_MONITOR " Preview")) {
|
||||
DrawEmulatorPreview();
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
}
|
||||
@@ -81,61 +89,61 @@ void ObjectEditorCard::Draw(bool* p_open) {
|
||||
void ObjectEditorCard::DrawObjectSelector() {
|
||||
ImGui::Text(ICON_MD_INFO " Select an object to place on the canvas");
|
||||
ImGui::Separator();
|
||||
|
||||
|
||||
// Text filter for objects
|
||||
static char object_filter[256] = "";
|
||||
ImGui::SetNextItemWidth(-1);
|
||||
if (ImGui::InputTextWithHint("##ObjectFilter",
|
||||
ICON_MD_SEARCH " Filter objects...",
|
||||
if (ImGui::InputTextWithHint("##ObjectFilter",
|
||||
ICON_MD_SEARCH " Filter objects...",
|
||||
object_filter, sizeof(object_filter))) {
|
||||
// Filter updated
|
||||
}
|
||||
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
|
||||
// Object list with categories
|
||||
if (ImGui::BeginChild("##ObjectList", ImVec2(0, 0), true)) {
|
||||
// Floor objects
|
||||
if (ImGui::CollapsingHeader(ICON_MD_GRID_ON " Floor Objects",
|
||||
if (ImGui::CollapsingHeader(ICON_MD_GRID_ON " Floor Objects",
|
||||
ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
for (int i = 0; i < 0x100; i++) {
|
||||
std::string filter_str = object_filter;
|
||||
if (!filter_str.empty()) {
|
||||
// Simple name-based filtering
|
||||
std::string object_name = absl::StrFormat("Object %02X", i);
|
||||
std::transform(filter_str.begin(), filter_str.end(),
|
||||
filter_str.begin(), ::tolower);
|
||||
std::transform(filter_str.begin(), filter_str.end(),
|
||||
filter_str.begin(), ::tolower);
|
||||
std::transform(object_name.begin(), object_name.end(),
|
||||
object_name.begin(), ::tolower);
|
||||
object_name.begin(), ::tolower);
|
||||
if (object_name.find(filter_str) == std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Create preview icon with small canvas
|
||||
ImGui::BeginGroup();
|
||||
|
||||
|
||||
// Small preview canvas (32x32 pixels)
|
||||
DrawObjectPreviewIcon(i, ImVec2(32, 32));
|
||||
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
|
||||
// Object label and selection
|
||||
std::string object_label = absl::StrFormat("%02X - Floor Object", i);
|
||||
|
||||
if (ImGui::Selectable(object_label.c_str(),
|
||||
has_preview_object_ && preview_object_.id_ == i,
|
||||
0, ImVec2(0, 32))) { // Match preview height
|
||||
preview_object_ = zelda3::RoomObject{
|
||||
static_cast<int16_t>(i), 0, 0, 0, 0};
|
||||
|
||||
if (ImGui::Selectable(object_label.c_str(),
|
||||
has_preview_object_ && preview_object_.id_ == i,
|
||||
0, ImVec2(0, 32))) { // Match preview height
|
||||
preview_object_ =
|
||||
zelda3::RoomObject{static_cast<int16_t>(i), 0, 0, 0, 0};
|
||||
has_preview_object_ = true;
|
||||
canvas_viewer_->SetPreviewObject(preview_object_);
|
||||
canvas_viewer_->SetObjectInteractionEnabled(true);
|
||||
interaction_mode_ = InteractionMode::Place;
|
||||
}
|
||||
|
||||
|
||||
ImGui::EndGroup();
|
||||
|
||||
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::Text("Object ID: 0x%02X", i);
|
||||
@@ -145,17 +153,17 @@ void ObjectEditorCard::DrawObjectSelector() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Wall objects
|
||||
if (ImGui::CollapsingHeader(ICON_MD_BORDER_ALL " Wall Objects")) {
|
||||
for (int i = 0; i < 0x50; i++) {
|
||||
std::string object_label = absl::StrFormat(
|
||||
"%s %02X - Wall Object", ICON_MD_BORDER_VERTICAL, i);
|
||||
|
||||
std::string object_label = absl::StrFormat("%s %02X - Wall Object",
|
||||
ICON_MD_BORDER_VERTICAL, i);
|
||||
|
||||
if (ImGui::Selectable(object_label.c_str())) {
|
||||
// Wall objects have special handling
|
||||
preview_object_ = zelda3::RoomObject{
|
||||
static_cast<int16_t>(i), 0, 0, 0, 1}; // layer=1 for walls
|
||||
preview_object_ = zelda3::RoomObject{static_cast<int16_t>(i), 0, 0, 0,
|
||||
1}; // layer=1 for walls
|
||||
has_preview_object_ = true;
|
||||
canvas_viewer_->SetPreviewObject(preview_object_);
|
||||
canvas_viewer_->SetObjectInteractionEnabled(true);
|
||||
@@ -163,22 +171,21 @@ void ObjectEditorCard::DrawObjectSelector() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Special objects
|
||||
if (ImGui::CollapsingHeader(ICON_MD_STAR " Special Objects")) {
|
||||
const char* special_objects[] = {
|
||||
"Stairs Down", "Stairs Up", "Chest", "Door", "Pot", "Block",
|
||||
"Switch", "Torch"
|
||||
};
|
||||
|
||||
const char* special_objects[] = {"Stairs Down", "Stairs Up", "Chest",
|
||||
"Door", "Pot", "Block",
|
||||
"Switch", "Torch"};
|
||||
|
||||
for (int i = 0; i < IM_ARRAYSIZE(special_objects); i++) {
|
||||
std::string object_label = absl::StrFormat(
|
||||
"%s %s", ICON_MD_STAR, special_objects[i]);
|
||||
|
||||
std::string object_label =
|
||||
absl::StrFormat("%s %s", ICON_MD_STAR, special_objects[i]);
|
||||
|
||||
if (ImGui::Selectable(object_label.c_str())) {
|
||||
// Special object IDs start at 0xF8
|
||||
preview_object_ = zelda3::RoomObject{
|
||||
static_cast<int16_t>(0xF8 + i), 0, 0, 0, 2};
|
||||
preview_object_ =
|
||||
zelda3::RoomObject{static_cast<int16_t>(0xF8 + i), 0, 0, 0, 2};
|
||||
has_preview_object_ = true;
|
||||
canvas_viewer_->SetPreviewObject(preview_object_);
|
||||
canvas_viewer_->SetObjectInteractionEnabled(true);
|
||||
@@ -186,10 +193,10 @@ void ObjectEditorCard::DrawObjectSelector() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
|
||||
// Quick actions at bottom
|
||||
if (ImGui::Button(ICON_MD_CLEAR " Clear Selection", ImVec2(-1, 0))) {
|
||||
has_preview_object_ = false;
|
||||
@@ -200,110 +207,115 @@ void ObjectEditorCard::DrawObjectSelector() {
|
||||
}
|
||||
|
||||
void ObjectEditorCard::DrawEmulatorPreview() {
|
||||
ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f),
|
||||
ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f),
|
||||
ICON_MD_INFO " Real-time object rendering preview");
|
||||
ImGui::Separator();
|
||||
|
||||
|
||||
// Toggle emulator preview visibility
|
||||
ImGui::Checkbox("Enable Preview", &show_emulator_preview_);
|
||||
ImGui::SameLine();
|
||||
gui::HelpMarker("Uses SNES emulation to render objects accurately.\n"
|
||||
"May impact performance.");
|
||||
|
||||
gui::HelpMarker(
|
||||
"Uses SNES emulation to render objects accurately.\n"
|
||||
"May impact performance.");
|
||||
|
||||
if (show_emulator_preview_) {
|
||||
ImGui::Separator();
|
||||
|
||||
|
||||
// Embed the emulator preview with improved layout
|
||||
ImGui::BeginChild("##EmulatorPreviewRegion", ImVec2(0, 0), true);
|
||||
|
||||
|
||||
emulator_preview_.Render();
|
||||
|
||||
|
||||
ImGui::EndChild();
|
||||
} else {
|
||||
ImGui::Separator();
|
||||
ImGui::TextDisabled(ICON_MD_PREVIEW " Preview disabled for performance");
|
||||
ImGui::TextWrapped("Enable to see accurate object rendering using "
|
||||
"SNES emulation.");
|
||||
ImGui::TextWrapped(
|
||||
"Enable to see accurate object rendering using "
|
||||
"SNES emulation.");
|
||||
}
|
||||
}
|
||||
|
||||
// DrawInteractionControls removed - controls moved to top of card
|
||||
|
||||
void ObjectEditorCard::DrawObjectPreviewIcon(int object_id, const ImVec2& size) {
|
||||
void ObjectEditorCard::DrawObjectPreviewIcon(int object_id,
|
||||
const ImVec2& size) {
|
||||
// Create a small preview box for the object
|
||||
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
||||
ImVec2 cursor_pos = ImGui::GetCursorScreenPos();
|
||||
ImVec2 box_min = cursor_pos;
|
||||
ImVec2 box_max = ImVec2(cursor_pos.x + size.x, cursor_pos.y + size.y);
|
||||
|
||||
|
||||
// Draw background
|
||||
draw_list->AddRectFilled(box_min, box_max, IM_COL32(40, 40, 45, 255));
|
||||
draw_list->AddRect(box_min, box_max, IM_COL32(100, 100, 100, 255));
|
||||
|
||||
|
||||
// Draw a simple representation based on object ID
|
||||
// For now, use colored squares and icons as placeholders
|
||||
// Later this can be replaced with actual object bitmaps
|
||||
|
||||
|
||||
// Color based on object ID for visual variety
|
||||
float hue = (object_id % 16) / 16.0f;
|
||||
ImU32 obj_color = ImGui::ColorConvertFloat4ToU32(
|
||||
ImVec4(0.5f + hue * 0.3f, 0.4f, 0.6f - hue * 0.2f, 1.0f));
|
||||
|
||||
|
||||
// Draw inner colored square (16x16 in the center)
|
||||
ImVec2 inner_min = ImVec2(cursor_pos.x + 8, cursor_pos.y + 8);
|
||||
ImVec2 inner_max = ImVec2(cursor_pos.x + 24, cursor_pos.y + 24);
|
||||
draw_list->AddRectFilled(inner_min, inner_max, obj_color);
|
||||
draw_list->AddRect(inner_min, inner_max, IM_COL32(200, 200, 200, 255));
|
||||
|
||||
|
||||
// Draw object ID text (very small)
|
||||
std::string id_text = absl::StrFormat("%02X", object_id);
|
||||
ImVec2 text_size = ImGui::CalcTextSize(id_text.c_str());
|
||||
ImVec2 text_pos = ImVec2(
|
||||
cursor_pos.x + (size.x - text_size.x) * 0.5f,
|
||||
cursor_pos.y + size.y - text_size.y - 2);
|
||||
ImVec2 text_pos = ImVec2(cursor_pos.x + (size.x - text_size.x) * 0.5f,
|
||||
cursor_pos.y + size.y - text_size.y - 2);
|
||||
draw_list->AddText(text_pos, IM_COL32(180, 180, 180, 255), id_text.c_str());
|
||||
|
||||
|
||||
// Advance cursor
|
||||
ImGui::Dummy(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:");
|
||||
|
||||
ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f), ICON_MD_INFO " Current:");
|
||||
|
||||
if (has_preview_object_) {
|
||||
ImGui::SameLine();
|
||||
ImGui::Text("ID: 0x%02X", preview_object_.id_);
|
||||
ImGui::SameLine();
|
||||
ImGui::Text("Layer: %s",
|
||||
preview_object_.layer_ == zelda3::RoomObject::BG1 ? "BG1" :
|
||||
preview_object_.layer_ == zelda3::RoomObject::BG2 ? "BG2" : "BG3");
|
||||
ImGui::Text("Layer: %s",
|
||||
preview_object_.layer_ == zelda3::RoomObject::BG1 ? "BG1"
|
||||
: preview_object_.layer_ == zelda3::RoomObject::BG2 ? "BG2"
|
||||
: "BG3");
|
||||
} else {
|
||||
ImGui::SameLine();
|
||||
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),
|
||||
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");
|
||||
|
||||
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();
|
||||
@@ -311,7 +323,7 @@ void ObjectEditorCard::DrawSelectedObjectInfo() {
|
||||
interaction.ClearSelection();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ImGui::EndGroup();
|
||||
}
|
||||
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
#include <unordered_map>
|
||||
|
||||
#include "app/editor/dungeon/dungeon_canvas_viewer.h"
|
||||
#include "app/gfx/backend/irenderer.h"
|
||||
#include "app/gui/canvas/canvas.h"
|
||||
#include "app/editor/dungeon/dungeon_object_selector.h"
|
||||
#include "app/gfx/backend/irenderer.h"
|
||||
#include "app/gui/app/editor_layout.h"
|
||||
#include "app/gui/canvas/canvas.h"
|
||||
#include "app/gui/widgets/dungeon_object_emulator_preview.h"
|
||||
#include "app/rom.h"
|
||||
#include "zelda3/dungeon/room_object.h"
|
||||
@@ -28,51 +28,49 @@ namespace editor {
|
||||
*/
|
||||
class ObjectEditorCard {
|
||||
public:
|
||||
ObjectEditorCard(gfx::IRenderer* renderer, Rom* rom, DungeonCanvasViewer* canvas_viewer);
|
||||
|
||||
ObjectEditorCard(gfx::IRenderer* renderer, Rom* rom,
|
||||
DungeonCanvasViewer* canvas_viewer);
|
||||
|
||||
// Main update function
|
||||
void Draw(bool* p_open);
|
||||
|
||||
|
||||
// Access to components
|
||||
DungeonObjectSelector& object_selector() { return object_selector_; }
|
||||
gui::DungeonObjectEmulatorPreview& emulator_preview() { return emulator_preview_; }
|
||||
|
||||
gui::DungeonObjectEmulatorPreview& emulator_preview() {
|
||||
return emulator_preview_;
|
||||
}
|
||||
|
||||
// Update current room context
|
||||
void SetCurrentRoom(int room_id) { current_room_id_ = room_id; }
|
||||
|
||||
|
||||
private:
|
||||
void DrawObjectSelector();
|
||||
void DrawEmulatorPreview();
|
||||
void DrawInteractionControls();
|
||||
void DrawSelectedObjectInfo();
|
||||
void DrawObjectPreviewIcon(int object_id, const ImVec2& size);
|
||||
|
||||
|
||||
Rom* rom_;
|
||||
DungeonCanvasViewer* canvas_viewer_;
|
||||
int current_room_id_ = 0;
|
||||
|
||||
|
||||
// Components
|
||||
DungeonObjectSelector object_selector_;
|
||||
gui::DungeonObjectEmulatorPreview emulator_preview_;
|
||||
|
||||
|
||||
// Object preview canvases (one per object type)
|
||||
std::unordered_map<int, gui::Canvas> object_preview_canvases_;
|
||||
|
||||
|
||||
// UI state
|
||||
int selected_tab_ = 0;
|
||||
bool show_emulator_preview_ = false; // Disabled by default for performance
|
||||
bool show_object_list_ = true;
|
||||
bool show_interaction_controls_ = true;
|
||||
|
||||
|
||||
// Object interaction mode
|
||||
enum class InteractionMode {
|
||||
None,
|
||||
Place,
|
||||
Select,
|
||||
Delete
|
||||
};
|
||||
enum class InteractionMode { None, Place, Select, Delete };
|
||||
InteractionMode interaction_mode_ = InteractionMode::None;
|
||||
|
||||
|
||||
// Selected object for placement
|
||||
zelda3::RoomObject preview_object_{0, 0, 0, 0, 0};
|
||||
bool has_preview_object_ = false;
|
||||
|
||||
Reference in New Issue
Block a user