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:
scawful
2025-11-20 01:35:33 -05:00
parent c2bb90a3f1
commit fa3da8fc27
600 changed files with 32605 additions and 27962 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -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

View File

@@ -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_();

View File

@@ -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;

View File

@@ -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();

View File

@@ -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;
};

View File

@@ -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();
}

View File

@@ -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_;

View File

@@ -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", &current_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;

View File

@@ -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_;
};

View File

@@ -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

View File

@@ -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_;

View File

@@ -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");
}

View File

@@ -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();

View File

@@ -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();
}

View File

@@ -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;