1#ifndef YAZE_APP_EDITOR_DUNGEON_PANELS_DUNGEON_ENTRANCES_PANEL_H_
2#define YAZE_APP_EDITOR_DUNGEON_PANELS_DUNGEON_ENTRANCES_PANEL_H_
13#include "absl/status/status.h"
14#include "absl/strings/str_format.h"
23#include "imgui/imgui.h"
44 std::array<zelda3::RoomEntrance, zelda3::kNumDungeonEntranceSlots>*
46 std::array<zelda3::DungeonSpawnPoint, zelda3::kNumDungeonSpawnPoints>*
48 int* current_entrance_id, std::function<
void(
int)> on_entrance_selected)
58 std::string
GetId()
const override {
return "dungeon.entrance_properties"; }
66 std::function<std::optional<DungeonEntranceCameraState>(
int)>;
85 std::function<
void()> data_changed_callback = {}) {
98 std::function<
bool()> automatic,
99 std::function<
void(
int)> select) {
117 ? (*spawn_points_)[selected].main_gfx
118 : (*entrances_)[selected].blockset_;
119 std::string preview = absl::StrFormat(
124 const std::string name =
125 automatic && !context.owner_name.empty() ? context.owner_name
130 context.uses_entrance()
132 "%s: %s / %s", automatic ?
"Auto" :
"Manual", name,
135 "Auto: %s / GFX %02X",
139 context.effective_blockset());
141 ImGui::SetNextItemWidth(width);
142 const bool open = ImGui::BeginCombo(
143 "##GraphicsEntrance", preview.c_str(),
146 if (ImGui::IsItemHovered())
148 "Auto follows the room's dungeon. Choosing an entrance pins its "
149 "graphics until you choose Auto again. This changes only the "
153 if (ImGui::Selectable(
"Auto (follow room)", automatic))
158 ImGui::BeginChild(
"##EntranceChoices",
159 ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 8));
161 for (
int slot = 0; slot < static_cast<int>(
entrances_->size()); ++slot) {
166 if (ImGui::Selectable(label.c_str(), !automatic && slot == selected)) {
169 ImGui::CloseCurrentPopup();
179 if (slot == selected) {
180 ImGui::SetItemDefaultFocus();
182 ImGui::SetScrollHereY(0.5f);
194 ImGui::TextWrapped(
"Choose an entrance to edit its properties.");
207 void Draw(
bool* p_open)
override {
217 ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows);
219 const bool split_layout = ImGui::GetContentRegionAvail().x >= 620.0f;
221 ImGui::BeginTable(
"##EntranceNavigator", 2,
222 ImGuiTableFlags_Resizable |
223 ImGuiTableFlags_BordersInnerV |
224 ImGuiTableFlags_SizingStretchProp)) {
225 ImGui::TableSetupColumn(
"Entrances", ImGuiTableColumnFlags_WidthFixed,
227 ImGui::TableSetupColumn(
"Properties", ImGuiTableColumnFlags_WidthStretch);
228 ImGui::TableNextRow();
229 ImGui::TableNextColumn();
231 ImGui::TableNextColumn();
237 if (ImGui::BeginTabBar(
"##EntranceNavigatorTabs")) {
252 constexpr char hexadecimal[] =
"0123456789ABCDEF";
253 float glyph_width = 0.0f;
254 for (
int index = 0; index < 16; ++index) {
255 const char glyph[] = {hexadecimal[index],
'\0'};
256 glyph_width = std::max(glyph_width, ImGui::CalcTextSize(glyph).x);
258 return glyph_width * digits + ImGui::GetStyle().FramePadding.x * 2.0f +
264 ImGui::GetCursorScreenPos().x + ImGui::GetContentRegionAvail().x;
265 const float spacing = ImGui::GetStyle().ItemSpacing.x;
266 const float next_width =
268 if (ImGui::GetItemRectMax().x + spacing + next_width <= right)
272 template <
typename T>
274 bool continue_row =
false) {
275 constexpr int digits =
sizeof(T) == 1 ? 2 : 4;
281 const float label_width =
282 std::max(1.0f, ImGui::GetContentRegionAvail().x - width -
283 ImGui::GetStyle().ItemSpacing.x);
284 ImGui::PushTextWrapPos(ImGui::GetCursorPosX() + label_width);
285 bool changed =
false;
286 if constexpr (
sizeof(T) == 1)
290 ImGui::PopTextWrapPos();
308 const auto validation =
310 if (!ImGui::BeginPopupModal(
"Camera Repair Preview##DungeonEntrance",
311 nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
316 "Review the derived camera values before changing this entrance. "
317 "The repair is one undoable action.");
319 if (ImGui::BeginTable(
320 "##CameraRepairValues", 3,
321 ImGuiTableFlags_BordersInner | ImGuiTableFlags_SizingFixedFit)) {
322 ImGui::TableSetupColumn(
"Field");
323 ImGui::TableSetupColumn(
"Current");
324 ImGui::TableSetupColumn(
"Derived");
325 ImGui::TableHeadersRow();
326 const auto draw_word_row = [](
const char* label, uint16_t current,
328 ImGui::TableNextRow();
329 ImGui::TableNextColumn();
330 ImGui::TextUnformatted(label);
331 ImGui::TableNextColumn();
332 ImGui::Text(
"0x%04X", current);
333 ImGui::TableNextColumn();
334 ImGui::Text(
"0x%04X", derived);
337 validation.expected.camera_x);
339 validation.expected.camera_y);
341 validation.expected.trigger_x);
343 validation.expected.trigger_y);
344 ImGui::TableNextRow();
345 ImGui::TableNextColumn();
346 ImGui::TextUnformatted(
"Quadrant");
347 ImGui::TableNextColumn();
349 ImGui::TableNextColumn();
350 if (validation.quadrant_repair_safe) {
351 ImGui::Text(
"0x%02X", validation.expected.quadrant);
353 ImGui::TextUnformatted(
"Preserved");
358 "Boundary pages: [%02X %02X %02X %02X | %02X %02X %02X %02X] "
359 "-> [%02X %02X %02X %02X | %02X %02X %02X %02X]",
368 validation.expected.boundaries[0], validation.expected.boundaries[1],
369 validation.expected.boundaries[2], validation.expected.boundaries[3],
370 validation.expected.boundaries[4], validation.expected.boundaries[5],
371 validation.expected.boundaries[6], validation.expected.boundaries[7]);
377 if (ImGui::Button(
"Apply Safe Repair")) {
378 const absl::Status status =
381 : absl::FailedPreconditionError(
382 "Camera repair callback is unavailable");
387 ImGui::CloseCurrentPopup();
393 ImGui::EndDisabled();
395 if (ImGui::Button(
"Cancel")) {
399 ImGui::CloseCurrentPopup();
409 if (!state.has_value()) {
410 ImGui::TextDisabled(
"Camera validation unavailable.");
414 ImGui::SeparatorText(
"Camera safety");
415 ImGui::PushTextWrapPos(0.0f);
416 if (!validation.geometry_valid()) {
417 ImGui::TextColored(ImVec4(0.95f, 0.35f, 0.30f, 1.0f),
418 "Unsafe camera geometry");
419 for (
const auto& error : validation.errors) {
422 ImGui::TextWrapped(
"%s", error.c_str());
424 if (validation.can_repair()) {
426 "The room and player position are valid, so a previewed repair "
427 "can replace these derived fields.");
429 }
else if (validation.matches_derived()) {
430 ImGui::TextColored(ImVec4(0.35f, 0.80f, 0.45f, 1.0f),
431 "Camera values match the player position.");
434 "Camera geometry is safe, but %zu derived value group(s) differ.",
435 validation.differences.size());
436 for (
const auto& difference : validation.differences) {
439 ImGui::TextWrapped(
"%s", difference.c_str());
443 ImGui::PopTextWrapPos();
445 if (ImGui::Button(
"Preview Camera Repair", ImVec2(-1, 0))) {
449 ImGui::OpenPopup(
"Camera Repair Preview##DungeonEntrance");
452 ImGui::EndDisabled();
469 const std::array<uint8_t*, 8>& boundaries) {
470 bool changed =
false;
472 const float mode_width = ImGui::CalcTextSize(
"Full room").x;
473 const float column_width =
474 std::max(value_width, ImGui::CalcTextSize(
"North").x);
475 const bool narrow = ImGui::GetContentRegionAvail().x <
476 mode_width + column_width * 4.0f +
477 ImGui::GetStyle().CellPadding.x * 10.0f;
478 if (!ImGui::BeginTable(table_id, narrow ? 2 : 5,
479 ImGuiTableFlags_BordersInnerV |
480 ImGuiTableFlags_SizingStretchProp)) {
484 ImGui::TableSetupColumn(
"Boundary", ImGuiTableColumnFlags_WidthStretch);
485 ImGui::TableSetupColumn(
"Value", ImGuiTableColumnFlags_WidthFixed,
488 ImGui::TableSetupColumn(
"Mode", ImGuiTableColumnFlags_WidthFixed,
490 for (
const auto* direction : {
"North",
"East",
"South",
"West"})
491 ImGui::TableSetupColumn(direction);
492 ImGui::TableHeadersRow();
494 constexpr std::array<int, 4> kQuadrantOrder = {0, 6, 2, 4};
495 constexpr std::array<int, 4> kFullRoomOrder = {1, 7, 3, 5};
496 const auto draw_row = [&changed, &boundaries, narrow, value_width,
this](
498 const std::array<int, 4>& order) {
500 ImGui::TableNextRow();
501 ImGui::TableNextColumn();
502 ImGui::TextUnformatted(label);
504 constexpr const char* directions[] = {
"North",
"East",
"South",
"West"};
505 for (
size_t direction = 0; direction < order.size(); ++direction) {
506 const int index = order[direction];
508 ImGui::TableNextRow();
509 ImGui::TableNextColumn();
510 ImGui::TextWrapped(
"%s %s", label, directions[direction]);
512 ImGui::TableNextColumn();
513 ImGui::PushID(index);
517 "##Boundary", absl::StrFormat(
"CameraBoundary%d", index).c_str());
521 draw_row(
"Quadrant", kQuadrantOrder);
522 draw_row(
"Full room", kFullRoomOrder);
529 ImGui::GetContentRegionAvail().x);
534 if (ImGui::BeginChild(
"##EntrancesList", ImVec2(0, 0),
true,
535 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
536 for (
int i = 0; i < kTotalEntries; i++) {
537 std::string entrance_name;
538 if (i < kNumSpawnPoints) {
539 entrance_name = absl::StrFormat(
"Spawn Point %d", i);
541 const int entrance_id = i - kNumSpawnPoints;
542 entrance_name = entrance_id < kNumEntrances
544 : absl::StrFormat(
"Unknown %d", i);
547 const int room_id = i < kNumSpawnPoints ? (*spawn_points_)[i].room_id
548 : (*entrances_)[i].room_;
549 const std::string room_name =
551 const std::string label = absl::StrFormat(
553 entrance_name, room_name, room_id);
559 if (ImGui::Selectable(label.c_str(), is_selected)) {
571 auto& spawn = (*spawn_points_)[slot_index];
572 const auto before = spawn;
573 const bool properties_editable =
575 bool changed =
false;
577 ImGui::Text(tr(
"Spawn Point %d"), slot_index);
578 if (!properties_editable) {
579 ImGui::TextWrapped(tr(
580 "Spawn point data is unavailable; reload the ROM before editing."));
582 ImGui::BeginDisabled(!properties_editable);
588 "SpawnDungeonId",
true);
589 changed |=
DrawHexProperty(
"Main GFX", &spawn.main_gfx,
"SpawnMainGfx");
594 changed |=
DrawHexProperty(
"Player X", &spawn.x_coordinate,
"SpawnPlayerX");
596 DrawHexProperty(
"Player Y", &spawn.y_coordinate,
"SpawnPlayerY",
true);
598 DrawHexProperty(
"Overworld Door Tilemap", &spawn.overworld_door_tilemap,
600 ImGui::EndDisabled();
605 const auto camera_before = spawn;
607 const bool camera_open = ImGui::CollapsingHeader(
"Advanced camera data");
610 ImGui::BeginDisabled(!properties_editable);
611 changed |=
DrawHexProperty(
"Camera Trigger X", &spawn.camera_trigger_x,
612 "SpawnCameraTriggerX");
613 changed |=
DrawHexProperty(
"Camera Trigger Y", &spawn.camera_trigger_y,
614 "SpawnCameraTriggerY",
true);
615 changed |=
DrawHexProperty(
"Horizontal Scroll", &spawn.horizontal_scroll,
616 "SpawnHorizontalScroll");
618 "SpawnVerticalScroll",
true);
621 "SpawnQuadrant",
true);
624 "SpawnScrollController",
true);
626 ImGui::SeparatorText(tr(
"Camera Boundaries"));
628 {&spawn.camera_scroll_boundaries[0],
629 &spawn.camera_scroll_boundaries[1],
630 &spawn.camera_scroll_boundaries[2],
631 &spawn.camera_scroll_boundaries[3],
632 &spawn.camera_scroll_boundaries[4],
633 &spawn.camera_scroll_boundaries[5],
634 &spawn.camera_scroll_boundaries[6],
635 &spawn.camera_scroll_boundaries[7]});
636 ImGui::EndDisabled();
643 auto& entrance = (*entrances_)[slot_index];
644 const auto before = entrance;
645 const bool properties_editable =
647 bool changed =
false;
649 ImGui::Text(tr(
"Entrance ID: %04X"), entrance.entrance_id_);
650 ImGui::BeginDisabled(!properties_editable);
651 changed |=
DrawHexProperty(
"Room ID", &entrance.room_,
"RegularRoomId");
653 "RegularDungeonId",
true);
656 changed |=
DrawHexProperty(
"Music", &entrance.music_,
"RegularMusic",
true);
657 changed |=
DrawHexProperty(
"Floor", &entrance.floor_,
"RegularFloor",
true);
663 "RegularPlayerY",
true);
665 ImGui::EndDisabled();
670 const auto camera_before = entrance;
672 const bool camera_open = ImGui::CollapsingHeader(
"Advanced camera data");
675 ImGui::BeginDisabled(!properties_editable);
678 "RegularCameraTriggerX");
681 "RegularCameraTriggerY",
true);
685 "RegularScrollY",
true);
686 changed |=
DrawHexProperty(
"Scroll Quadrant", &entrance.scroll_quadrant_,
687 "RegularScrollQuadrant");
689 ImGui::SeparatorText(tr(
"Camera Boundaries"));
691 "##RegularCameraBoundaries",
692 {&entrance.camera_boundary_qn_, &entrance.camera_boundary_fn_,
693 &entrance.camera_boundary_qs_, &entrance.camera_boundary_fs_,
694 &entrance.camera_boundary_qw_, &entrance.camera_boundary_fw_,
695 &entrance.camera_boundary_qe_, &entrance.camera_boundary_fe_});
696 ImGui::EndDisabled();
705 auto& entrance = (*entrances_)[slot_index];
721 auto& spawn = (*spawn_points_)[slot_index];
737 std::array<zelda3::RoomEntrance, zelda3::kNumDungeonEntranceSlots>*
739 std::array<zelda3::DungeonSpawnPoint, zelda3::kNumDungeonSpawnPoints>*
WindowContent for displaying and editing dungeon entrances.
ImGuiID property_edit_active_id_
void DrawGraphicsContext(float width=-1.0f)
void SetPropertyEditCallbacks(EntrancePropertyEditCallback entrance_callback, SpawnPropertyEditCallback spawn_callback)
bool OwnsNavigationShortcutFocus() const
DungeonEntrancesPanel(std::array< zelda3::RoomEntrance, zelda3::kNumDungeonEntranceSlots > *entrances, std::array< zelda3::DungeonSpawnPoint, zelda3::kNumDungeonSpawnPoints > *spawn_points, int *current_entrance_id, std::function< void(int)> on_entrance_selected)
void DrawCameraRepairPreview()
std::optional< DungeonEntranceCameraState > camera_repair_preview_
std::function< bool()> preview_automatic_
void DrawSpawnPointProperties(int slot_index)
int navigation_shortcut_last_draw_frame_
void Draw(bool *p_open) override
Draw the panel content.
std::function< void(int)> on_entrance_selected_
void SetCameraTools(CameraStateProvider state_provider, CameraRepairCallback repair_callback, std::function< void()> data_changed_callback={})
int GetPriority() const override
Get display priority for menu ordering.
SpawnPropertyEditCallback spawn_property_edit_callback_
void RegisterPropertyInput(const char *field_label, const char *widget_label)
void DrawCameraSafety(int slot_index)
void DrawSelectedProperties()
std::function< std::optional< DungeonEntranceCameraState >(int)> CameraStateProvider
void CommitEntrancePropertyEdit(int slot_index, const zelda3::RoomEntrance &before, bool changed)
bool navigation_shortcut_focus_
bool DrawHexProperty(const char *label, T *value, const char *widget_label, bool continue_row=false)
std::array< zelda3::DungeonSpawnPoint, zelda3::kNumDungeonSpawnPoints > * spawn_points_
float HexPropertyWidth(int digits) const
void SetProject(const project::YazeProject *project)
std::function< void( int, const zelda3::DungeonSpawnPoint &, const zelda3::DungeonSpawnPoint &)> SpawnPropertyEditCallback
std::string GetDisplayName() const override
Human-readable name shown in menus and title bars.
int * current_entrance_id_
std::string GetWorkflowGroup() const override
Optional workflow group for hack-centric actions.
std::array< zelda3::RoomEntrance, zelda3::kNumDungeonEntranceSlots > * entrances_
std::string camera_repair_error_
const project::YazeProject * project_
std::shared_ptr< const int > property_edit_gesture() const
std::string GetEditorCategory() const override
Editor category this panel belongs to.
EntrancePropertyEditCallback entrance_property_edit_callback_
CameraRepairCallback camera_repair_callback_
std::string GetId() const override
Unique identifier for this panel.
ImGuiTextFilter entrance_filter_
std::string GetIcon() const override
Material Design icon for this panel.
std::function< void()> camera_data_changed_callback_
CameraStateProvider camera_state_provider_
std::function< DungeonRenderContext()> preview_context_
void DrawPropertiesInWorkbench()
void DrawRegularEntranceProperties(int slot_index)
void SetGraphicsPreview(std::function< DungeonRenderContext()> context, std::function< bool()> automatic, std::function< void(int)> select)
void ContinuePropertyRow(const char *label, int digits) const
std::function< absl::Status(int)> CameraRepairCallback
std::function< void(int)> select_preview_
std::function< void( int, const zelda3::RoomEntrance &, const zelda3::RoomEntrance &)> EntrancePropertyEditCallback
void ObservePropertyEditGesture()
std::string EntranceLabel(int slot) const
std::shared_ptr< const int > property_edit_gesture_
bool DrawCameraBoundaries(const char *table_id, const std::array< uint8_t *, 8 > &boundaries)
void CommitSpawnPropertyEdit(int slot_index, const zelda3::DungeonSpawnPoint &before, bool changed)
Base interface for all logical window content components.
Dungeon Room Entrance or Spawn Point.
#define ICON_MD_DOOR_FRONT
std::string GetEntranceLabel(const project::YazeProject *project, int entrance_id, int room_id)
std::string GetRoomLabel(const project::YazeProject *project, int room_id)
std::string DungeonEntrancePreviewLabel(int slot)
bool CanEditDungeonEntrance(int slot_index, const zelda3::RoomEntrance &entrance)
bool CanEditDungeonSpawnPoint(int slot_index, const zelda3::DungeonSpawnPoint &spawn)
bool MarkDungeonEntranceDirtyIfEditable(int slot_index, zelda3::RoomEntrance &entrance, bool properties_changed)
bool MarkDungeonSpawnPointDirtyIfEditable(int slot_index, zelda3::DungeonSpawnPoint &spawn, bool properties_changed)
DungeonEntranceCameraValidation ValidateDungeonEntranceCamera(const DungeonEntranceCameraState &state)
void AutoRegisterLastItem(const std::string &widget_type, const std::string &explicit_label, const std::string &description)
Automatically register the last ImGui item.
bool InputHexWord(const char *label, uint16_t *data, float input_width, bool no_step)
bool InputHexByte(const char *label, uint8_t *data, float input_width, bool no_step)
constexpr int kNumRegularDungeonEntrances
constexpr int kNumDungeonSpawnPoints
constexpr int kNumDungeonEntranceSlots
Modern project structure with comprehensive settings consolidation.