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"
22#include "imgui/imgui.h"
43 std::array<zelda3::RoomEntrance, zelda3::kNumDungeonEntranceSlots>*
45 std::array<zelda3::DungeonSpawnPoint, zelda3::kNumDungeonSpawnPoints>*
47 int* current_entrance_id, std::function<
void(
int)> on_entrance_selected)
57 std::string
GetId()
const override {
return "dungeon.entrance_properties"; }
65 std::function<std::optional<DungeonEntranceCameraState>(
int)>;
84 std::function<
void()> data_changed_callback = {}) {
97 std::function<
bool()> automatic,
98 std::function<
void(
int)> select) {
114 ? (*spawn_points_)[selected].main_gfx
115 : (*entrances_)[selected].blockset_;
116 std::string preview = absl::StrFormat(
121 const std::string name =
122 automatic && !context.owner_name.empty() ? context.owner_name
128 context.uses_entrance()
130 "%s: %s / %s", automatic ?
"Auto" :
"Manual", name,
133 "Auto: %s / GFX %02X",
137 context.effective_blockset());
139 ImGui::SetNextItemWidth(width);
140 const bool open = ImGui::BeginCombo(
141 "##GraphicsEntrance", preview.c_str(),
144 if (ImGui::IsItemHovered())
146 "Auto follows the room's dungeon. Choosing an entrance pins its "
147 "graphics until you choose Auto again. This changes only the "
151 if (ImGui::Selectable(
"Auto (follow room)", automatic))
156 ImGui::BeginChild(
"##EntranceChoices",
157 ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 8));
159 for (
int slot = 0; slot < static_cast<int>(
entrances_->size()); ++slot) {
161 const std::string label =
167 if (ImGui::Selectable(label.c_str(), !automatic && slot == selected)) {
170 ImGui::CloseCurrentPopup();
182 if (slot == selected) {
183 ImGui::SetItemDefaultFocus();
185 ImGui::SetScrollHereY(0.5f);
197 ImGui::TextWrapped(
"Choose an entrance to edit its properties.");
210 void Draw(
bool* p_open)
override {
220 ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows);
222 const bool split_layout = ImGui::GetContentRegionAvail().x >= 620.0f;
224 ImGui::BeginTable(
"##EntranceNavigator", 2,
225 ImGuiTableFlags_Resizable |
226 ImGuiTableFlags_BordersInnerV |
227 ImGuiTableFlags_SizingStretchProp)) {
228 ImGui::TableSetupColumn(
"Entrances", ImGuiTableColumnFlags_WidthFixed,
230 ImGui::TableSetupColumn(
"Properties", ImGuiTableColumnFlags_WidthStretch);
231 ImGui::TableNextRow();
232 ImGui::TableNextColumn();
234 ImGui::TableNextColumn();
240 if (ImGui::BeginTabBar(
"##EntranceNavigatorTabs")) {
258 const auto validation =
260 if (!ImGui::BeginPopupModal(
"Camera Repair Preview##DungeonEntrance",
261 nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
266 "Review the derived camera values before changing this entrance. "
267 "The repair is one undoable action.");
269 if (ImGui::BeginTable(
270 "##CameraRepairValues", 3,
271 ImGuiTableFlags_BordersInner | ImGuiTableFlags_SizingFixedFit)) {
272 ImGui::TableSetupColumn(
"Field");
273 ImGui::TableSetupColumn(
"Current");
274 ImGui::TableSetupColumn(
"Derived");
275 ImGui::TableHeadersRow();
276 const auto draw_word_row = [](
const char* label, uint16_t current,
278 ImGui::TableNextRow();
279 ImGui::TableNextColumn();
280 ImGui::TextUnformatted(label);
281 ImGui::TableNextColumn();
282 ImGui::Text(
"0x%04X", current);
283 ImGui::TableNextColumn();
284 ImGui::Text(
"0x%04X", derived);
287 validation.expected.camera_x);
289 validation.expected.camera_y);
291 validation.expected.trigger_x);
293 validation.expected.trigger_y);
294 ImGui::TableNextRow();
295 ImGui::TableNextColumn();
296 ImGui::TextUnformatted(
"Quadrant");
297 ImGui::TableNextColumn();
299 ImGui::TableNextColumn();
300 if (validation.quadrant_repair_safe) {
301 ImGui::Text(
"0x%02X", validation.expected.quadrant);
303 ImGui::TextUnformatted(
"Preserved");
308 "Boundary pages: [%02X %02X %02X %02X | %02X %02X %02X %02X] "
309 "-> [%02X %02X %02X %02X | %02X %02X %02X %02X]",
318 validation.expected.boundaries[0], validation.expected.boundaries[1],
319 validation.expected.boundaries[2], validation.expected.boundaries[3],
320 validation.expected.boundaries[4], validation.expected.boundaries[5],
321 validation.expected.boundaries[6], validation.expected.boundaries[7]);
327 if (ImGui::Button(
"Apply Safe Repair")) {
328 const absl::Status status =
331 : absl::FailedPreconditionError(
332 "Camera repair callback is unavailable");
337 ImGui::CloseCurrentPopup();
343 ImGui::EndDisabled();
345 if (ImGui::Button(
"Cancel")) {
349 ImGui::CloseCurrentPopup();
359 if (!state.has_value()) {
360 ImGui::TextDisabled(
"Camera validation unavailable.");
364 ImGui::SeparatorText(
"Camera safety");
365 if (!validation.geometry_valid()) {
366 ImGui::TextColored(ImVec4(0.95f, 0.35f, 0.30f, 1.0f),
367 "Unsafe camera geometry");
368 for (
const auto& error : validation.errors) {
369 ImGui::BulletText(
"%s", error.c_str());
371 if (validation.can_repair()) {
373 "The room and player position are valid, so a previewed repair "
374 "can replace these derived fields.");
376 }
else if (validation.matches_derived()) {
377 ImGui::TextColored(ImVec4(0.35f, 0.80f, 0.45f, 1.0f),
378 "Camera values match the player position.");
381 "Camera geometry is safe, but %zu derived value group(s) differ.",
382 validation.differences.size());
383 for (
const auto& difference : validation.differences) {
384 ImGui::BulletText(
"%s", difference.c_str());
389 if (ImGui::Button(
"Preview Camera Repair")) {
393 ImGui::OpenPopup(
"Camera Repair Preview##DungeonEntrance");
396 ImGui::EndDisabled();
413 const std::array<uint8_t*, 8>& boundaries) {
414 bool changed =
false;
415 if (!ImGui::BeginTable(table_id, 5,
416 ImGuiTableFlags_BordersInnerV |
417 ImGuiTableFlags_SizingStretchSame)) {
420 ImGui::TableSetupColumn(
"Mode", ImGuiTableColumnFlags_WidthFixed, 70.0f);
421 ImGui::TableSetupColumn(
"North");
422 ImGui::TableSetupColumn(
"East");
423 ImGui::TableSetupColumn(
"South");
424 ImGui::TableSetupColumn(
"West");
425 ImGui::TableHeadersRow();
426 constexpr std::array<int, 4> kQuadrantOrder = {0, 6, 2, 4};
427 constexpr std::array<int, 4> kFullRoomOrder = {1, 7, 3, 5};
428 const auto draw_row = [&changed, &boundaries](
430 const std::array<int, 4>& order) {
431 ImGui::TableNextRow();
432 ImGui::TableNextColumn();
433 ImGui::TextUnformatted(label);
434 for (
const int index : order) {
435 ImGui::TableNextColumn();
436 ImGui::PushID(index);
438 "##Boundary", boundaries[index],
439 std::max(36.0f, ImGui::GetContentRegionAvail().x),
true);
443 draw_row(
"Quadrant", kQuadrantOrder);
444 draw_row(
"Full room", kFullRoomOrder);
451 ImGui::GetContentRegionAvail().x);
456 if (ImGui::BeginChild(
"##EntrancesList", ImVec2(0, 0),
true,
457 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
458 for (
int i = 0; i < kTotalEntries; i++) {
459 std::string entrance_name;
460 if (i < kNumSpawnPoints) {
461 entrance_name = absl::StrFormat(
"Spawn Point %d", i);
463 const int entrance_id = i - kNumSpawnPoints;
464 entrance_name = entrance_id < kNumEntrances
466 : absl::StrFormat(
"Unknown %d", i);
469 const int room_id = i < kNumSpawnPoints ? (*spawn_points_)[i].room_id
470 : (*entrances_)[i].room_;
472 const std::string label = absl::StrFormat(
473 "[%02X] %s -> %s (%03X)", i, entrance_name, room_name, room_id);
479 if (ImGui::Selectable(label.c_str(), is_selected)) {
491 auto& spawn = (*spawn_points_)[slot_index];
492 const auto before = spawn;
493 const bool properties_editable =
495 bool changed =
false;
497 ImGui::Text(tr(
"Spawn Point %d"), slot_index);
498 if (!properties_editable) {
499 ImGui::TextWrapped(tr(
500 "Spawn point data is unavailable; reload the ROM before editing."));
502 ImGui::BeginDisabled(!properties_editable);
526 &spawn.overworld_door_tilemap, 70.f,
true);
527 ImGui::EndDisabled();
532 const auto camera_before = spawn;
534 const bool camera_open = ImGui::CollapsingHeader(
"Advanced camera data");
537 ImGui::BeginDisabled(!properties_editable);
555 &spawn.camera_scroll_controller, 50.f,
true);
557 ImGui::SeparatorText(tr(
"Camera Boundaries"));
559 {&spawn.camera_scroll_boundaries[0],
560 &spawn.camera_scroll_boundaries[1],
561 &spawn.camera_scroll_boundaries[2],
562 &spawn.camera_scroll_boundaries[3],
563 &spawn.camera_scroll_boundaries[4],
564 &spawn.camera_scroll_boundaries[5],
565 &spawn.camera_scroll_boundaries[6],
566 &spawn.camera_scroll_boundaries[7]});
567 ImGui::EndDisabled();
574 auto& entrance = (*entrances_)[slot_index];
575 const auto before = entrance;
576 const bool properties_editable =
578 bool changed =
false;
580 ImGui::Text(tr(
"Entrance ID: %04X"), entrance.entrance_id_);
581 ImGui::BeginDisabled(!properties_editable);
604 ImGui::EndDisabled();
609 const auto camera_before = entrance;
611 const bool camera_open = ImGui::CollapsingHeader(
"Advanced camera data");
614 ImGui::BeginDisabled(!properties_editable);
627 &entrance.scroll_quadrant_, 50.f,
true);
629 ImGui::SeparatorText(tr(
"Camera Boundaries"));
631 "##RegularCameraBoundaries",
632 {&entrance.camera_boundary_qn_, &entrance.camera_boundary_fn_,
633 &entrance.camera_boundary_qs_, &entrance.camera_boundary_fs_,
634 &entrance.camera_boundary_qw_, &entrance.camera_boundary_fw_,
635 &entrance.camera_boundary_qe_, &entrance.camera_boundary_fe_});
636 ImGui::EndDisabled();
645 auto& entrance = (*entrances_)[slot_index];
661 auto& spawn = (*spawn_points_)[slot_index];
677 std::array<zelda3::RoomEntrance, zelda3::kNumDungeonEntranceSlots>*
679 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_
std::array< zelda3::DungeonSpawnPoint, zelda3::kNumDungeonSpawnPoints > * spawn_points_
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_
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)
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::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 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)
std::string GetEntranceLabel(int id)
Convenience function to get an entrance label.
std::string GetRoomLabel(int id)
Convenience function to get a room label.
constexpr int kNumRegularDungeonEntrances
constexpr int kNumDungeonSpawnPoints
constexpr int kNumDungeonEntranceSlots