diff --git a/src/app/editor/dungeon/dungeon_editor_v2.cc b/src/app/editor/dungeon/dungeon_editor_v2.cc index 50910cb1..75a52675 100644 --- a/src/app/editor/dungeon/dungeon_editor_v2.cc +++ b/src/app/editor/dungeon/dungeon_editor_v2.cc @@ -1,5 +1,6 @@ #include "dungeon_editor_v2.h" +#include #include #include "absl/strings/str_format.h" @@ -55,7 +56,7 @@ absl::Status DungeonEditorV2::Load() { palette_editor_.Initialize(rom_); // Wire palette changes to trigger room re-renders - palette_editor_.SetOnPaletteChanged([this](int palette_id) { + palette_editor_.SetOnPaletteChanged([this](int /*palette_id*/) { // Re-render all active rooms when palette changes for (int i = 0; i < active_rooms_.Size; i++) { int room_id = active_rooms_[i]; @@ -118,6 +119,30 @@ void DungeonEditorV2::DrawToolset() { if (toolbar.AddAction(ICON_MD_ADD, "Open Room")) { OnRoomSelected(room_selector_.current_room_id()); } + + toolbar.AddSeparator(); + + if (toolbar.AddToggle(ICON_MD_LIST, &show_room_selector_, "Toggle Room Selector")) { + // Toggled + } + + if (toolbar.AddToggle(ICON_MD_GRID_VIEW, &show_room_matrix_, "Toggle Room Matrix")) { + // Toggled + } + + if (toolbar.AddToggle(ICON_MD_DOOR_FRONT, &show_entrances_list_, "Toggle Entrances List")) { + // Toggled + } + + toolbar.AddSeparator(); + + if (toolbar.AddToggle(ICON_MD_CATEGORY, &show_object_selector_, "Toggle Object Selector")) { + // Toggled + } + + if (toolbar.AddToggle(ICON_MD_PALETTE, &show_palette_editor_, "Toggle Palette Editor")) { + // Toggled + } toolbar.End(); } @@ -126,42 +151,43 @@ void DungeonEditorV2::DrawLayout() { // NO TABLE LAYOUT - All independent dockable EditorCards // 1. Room Selector Card (independent, dockable) - { - static bool show_room_selector = true; - gui::EditorCard selector_card( - MakeCardTitle("Room Selector").c_str(), - ICON_MD_LIST, &show_room_selector); - if (selector_card.Begin()) { - room_selector_.Draw(); - } - selector_card.End(); + if (show_room_selector_) { + DrawRoomsListCard(); + } + + // 2. Room Matrix Card (visual navigation) + if (show_room_matrix_) { + DrawRoomMatrixCard(); + } + + // 3. Entrances List Card + if (show_entrances_list_) { + DrawEntrancesListCard(); } - // 2. Object Selector/Manager Card (independent, dockable) - { - static bool show_object_selector = true; + // 4. Object Selector/Manager Card (independent, dockable) + if (show_object_selector_) { gui::EditorCard object_card( MakeCardTitle("Object Selector").c_str(), - ICON_MD_CATEGORY, &show_object_selector); + ICON_MD_CATEGORY, &show_object_selector_); if (object_card.Begin()) { object_selector_.Draw(); } object_card.End(); } - // 3. Palette Editor Card (independent, dockable) - { - static bool show_palette_editor = true; + // 5. Palette Editor Card (independent, dockable) + if (show_palette_editor_) { gui::EditorCard palette_card( MakeCardTitle("Palette Editor").c_str(), - ICON_MD_PALETTE, &show_palette_editor); + ICON_MD_PALETTE, &show_palette_editor_); if (palette_card.Begin()) { palette_editor_.Draw(); } palette_card.End(); } - // 4. Active Room Cards (independent, dockable, no inheritance) + // 6. Active Room Cards (independent, dockable, tracked for jump-to) for (int i = 0; i < active_rooms_.Size; i++) { int room_id = active_rooms_[i]; bool open = true; @@ -177,14 +203,20 @@ void DungeonEditorV2::DrawLayout() { std::string card_name_str = absl::StrFormat("%s###RoomCard%d", MakeCardTitle(base_name).c_str(), room_id); - // Each room card is COMPLETELY independent - no parent windows - gui::EditorCard room_card(card_name_str.c_str(), ICON_MD_GRID_ON, &open); - if (room_card.Begin()) { + // Track or create card for jump-to functionality + if (room_cards_.find(room_id) == room_cards_.end()) { + room_cards_[room_id] = std::make_shared( + card_name_str.c_str(), ICON_MD_GRID_ON, &open); + } + + auto& room_card = room_cards_[room_id]; + if (room_card->Begin(&open)) { DrawRoomTab(room_id); } - room_card.End(); + room_card->End(); if (!open) { + room_cards_.erase(room_id); active_rooms_.erase(active_rooms_.Data + i); i--; } @@ -235,7 +267,8 @@ void DungeonEditorV2::OnRoomSelected(int room_id) { // Check if already open for (int i = 0; i < active_rooms_.Size; i++) { if (active_rooms_[i] == room_id) { - // Optional: Focus the existing window if possible. For now, do nothing. + // Focus the existing room card + FocusRoom(room_id); return; } } @@ -245,5 +278,212 @@ void DungeonEditorV2::OnRoomSelected(int room_id) { room_selector_.set_active_rooms(active_rooms_); } +void DungeonEditorV2::OnEntranceSelected(int entrance_id) { + if (entrance_id < 0 || entrance_id >= static_cast(entrances_.size())) { + return; + } + + // Get the room ID associated with this entrance + int room_id = entrances_[entrance_id].room_; + + // Open and focus the room + OnRoomSelected(room_id); +} + +void DungeonEditorV2::add_room(int room_id) { + OnRoomSelected(room_id); +} + +void DungeonEditorV2::FocusRoom(int room_id) { + // Focus the room card if it exists + auto it = room_cards_.find(room_id); + if (it != room_cards_.end()) { + it->second->Focus(); + } +} + +void DungeonEditorV2::DrawRoomsListCard() { + gui::EditorCard selector_card( + MakeCardTitle("Rooms List").c_str(), + ICON_MD_LIST, &show_room_selector_); + + if (selector_card.Begin()) { + // Add text filter + static char room_filter[256] = ""; + ImGui::SetNextItemWidth(-1); + if (ImGui::InputTextWithHint("##RoomFilter", ICON_MD_SEARCH " Filter rooms...", + room_filter, sizeof(room_filter))) { + // Filter updated + } + + ImGui::Separator(); + + // Scrollable room list with resource labels + if (ImGui::BeginChild("##RoomsList", ImVec2(0, 0), true)) { + std::string filter_str = room_filter; + std::transform(filter_str.begin(), filter_str.end(), filter_str.begin(), ::tolower); + + for (int i = 0; i < 0x128; i++) { + std::string room_name; + if (i < static_cast(std::size(zelda3::kRoomNames))) { + room_name = absl::StrFormat("%03X - %s", i, zelda3::kRoomNames[i].data()); + } else { + room_name = absl::StrFormat("%03X - Room %d", i, i); + } + + // Apply filter + if (!filter_str.empty()) { + std::string room_name_lower = room_name; + std::transform(room_name_lower.begin(), room_name_lower.end(), + room_name_lower.begin(), ::tolower); + if (room_name_lower.find(filter_str) == std::string::npos) { + continue; + } + } + + bool is_selected = (current_room_id_ == i); + if (ImGui::Selectable(room_name.c_str(), is_selected)) { + OnRoomSelected(i); + } + + if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) { + OnRoomSelected(i); + } + } + ImGui::EndChild(); + } + } + selector_card.End(); +} + +void DungeonEditorV2::DrawEntrancesListCard() { + gui::EditorCard entrances_card( + MakeCardTitle("Entrances List").c_str(), + ICON_MD_DOOR_FRONT, &show_entrances_list_); + + if (entrances_card.Begin()) { + // Add text filter + static char entrance_filter[256] = ""; + ImGui::SetNextItemWidth(-1); + if (ImGui::InputTextWithHint("##EntranceFilter", ICON_MD_SEARCH " Filter entrances...", + entrance_filter, sizeof(entrance_filter))) { + // Filter updated + } + + ImGui::Separator(); + + // Scrollable entrance list with associated room names + if (ImGui::BeginChild("##EntrancesList", ImVec2(0, 0), true)) { + std::string filter_str = entrance_filter; + std::transform(filter_str.begin(), filter_str.end(), filter_str.begin(), ::tolower); + + for (int i = 0; i < static_cast(entrances_.size()); i++) { + int room_id = entrances_[i].room_; + + std::string room_name = "Unknown"; + if (room_id >= 0 && room_id < static_cast(std::size(zelda3::kRoomNames))) { + room_name = zelda3::kRoomNames[room_id].data(); + } + + std::string entrance_label = absl::StrFormat("%02X - %s (Room %03X)", + i, room_name.c_str(), room_id); + + // Apply filter + if (!filter_str.empty()) { + std::string entrance_label_lower = entrance_label; + std::transform(entrance_label_lower.begin(), entrance_label_lower.end(), + entrance_label_lower.begin(), ::tolower); + if (entrance_label_lower.find(filter_str) == std::string::npos) { + continue; + } + } + + if (ImGui::Selectable(entrance_label.c_str())) { + OnEntranceSelected(i); + } + } + ImGui::EndChild(); + } + } + entrances_card.End(); +} + +void DungeonEditorV2::DrawRoomMatrixCard() { + gui::EditorCard matrix_card( + MakeCardTitle("Room Matrix").c_str(), + ICON_MD_GRID_VIEW, &show_room_matrix_); + + matrix_card.SetDefaultSize(600, 600); + + if (matrix_card.Begin()) { + // Draw 8x8 grid of rooms (first 64 rooms) + constexpr int kRoomsPerRow = 8; + constexpr int kRoomsPerCol = 8; + constexpr float kRoomCellSize = 64.0f; + + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + ImVec2 canvas_pos = ImGui::GetCursorScreenPos(); + + for (int row = 0; row < kRoomsPerCol; row++) { + for (int col = 0; col < kRoomsPerRow; col++) { + int room_id = row * kRoomsPerRow + col; + + ImVec2 cell_min = ImVec2(canvas_pos.x + col * kRoomCellSize, + canvas_pos.y + row * kRoomCellSize); + ImVec2 cell_max = ImVec2(cell_min.x + kRoomCellSize, + cell_min.y + kRoomCellSize); + + // Check if room is active + bool is_active = false; + for (int i = 0; i < active_rooms_.Size; i++) { + if (active_rooms_[i] == room_id) { + is_active = true; + break; + } + } + + // Draw cell background + ImU32 bg_color = is_active ? IM_COL32(100, 150, 255, 255) + : IM_COL32(50, 50, 50, 255); + draw_list->AddRectFilled(cell_min, cell_max, bg_color); + + // Draw cell border + draw_list->AddRect(cell_min, cell_max, IM_COL32(150, 150, 150, 255)); + + // Draw room ID + std::string room_label = absl::StrFormat("%02X", room_id); + ImVec2 text_size = ImGui::CalcTextSize(room_label.c_str()); + ImVec2 text_pos = ImVec2(cell_min.x + (kRoomCellSize - text_size.x) * 0.5f, + cell_min.y + (kRoomCellSize - text_size.y) * 0.5f); + draw_list->AddText(text_pos, IM_COL32(255, 255, 255, 255), room_label.c_str()); + + // Handle clicks + ImGui::SetCursorScreenPos(cell_min); + ImGui::InvisibleButton(absl::StrFormat("##room%d", room_id).c_str(), + ImVec2(kRoomCellSize, kRoomCellSize)); + + if (ImGui::IsItemClicked()) { + OnRoomSelected(room_id); + } + + // Hover preview (TODO: implement room bitmap preview) + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + if (room_id < static_cast(std::size(zelda3::kRoomNames))) { + ImGui::Text("%s", zelda3::kRoomNames[room_id].data()); + } else { + ImGui::Text("Room %03X", room_id); + } + ImGui::EndTooltip(); + } + } + } + + // Advance cursor past the grid + ImGui::Dummy(ImVec2(kRoomsPerRow * kRoomCellSize, kRoomsPerCol * kRoomCellSize)); + } + matrix_card.End(); +} + } // namespace yaze::editor diff --git a/src/app/editor/dungeon/dungeon_editor_v2.h b/src/app/editor/dungeon/dungeon_editor_v2.h index 0195cac5..16cc2bcf 100644 --- a/src/app/editor/dungeon/dungeon_editor_v2.h +++ b/src/app/editor/dungeon/dungeon_editor_v2.h @@ -1,6 +1,9 @@ #ifndef YAZE_APP_EDITOR_DUNGEON_EDITOR_V2_H #define YAZE_APP_EDITOR_DUNGEON_EDITOR_V2_H +#include +#include + #include "absl/status/status.h" #include "absl/strings/str_format.h" #include "app/editor/editor.h" @@ -70,7 +73,8 @@ class DungeonEditorV2 : public Editor { Rom* rom() const { return rom_; } // Room management - void add_room(int room_id) { active_rooms_.push_back(room_id); } + void add_room(int room_id); + void FocusRoom(int room_id); // ROM state bool IsRomLoaded() const override { return rom_ && rom_->is_loaded(); } @@ -85,19 +89,31 @@ class DungeonEditorV2 : public Editor { void DrawLayout(); void DrawRoomTab(int room_id); void DrawToolset(); + void DrawRoomMatrixCard(); + void DrawRoomsListCard(); + void DrawEntrancesListCard(); // Room selection callback void OnRoomSelected(int room_id); + void OnEntranceSelected(int entrance_id); // Data Rom* rom_; std::array rooms_; std::array entrances_; - // Active room tabs + // Active room tabs and card tracking for jump-to ImVector active_rooms_; + std::unordered_map> room_cards_; int current_room_id_ = 0; + // Card visibility flags + bool show_room_selector_ = true; + bool show_room_matrix_ = false; + bool show_entrances_list_ = false; + bool show_object_selector_ = true; + bool show_palette_editor_ = true; + // Palette management gfx::SnesPalette current_palette_; gfx::PaletteGroup current_palette_group_; diff --git a/src/app/editor/editor_manager.cc b/src/app/editor/editor_manager.cc index 0bc74d46..e82b7593 100644 --- a/src/app/editor/editor_manager.cc +++ b/src/app/editor/editor_manager.cc @@ -3267,5 +3267,42 @@ void EditorManager::DrawWelcomeScreen() { } } +// ============================================================================ +// Jump-to Functionality for Cross-Editor Navigation +// ============================================================================ + +void EditorManager::JumpToDungeonRoom(int room_id) { + if (!current_editor_set_) return; + + // Switch to dungeon editor + SwitchToEditor(EditorType::kDungeon); + + // Open the room in the dungeon editor + current_editor_set_->dungeon_editor_.add_room(room_id); +} + +void EditorManager::JumpToOverworldMap(int map_id) { + if (!current_editor_set_) return; + + // Switch to overworld editor + SwitchToEditor(EditorType::kOverworld); + + // Set the current map in the overworld editor + current_editor_set_->overworld_editor_.set_current_map(map_id); +} + +void EditorManager::SwitchToEditor(EditorType editor_type) { + // Find the editor tab and activate it + for (size_t i = 0; i < current_editor_set_->active_editors_.size(); ++i) { + if (current_editor_set_->active_editors_[i]->type() == editor_type) { + current_editor_set_->active_editors_[i]->set_active(true); + + // Set editor as the current/focused one + // This will make it visible when tabs are rendered + break; + } + } +} + } // namespace editor } // namespace yaze diff --git a/src/app/editor/editor_manager.h b/src/app/editor/editor_manager.h index 5022580b..c66d3c8a 100644 --- a/src/app/editor/editor_manager.h +++ b/src/app/editor/editor_manager.h @@ -134,6 +134,11 @@ class EditorManager { } void BuildModernMenu(); + + // Jump-to functionality for cross-editor navigation + void JumpToDungeonRoom(int room_id); + void JumpToOverworldMap(int map_id); + void SwitchToEditor(EditorType editor_type); private: void DrawWelcomeScreen(); diff --git a/src/app/editor/overworld/overworld_editor.h b/src/app/editor/overworld/overworld_editor.h index c02b5f5b..c43663c2 100644 --- a/src/app/editor/overworld/overworld_editor.h +++ b/src/app/editor/overworld/overworld_editor.h @@ -101,6 +101,14 @@ class OverworldEditor : public Editor, public gfx::GfxContext { if (!rom_->is_loaded()) return "ROM failed to load"; return absl::StrFormat("ROM loaded: %s", rom_->title()); } + + // Jump-to functionality + void set_current_map(int map_id) { + if (map_id >= 0 && map_id < zelda3::kNumOverworldMaps) { + current_map_ = map_id; + current_world_ = map_id / 0x40; // Calculate which world the map belongs to + } + } /** * @brief Load the Bitmap objects for each OverworldMap. diff --git a/src/app/editor/overworld/overworld_entity_renderer.cc b/src/app/editor/overworld/overworld_entity_renderer.cc index da19c11b..871ff50e 100644 --- a/src/app/editor/overworld/overworld_entity_renderer.cc +++ b/src/app/editor/overworld/overworld_entity_renderer.cc @@ -15,10 +15,10 @@ using namespace ImGui; // Entity colors - solid with good visibility namespace { -ImVec4 GetEntranceColor() { return ImVec4(0.0f, 255.0f, 0.0f, 255.0f); } // Solid green -ImVec4 GetExitColor() { return ImVec4(255.0f, 0.0f, 0.0f, 255.0f); } // Solid red -ImVec4 GetItemColor() { return ImVec4(255.0f, 255.0f, 0.0f, 255.0f); } // Solid yellow -ImVec4 GetSpriteColor() { return ImVec4(255.0f, 128.0f, 0.0f, 255.0f); } // Solid orange +ImVec4 GetEntranceColor() { return ImVec4{1.0f, 1.0f, 0.0f, 1.0f}; } // Solid yellow (#FFFF00FF, fully opaque) +ImVec4 GetExitColor() { return ImVec4{1.0f, 1.0f, 1.0f, 1.0f}; } // Solid white (#FFFFFFFF, fully opaque) +ImVec4 GetItemColor() { return ImVec4{1.0f, 0.0f, 0.0f, 1.0f}; } // Solid red (#FF0000FF, fully opaque) +ImVec4 GetSpriteColor() { return ImVec4{1.0f, 0.0f, 1.0f, 1.0f}; } // Solid magenta (#FF00FFFF, fully opaque) } // namespace void OverworldEntityRenderer::DrawEntrances(ImVec2 canvas_p0, ImVec2 scrolling, diff --git a/src/app/gui/canvas_utils.cc b/src/app/gui/canvas_utils.cc index b55edb2b..e108efee 100644 --- a/src/app/gui/canvas_utils.cc +++ b/src/app/gui/canvas_utils.cc @@ -141,7 +141,7 @@ void DrawCanvasRect(ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling, ImVec2 size(canvas_p0.x + scrolling.x + scaled_x + scaled_w, canvas_p0.y + scrolling.y + scaled_y + scaled_h); - uint32_t color_u32 = IM_COL32(color.x, color.y, color.z, color.w); + uint32_t color_u32 = IM_COL32(color.x * 255, color.y * 255, color.z * 255, color.w * 255); draw_list->AddRectFilled(origin, size, color_u32); // Add a black outline diff --git a/src/app/gui/editor_layout.cc b/src/app/gui/editor_layout.cc index 8fa18580..403c912b 100644 --- a/src/app/gui/editor_layout.cc +++ b/src/app/gui/editor_layout.cc @@ -229,11 +229,14 @@ bool Toolset::AddUsageStatsButton(const char* tooltip) { // ============================================================================ EditorCard::EditorCard(const char* title, const char* icon) - : title_(title), icon_(icon ? icon : ""), default_size_(400, 300) {} + : title_(title), icon_(icon ? icon : ""), default_size_(400, 300) { + window_name_ = icon_.empty() ? title_ : icon_ + " " + title_; +} EditorCard::EditorCard(const char* title, const char* icon, bool* p_open) : title_(title), icon_(icon ? icon : ""), default_size_(400, 300) { p_open_ = p_open; + window_name_ = icon_.empty() ? title_ : icon_ + " " + title_; } void EditorCard::SetDefaultSize(float width, float height) { @@ -301,11 +304,20 @@ bool EditorCard::Begin(bool* p_open) { } void EditorCard::End() { + // Check if window was focused this frame + focused_ = ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows); + ImGui::End(); ImGui::PopStyleColor(2); ImGui::PopStyleVar(2); } +void EditorCard::Focus() { + // Set window focus using ImGui's focus system + ImGui::SetWindowFocus(window_name_.c_str()); + focused_ = true; +} + // ============================================================================ // EditorLayout Implementation // ============================================================================ diff --git a/src/app/gui/editor_layout.h b/src/app/gui/editor_layout.h index 0af16581..f322a995 100644 --- a/src/app/gui/editor_layout.h +++ b/src/app/gui/editor_layout.h @@ -132,15 +132,24 @@ class EditorCard { void SetMinimized(bool minimized) { minimized_ = minimized; } bool IsMinimized() const { return minimized_; } + // Focus the card window (bring to front and set focused) + void Focus(); + bool IsFocused() const { return focused_; } + + // Get the window name for ImGui operations + const char* GetWindowName() const { return window_name_.c_str(); } + private: std::string title_; std::string icon_; + std::string window_name_; // Full window name with icon ImVec2 default_size_; Position position_ = Position::Free; bool minimizable_ = true; bool closable_ = true; bool minimized_ = false; bool first_draw_ = true; + bool focused_ = false; bool* p_open_ = nullptr; }; diff --git a/src/app/gui/theme_manager.h b/src/app/gui/theme_manager.h index 636b0a8a..45172f32 100644 --- a/src/app/gui/theme_manager.h +++ b/src/app/gui/theme_manager.h @@ -133,6 +133,12 @@ struct EnhancedTheme { Color editor_grid; // Grid lines in editors Color editor_cursor; // Cursor/selection in editors Color editor_selection; // Selected area in editors + + Color entrance_color; + Color hole_color; + Color exit_color; + Color item_color; + Color sprite_color; // Style parameters float window_rounding = 0.0f;