yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
dungeon_entrances_panel.h
Go to the documentation of this file.
1#ifndef YAZE_APP_EDITOR_DUNGEON_PANELS_DUNGEON_ENTRANCES_PANEL_H_
2#define YAZE_APP_EDITOR_DUNGEON_PANELS_DUNGEON_ENTRANCES_PANEL_H_
3
4#include <algorithm>
5#include <array>
6#include <cstdio>
7#include <functional>
8#include <memory>
9#include <optional>
10#include <string>
11#include <utility>
12
13#include "absl/status/status.h"
14#include "absl/strings/str_format.h"
21#include "app/gui/core/icons.h"
22#include "app/gui/core/input.h"
23#include "imgui/imgui.h"
24#include "util/i18n/tr.h"
25#include "zelda3/common.h"
28
29namespace yaze {
30namespace editor {
31
42 public:
44 std::array<zelda3::RoomEntrance, zelda3::kNumDungeonEntranceSlots>*
45 entrances,
46 std::array<zelda3::DungeonSpawnPoint, zelda3::kNumDungeonSpawnPoints>*
47 spawn_points,
48 int* current_entrance_id, std::function<void(int)> on_entrance_selected)
49 : entrances_(entrances),
50 spawn_points_(spawn_points),
51 current_entrance_id_(current_entrance_id),
52 on_entrance_selected_(std::move(on_entrance_selected)) {}
53
54 // ==========================================================================
55 // WindowContent Identity
56 // ==========================================================================
57
58 std::string GetId() const override { return "dungeon.entrance_properties"; }
59 std::string GetDisplayName() const override { return "Entrances"; }
60 std::string GetIcon() const override { return ICON_MD_DOOR_FRONT; }
61 std::string GetEditorCategory() const override { return "Dungeon"; }
62 int GetPriority() const override { return 26; }
63 std::string GetWorkflowGroup() const override { return "Core"; }
64
66 std::function<std::optional<DungeonEntranceCameraState>(int)>;
67 using CameraRepairCallback = std::function<absl::Status(int)>;
68 using EntrancePropertyEditCallback = std::function<void(
69 int, const zelda3::RoomEntrance&, const zelda3::RoomEntrance&)>;
70 using SpawnPropertyEditCallback = std::function<void(
72
74 SpawnPropertyEditCallback spawn_callback) {
75 entrance_property_edit_callback_ = std::move(entrance_callback);
76 spawn_property_edit_callback_ = std::move(spawn_callback);
77 }
78
79 std::shared_ptr<const int> property_edit_gesture() const {
81 }
82
84 CameraRepairCallback repair_callback,
85 std::function<void()> data_changed_callback = {}) {
86 camera_state_provider_ = std::move(state_provider);
87 camera_repair_callback_ = std::move(repair_callback);
88 camera_data_changed_callback_ = std::move(data_changed_callback);
89 }
91 if (!navigation_shortcut_focus_ || ImGui::GetCurrentContext() == nullptr) {
92 return false;
93 }
94 return navigation_shortcut_last_draw_frame_ >= ImGui::GetFrameCount() - 1;
95 }
96
97 void SetGraphicsPreview(std::function<DungeonRenderContext()> context,
98 std::function<bool()> automatic,
99 std::function<void(int)> select) {
100 preview_context_ = std::move(context);
101 preview_automatic_ = std::move(automatic);
102 select_preview_ = std::move(select);
103 }
104
105 void SetProject(const project::YazeProject* project) { project_ = project; }
106
107 // Shared preview selector: regular IDs are not the combined list slots,
108 // whose first entries are dedicated spawn points.
109 void DrawGraphicsContext(float width = -1.0f) {
111 return;
112 if (*current_entrance_id_ < 0 ||
113 *current_entrance_id_ >= static_cast<int>(entrances_->size()))
115 const int selected = *current_entrance_id_;
116 const uint8_t main_gfx = selected < zelda3::kNumDungeonSpawnPoints
117 ? (*spawn_points_)[selected].main_gfx
118 : (*entrances_)[selected].blockset_;
119 std::string preview = absl::StrFormat(
120 "%s / GFX %02X", DungeonEntrancePreviewLabel(selected), main_gfx);
121 const bool automatic = preview_automatic_ && preview_automatic_();
122 if (preview_context_) {
123 const auto context = preview_context_();
124 const std::string name =
125 automatic && !context.owner_name.empty() ? context.owner_name
126 : context.entrance_slot >= zelda3::kNumDungeonSpawnPoints
127 ? EntranceLabel(context.entrance_slot)
128 : DungeonEntrancePreviewLabel(context.entrance_slot);
129 preview =
130 context.uses_entrance()
131 ? absl::StrFormat(
132 "%s: %s / %s", automatic ? "Auto" : "Manual", name,
133 DungeonEntrancePreviewLabel(context.entrance_slot))
134 : absl::StrFormat(
135 "Auto: %s / GFX %02X",
137 ? "Choose entrance"
138 : "Room fallback",
139 context.effective_blockset());
140 }
141 ImGui::SetNextItemWidth(width);
142 const bool open = ImGui::BeginCombo(
143 "##GraphicsEntrance", preview.c_str(),
144 select_preview_ ? ImGuiComboFlags_HeightLarge : ImGuiComboFlags_None);
145 gui::AutoRegisterLastItem("combo", "Graphics entrance");
146 if (ImGui::IsItemHovered())
147 ImGui::SetTooltip(
148 "Auto follows the room's dungeon. Choosing an entrance pins its "
149 "graphics until you choose Auto again. This changes only the "
150 "preview.");
151 if (open) {
152 if (select_preview_) {
153 if (ImGui::Selectable("Auto (follow room)", automatic))
154 select_preview_(-1);
155 gui::AutoRegisterLastItem("selectable", "Auto (follow room)");
156 ImGui::Separator();
157 // Keep Auto visible while the entrance list scrolls to a manual choice.
158 ImGui::BeginChild("##EntranceChoices",
159 ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 8));
160 }
161 for (int slot = 0; slot < static_cast<int>(entrances_->size()); ++slot) {
162 const std::string entry = DungeonEntrancePreviewLabel(slot);
163 const std::string label = slot >= zelda3::kNumDungeonSpawnPoints
164 ? entry + " - " + EntranceLabel(slot)
165 : entry;
166 if (ImGui::Selectable(label.c_str(), !automatic && slot == selected)) {
167 if (select_preview_) {
168 select_preview_(slot);
169 ImGui::CloseCurrentPopup();
170 } else {
171 *current_entrance_id_ = slot;
174 }
175 }
176 gui::AutoRegisterLastItem("selectable", entry);
177 if (ImGui::IsItemHovered() && slot >= zelda3::kNumDungeonSpawnPoints)
178 ImGui::SetTooltip("%s", EntranceLabel(slot).c_str());
179 if (slot == selected) {
180 ImGui::SetItemDefaultFocus();
181 if (select_preview_ && ImGui::IsWindowAppearing())
182 ImGui::SetScrollHereY(0.5f);
183 }
184 }
185 if (select_preview_)
186 ImGui::EndChild();
187 ImGui::EndCombo();
188 }
189 }
190
193 !preview_context_().uses_entrance()) {
194 ImGui::TextWrapped("Choose an entrance to edit its properties.");
195 return;
196 }
198 *current_entrance_id_ >= 0 &&
199 *current_entrance_id_ < static_cast<int>(entrances_->size()))
201 }
202
203 // ==========================================================================
204 // WindowContent Drawing
205 // ==========================================================================
206
207 void Draw(bool* p_open) override {
208 (void)p_open;
210 return;
211 if (*current_entrance_id_ < 0 ||
212 *current_entrance_id_ >= static_cast<int>(entrances_->size())) {
214 }
215 navigation_shortcut_last_draw_frame_ = ImGui::GetFrameCount();
217 ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows);
218
219 const bool split_layout = ImGui::GetContentRegionAvail().x >= 620.0f;
220 if (split_layout &&
221 ImGui::BeginTable("##EntranceNavigator", 2,
222 ImGuiTableFlags_Resizable |
223 ImGuiTableFlags_BordersInnerV |
224 ImGuiTableFlags_SizingStretchProp)) {
225 ImGui::TableSetupColumn("Entrances", ImGuiTableColumnFlags_WidthFixed,
226 300.0f);
227 ImGui::TableSetupColumn("Properties", ImGuiTableColumnFlags_WidthStretch);
228 ImGui::TableNextRow();
229 ImGui::TableNextColumn();
231 ImGui::TableNextColumn();
233 ImGui::EndTable();
234 return;
235 }
236
237 if (ImGui::BeginTabBar("##EntranceNavigatorTabs")) {
238 if (ImGui::BeginTabItem(ICON_MD_DOOR_FRONT " Entrances")) {
240 ImGui::EndTabItem();
241 }
242 if (ImGui::BeginTabItem(ICON_MD_TUNE " Properties")) {
244 ImGui::EndTabItem();
245 }
246 ImGui::EndTabBar();
247 }
248 }
249
250 private:
251 float HexPropertyWidth(int digits) const {
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);
257 }
258 return glyph_width * digits + ImGui::GetStyle().FramePadding.x * 2.0f +
259 2.0f;
260 }
261
262 void ContinuePropertyRow(const char* label, int digits) const {
263 const float right =
264 ImGui::GetCursorScreenPos().x + ImGui::GetContentRegionAvail().x;
265 const float spacing = ImGui::GetStyle().ItemSpacing.x;
266 const float next_width =
267 ImGui::CalcTextSize(label).x + spacing + HexPropertyWidth(digits);
268 if (ImGui::GetItemRectMax().x + spacing + next_width <= right)
269 ImGui::SameLine();
270 }
271
272 template <typename T>
273 bool DrawHexProperty(const char* label, T* value, const char* widget_label,
274 bool continue_row = false) {
275 constexpr int digits = sizeof(T) == 1 ? 2 : 4;
276 if (continue_row)
277 ContinuePropertyRow(label, digits);
278 const float width = HexPropertyWidth(digits);
279 // InputScalarLeft draws its label with Text(), so wrapping the label keeps
280 // the existing input ID and wheel/typing behavior even for a long label.
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)
287 changed = gui::InputHexByte(label, value, width, true);
288 else
289 changed = gui::InputHexWord(label, value, width, true);
290 ImGui::PopTextWrapPos();
291 RegisterPropertyInput(label, widget_label);
292 return changed;
293 }
294
295 std::string EntranceLabel(int slot) const {
296 const int entrance_id = slot - zelda3::kNumDungeonSpawnPoints;
297 if (!entrances_ || entrance_id < 0 ||
298 slot >= static_cast<int>(entrances_->size()))
299 return DungeonEntrancePreviewLabel(slot);
301 (*entrances_)[slot].room_);
302 }
303
305 if (!camera_repair_preview_.has_value()) {
306 return;
307 }
308 const auto validation =
310 if (!ImGui::BeginPopupModal("Camera Repair Preview##DungeonEntrance",
311 nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
312 return;
313 }
314
315 ImGui::TextWrapped(
316 "Review the derived camera values before changing this entrance. "
317 "The repair is one undoable action.");
318 ImGui::Separator();
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,
327 uint16_t derived) {
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);
335 };
336 draw_word_row("Scroll X", camera_repair_preview_->camera_x,
337 validation.expected.camera_x);
338 draw_word_row("Scroll Y", camera_repair_preview_->camera_y,
339 validation.expected.camera_y);
340 draw_word_row("Trigger X", camera_repair_preview_->trigger_x,
341 validation.expected.trigger_x);
342 draw_word_row("Trigger Y", camera_repair_preview_->trigger_y,
343 validation.expected.trigger_y);
344 ImGui::TableNextRow();
345 ImGui::TableNextColumn();
346 ImGui::TextUnformatted("Quadrant");
347 ImGui::TableNextColumn();
348 ImGui::Text("0x%02X", camera_repair_preview_->quadrant);
349 ImGui::TableNextColumn();
350 if (validation.quadrant_repair_safe) {
351 ImGui::Text("0x%02X", validation.expected.quadrant);
352 } else {
353 ImGui::TextUnformatted("Preserved");
354 }
355 ImGui::EndTable();
356 }
357 ImGui::TextWrapped(
358 "Boundary pages: [%02X %02X %02X %02X | %02X %02X %02X %02X] "
359 "-> [%02X %02X %02X %02X | %02X %02X %02X %02X]",
360 camera_repair_preview_->boundaries[0],
361 camera_repair_preview_->boundaries[1],
362 camera_repair_preview_->boundaries[2],
363 camera_repair_preview_->boundaries[3],
364 camera_repair_preview_->boundaries[4],
365 camera_repair_preview_->boundaries[5],
366 camera_repair_preview_->boundaries[6],
367 camera_repair_preview_->boundaries[7],
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]);
372 if (!camera_repair_error_.empty()) {
373 ImGui::TextWrapped("Repair failed: %s", camera_repair_error_.c_str());
374 }
375
376 ImGui::BeginDisabled(!validation.can_repair() || !camera_repair_callback_);
377 if (ImGui::Button("Apply Safe Repair")) {
378 const absl::Status status =
381 : absl::FailedPreconditionError(
382 "Camera repair callback is unavailable");
383 if (status.ok()) {
386 camera_repair_error_.clear();
387 ImGui::CloseCurrentPopup();
388 } else {
389 camera_repair_error_ = std::string(status.message());
390 }
391 }
392 gui::AutoRegisterLastItem("button", "ApplyCameraRepair");
393 ImGui::EndDisabled();
394 ImGui::SameLine();
395 if (ImGui::Button("Cancel")) {
398 camera_repair_error_.clear();
399 ImGui::CloseCurrentPopup();
400 }
401 ImGui::EndPopup();
402 }
403
404 void DrawCameraSafety(int slot_index) {
406 return;
407 }
408 const auto state = camera_state_provider_(slot_index);
409 if (!state.has_value()) {
410 ImGui::TextDisabled("Camera validation unavailable.");
411 return;
412 }
413 const auto validation = ValidateDungeonEntranceCamera(*state);
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) {
420 ImGui::Bullet();
421 ImGui::SameLine();
422 ImGui::TextWrapped("%s", error.c_str());
423 }
424 if (validation.can_repair()) {
425 ImGui::TextWrapped(
426 "The room and player position are valid, so a previewed repair "
427 "can replace these derived fields.");
428 }
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.");
432 } else {
433 ImGui::TextWrapped(
434 "Camera geometry is safe, but %zu derived value group(s) differ.",
435 validation.differences.size());
436 for (const auto& difference : validation.differences) {
437 ImGui::Bullet();
438 ImGui::SameLine();
439 ImGui::TextWrapped("%s", difference.c_str());
440 }
441 }
442
443 ImGui::PopTextWrapPos();
444 ImGui::BeginDisabled(!validation.can_repair() || !camera_repair_callback_);
445 if (ImGui::Button("Preview Camera Repair", ImVec2(-1, 0))) {
446 camera_repair_preview_ = *state;
447 camera_repair_slot_ = slot_index;
448 camera_repair_error_.clear();
449 ImGui::OpenPopup("Camera Repair Preview##DungeonEntrance");
450 }
451 gui::AutoRegisterLastItem("button", "PreviewCameraRepair");
452 ImGui::EndDisabled();
454 }
455
467
468 bool DrawCameraBoundaries(const char* table_id,
469 const std::array<uint8_t*, 8>& boundaries) {
470 bool changed = false;
471 const float value_width = HexPropertyWidth(2);
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)) {
481 return false;
482 }
483 if (narrow) {
484 ImGui::TableSetupColumn("Boundary", ImGuiTableColumnFlags_WidthStretch);
485 ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthFixed,
486 value_width);
487 } else {
488 ImGui::TableSetupColumn("Mode", ImGuiTableColumnFlags_WidthFixed,
489 mode_width);
490 for (const auto* direction : {"North", "East", "South", "West"})
491 ImGui::TableSetupColumn(direction);
492 ImGui::TableHeadersRow();
493 }
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](
497 const char* label,
498 const std::array<int, 4>& order) {
499 if (!narrow) {
500 ImGui::TableNextRow();
501 ImGui::TableNextColumn();
502 ImGui::TextUnformatted(label);
503 }
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];
507 if (narrow) {
508 ImGui::TableNextRow();
509 ImGui::TableNextColumn();
510 ImGui::TextWrapped("%s %s", label, directions[direction]);
511 }
512 ImGui::TableNextColumn();
513 ImGui::PushID(index);
514 changed |= gui::InputHexByte("##Boundary", boundaries[index],
515 value_width, true);
517 "##Boundary", absl::StrFormat("CameraBoundary%d", index).c_str());
518 ImGui::PopID();
519 }
520 };
521 draw_row("Quadrant", kQuadrantOrder);
522 draw_row("Full room", kFullRoomOrder);
523 ImGui::EndTable();
524 return changed;
525 }
526
528 entrance_filter_.Draw(ICON_MD_SEARCH " Filter",
529 ImGui::GetContentRegionAvail().x);
530 constexpr int kNumSpawnPoints = zelda3::kNumDungeonSpawnPoints;
531 constexpr int kNumEntrances = zelda3::kNumRegularDungeonEntrances;
532 constexpr int kTotalEntries = zelda3::kNumDungeonEntranceSlots;
533
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);
540 } else {
541 const int entrance_id = i - kNumSpawnPoints;
542 entrance_name = entrance_id < kNumEntrances
543 ? EntranceLabel(i)
544 : absl::StrFormat("Unknown %d", i);
545 }
546
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(
552 "%s - %s -> %s (%03X)", DungeonEntrancePreviewLabel(i),
553 entrance_name, room_name, room_id);
554 if (!entrance_filter_.PassFilter(label.c_str())) {
555 continue;
556 }
557
558 const bool is_selected = (*current_entrance_id_ == i);
559 if (ImGui::Selectable(label.c_str(), is_selected)) {
563 }
564 }
565 }
566 }
567 ImGui::EndChild();
568 }
569
570 void DrawSpawnPointProperties(int slot_index) {
571 auto& spawn = (*spawn_points_)[slot_index];
572 const auto before = spawn;
573 const bool properties_editable =
574 CanEditDungeonSpawnPoint(slot_index, spawn);
575 bool changed = false;
576
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."));
581 }
582 ImGui::BeginDisabled(!properties_editable);
583
584 changed |=
585 DrawHexProperty("Entrance ID", &spawn.entrance_id, "SpawnEntranceId");
586 changed |= DrawHexProperty("Room ID", &spawn.room_id, "SpawnRoomId");
587 changed |= DrawHexProperty("Dungeon ID", &spawn.dungeon_id,
588 "SpawnDungeonId", true);
589 changed |= DrawHexProperty("Main GFX", &spawn.main_gfx, "SpawnMainGfx");
590 changed |= DrawHexProperty("Song", &spawn.song, "SpawnSong", true);
591 changed |= DrawHexProperty("Floor", &spawn.floor, "SpawnFloor", true);
592
593 ImGui::Separator();
594 changed |= DrawHexProperty("Player X", &spawn.x_coordinate, "SpawnPlayerX");
595 changed |=
596 DrawHexProperty("Player Y", &spawn.y_coordinate, "SpawnPlayerY", true);
597 changed |=
598 DrawHexProperty("Overworld Door Tilemap", &spawn.overworld_door_tilemap,
599 "SpawnDoorTilemap");
600 ImGui::EndDisabled();
601
602 CommitSpawnPropertyEdit(slot_index, before, changed);
603 DrawCameraSafety(slot_index);
604
605 const auto camera_before = spawn;
606 changed = false;
607 const bool camera_open = ImGui::CollapsingHeader("Advanced camera data");
608 gui::AutoRegisterLastItem("header", "AdvancedCameraData");
609 if (camera_open) {
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");
617 changed |= DrawHexProperty("Vertical Scroll", &spawn.vertical_scroll,
618 "SpawnVerticalScroll", true);
619 changed |= DrawHexProperty("Layer", &spawn.layer, "SpawnLayer");
620 changed |= DrawHexProperty("Spawn Quadrant", &spawn.quadrant,
621 "SpawnQuadrant", true);
622 changed |=
623 DrawHexProperty("Scroll Controller", &spawn.camera_scroll_controller,
624 "SpawnScrollController", true);
625
626 ImGui::SeparatorText(tr("Camera Boundaries"));
627 changed |= DrawCameraBoundaries("##SpawnCameraBoundaries",
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();
637 }
638
639 CommitSpawnPropertyEdit(slot_index, camera_before, changed);
640 }
641
642 void DrawRegularEntranceProperties(int slot_index) {
643 auto& entrance = (*entrances_)[slot_index];
644 const auto before = entrance;
645 const bool properties_editable =
646 CanEditDungeonEntrance(slot_index, entrance);
647 bool changed = false;
648
649 ImGui::Text(tr("Entrance ID: %04X"), entrance.entrance_id_);
650 ImGui::BeginDisabled(!properties_editable);
651 changed |= DrawHexProperty("Room ID", &entrance.room_, "RegularRoomId");
652 changed |= DrawHexProperty("Dungeon ID", &entrance.dungeon_id_,
653 "RegularDungeonId", true);
654 changed |=
655 DrawHexProperty("Blockset", &entrance.blockset_, "RegularBlockset");
656 changed |= DrawHexProperty("Music", &entrance.music_, "RegularMusic", true);
657 changed |= DrawHexProperty("Floor", &entrance.floor_, "RegularFloor", true);
658
659 ImGui::Separator();
660 changed |=
661 DrawHexProperty("Player X ", &entrance.x_position_, "RegularPlayerX");
662 changed |= DrawHexProperty("Player Y ", &entrance.y_position_,
663 "RegularPlayerY", true);
664 changed |= DrawHexProperty("Exit", &entrance.exit_, "RegularExit");
665 ImGui::EndDisabled();
666
667 CommitEntrancePropertyEdit(slot_index, before, changed);
668 DrawCameraSafety(slot_index);
669
670 const auto camera_before = entrance;
671 changed = false;
672 const bool camera_open = ImGui::CollapsingHeader("Advanced camera data");
673 gui::AutoRegisterLastItem("header", "AdvancedCameraData");
674 if (camera_open) {
675 ImGui::BeginDisabled(!properties_editable);
676 changed |=
677 DrawHexProperty("Camera Trigger X", &entrance.camera_trigger_x_,
678 "RegularCameraTriggerX");
679 changed |=
680 DrawHexProperty("Camera Trigger Y", &entrance.camera_trigger_y_,
681 "RegularCameraTriggerY", true);
682 changed |=
683 DrawHexProperty("Scroll X", &entrance.camera_x_, "RegularScrollX");
684 changed |= DrawHexProperty("Scroll Y", &entrance.camera_y_,
685 "RegularScrollY", true);
686 changed |= DrawHexProperty("Scroll Quadrant", &entrance.scroll_quadrant_,
687 "RegularScrollQuadrant");
688
689 ImGui::SeparatorText(tr("Camera Boundaries"));
690 changed |= DrawCameraBoundaries(
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();
697 }
698
699 CommitEntrancePropertyEdit(slot_index, camera_before, changed);
700 }
701
702 void CommitEntrancePropertyEdit(int slot_index,
703 const zelda3::RoomEntrance& before,
704 bool changed) {
705 auto& entrance = (*entrances_)[slot_index];
706 if (!MarkDungeonEntranceDirtyIfEditable(slot_index, entrance, changed)) {
707 return;
708 }
711 entrance_property_edit_callback_(slot_index, before, entrance);
712 }
715 }
716 }
717
718 void CommitSpawnPropertyEdit(int slot_index,
719 const zelda3::DungeonSpawnPoint& before,
720 bool changed) {
721 auto& spawn = (*spawn_points_)[slot_index];
722 if (!MarkDungeonSpawnPointDirtyIfEditable(slot_index, spawn, changed)) {
723 return;
724 }
727 spawn_property_edit_callback_(slot_index, before, spawn);
728 }
731 }
732 }
733
735 void RegisterPropertyInput(const char* field_label, const char* widget_label);
736
737 std::array<zelda3::RoomEntrance, zelda3::kNumDungeonEntranceSlots>*
738 entrances_ = nullptr;
739 std::array<zelda3::DungeonSpawnPoint, zelda3::kNumDungeonSpawnPoints>*
740 spawn_points_ = nullptr;
741 int* current_entrance_id_ = nullptr;
743 std::function<void(int)> on_entrance_selected_;
745 std::function<bool()> preview_automatic_;
746 std::function<void(int)> select_preview_;
752 std::shared_ptr<const int> property_edit_gesture_;
753 std::function<void()> camera_data_changed_callback_;
754 std::optional<DungeonEntranceCameraState> camera_repair_preview_;
759 ImGuiTextFilter entrance_filter_;
760};
761
762} // namespace editor
763} // namespace yaze
764
765#endif // YAZE_APP_EDITOR_DUNGEON_PANELS_DUNGEON_ENTRANCES_PANEL_H_
WindowContent for displaying and editing dungeon entrances.
void SetPropertyEditCallbacks(EntrancePropertyEditCallback entrance_callback, SpawnPropertyEditCallback spawn_callback)
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)
std::optional< DungeonEntranceCameraState > camera_repair_preview_
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)
std::function< std::optional< DungeonEntranceCameraState >(int)> CameraStateProvider
void CommitEntrancePropertyEdit(int slot_index, const zelda3::RoomEntrance &before, bool changed)
bool DrawHexProperty(const char *label, T *value, const char *widget_label, bool continue_row=false)
std::array< zelda3::DungeonSpawnPoint, zelda3::kNumDungeonSpawnPoints > * spawn_points_
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.
std::string GetWorkflowGroup() const override
Optional workflow group for hack-centric actions.
std::array< zelda3::RoomEntrance, zelda3::kNumDungeonEntranceSlots > * entrances_
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_
std::string GetId() const override
Unique identifier for this panel.
std::string GetIcon() const override
Material Design icon for this panel.
std::function< DungeonRenderContext()> preview_context_
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, const zelda3::RoomEntrance &, const zelda3::RoomEntrance &)> EntrancePropertyEditCallback
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.
RAII scope that enables automatic widget registration.
Dungeon Room Entrance or Spawn Point.
#define ICON_MD_SEARCH
Definition icons.h:1673
#define ICON_MD_TUNE
Definition icons.h:2022
#define ICON_MD_DOOR_FRONT
Definition icons.h:613
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)
Definition input.cc:462
bool InputHexByte(const char *label, uint8_t *data, float input_width, bool no_step)
Definition input.cc:483
constexpr int kNumRegularDungeonEntrances
constexpr int kNumDungeonSpawnPoints
constexpr int kNumDungeonEntranceSlots
Modern project structure with comprehensive settings consolidation.
Definition project.h:290
Automatic widget registration helpers for ImGui Test Engine integration.