feat: Implement Jump-to Functionality for Cross-Editor Navigation

- Added JumpToDungeonRoom and JumpToOverworldMap methods to facilitate quick navigation between dungeon and overworld editors.
- Introduced SwitchToEditor method to manage editor tab activation based on the selected editor type.
- Enhanced DungeonEditorV2 with room and entrance selection capabilities, improving user experience and workflow efficiency.
- Updated header files to declare new methods and ensure proper integration within the editor management system.
This commit is contained in:
scawful
2025-10-07 12:31:26 -04:00
parent 33f64f38a5
commit 5e4e6f5d45
10 changed files with 365 additions and 32 deletions

View File

@@ -1,5 +1,6 @@
#include "dungeon_editor_v2.h"
#include <algorithm>
#include <cstdio>
#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<gui::EditorCard>(
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<int>(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<int>(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<int>(entrances_.size()); i++) {
int room_id = entrances_[i].room_;
std::string room_name = "Unknown";
if (room_id >= 0 && room_id < static_cast<int>(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<int>(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

View File

@@ -1,6 +1,9 @@
#ifndef YAZE_APP_EDITOR_DUNGEON_EDITOR_V2_H
#define YAZE_APP_EDITOR_DUNGEON_EDITOR_V2_H
#include <memory>
#include <unordered_map>
#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<zelda3::Room, 0x128> rooms_;
std::array<zelda3::RoomEntrance, 0x8C> entrances_;
// Active room tabs
// 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;
// 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_;