7#include <initializer_list>
11#include "absl/status/status.h"
12#include "absl/strings/str_format.h"
28#include "imgui/imgui.h"
39 static constexpr const char* kNames[] = {
40 "Off",
"Parallax",
"Dark",
"On top",
"Translucent",
41 "Addition",
"Normal",
"Transparent",
"Dark room"};
42 constexpr int kNameCount =
sizeof(kNames) /
sizeof(kNames[0]);
43 return (value >= 0 && value < kNameCount) ? kNames[value] :
"Unknown";
47 static constexpr const char* kNames[] = {
"One",
"Both",
"Both + Scroll",
48 "Moving Floor",
"Moving Water"};
49 constexpr int kNameCount =
sizeof(kNames) /
sizeof(kNames[0]);
50 return (value >= 0 && value < kNameCount) ? kNames[value] :
"Unknown";
65 auto* rooms = viewer.
rooms();
66 if (!rooms || room_id < 0 || room_id >=
static_cast<int>(rooms->size()))
69 const bool stack_choices =
70 ImGui::GetContentRegionAvail().x < ImGui::GetFontSize() * 25.0f;
71 const ImGuiID error_id =
72 ImGui::GetID(absl::StrFormat(
"RoomMetadataError/%d", room_id).c_str());
74 auto* inspector_storage = ImGui::GetStateStorage();
76 reinterpret_cast<uintptr_t
>(rooms),
static_cast<uint64_t
>(room_id)};
79 room_id, {.field = field, .value = value, .index = index});
80 inspector_storage->SetBool(error_id, !status.ok());
82 auto begin_properties = [](
const char* id,
float label_width = 104.0f) {
83 if (!ImGui::BeginTable(
84 id, 2, ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchProp)) {
87 ImGui::TableSetupColumn(
"Property", ImGuiTableColumnFlags_WidthFixed,
89 ImGui::TableSetupColumn(
"Value", ImGuiTableColumnFlags_WidthStretch, 1.0f);
92 auto property_row = [](
const char* label) {
94 ImGui::TableNextColumn();
95 ImGui::AlignTextToFramePadding();
96 ImGui::TextUnformatted(label);
97 ImGui::TableNextColumn();
98 ImGui::SetNextItemWidth(-1);
100 auto draw_hex = [&](
const char* label,
const char* id,
int value,
103 auto target = identity;
104 target.element =
static_cast<uint64_t
>(field);
105 target.component =
static_cast<uint64_t
>(index);
107 id, ImGuiDataType_S32, &value, max_value > 0xFF ?
"%03X" :
"%02X",
108 ImGuiInputTextFlags_CharsHexadecimal, target)) {
109 apply(field, value, index);
113 :
"Dungeon/Workbench");
116 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
117 const char* description = label;
119 description =
"Sprite graphics set";
121 description =
"Background 1 floor pattern";
123 description =
"Background 2 floor pattern";
124 ImGui::SetTooltip(
"%s (hex 00-%X). Enter or leave the field to apply.",
125 description, max_value);
128 auto draw_choice = [&](
const char* label,
const char* id,
int value,
131 ImGui::TextUnformatted(label);
132 ImGui::SetNextItemWidth(-1);
136 const std::string preview = name(value);
137 const bool open = ImGui::BeginCombo(
id, preview.c_str());
140 :
"Dungeon/Workbench");
143 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
144 ImGui::SetTooltip(
"%s: %s (0x%02X)", label, preview.c_str(), value);
147 for (
int option = 0; option < count; ++option) {
148 const std::string option_label = name(option);
149 ImGui::PushID(option);
150 if (ImGui::Selectable(option_label.c_str(), value == option)) {
151 apply(field, option);
155 compact ?
"Dungeon/RoomProperties" :
"Dungeon/Workbench");
157 absl::StrFormat(
"%s/%d",
id + 2, option));
159 if (value == option) {
160 ImGui::SetItemDefaultFocus();
162 if (ImGui::IsItemHovered())
163 ImGui::SetTooltip(
"%s (0x%02X)", option_label.c_str(), option);
169 auto choice_group = [&](
const char* id,
float label_width,
auto draw) {
172 }
else if (begin_properties(
id, label_width)) {
177 auto label_width = [](std::initializer_list<const char*> labels) {
179 for (
const auto* label : labels)
180 width = std::max(width, ImGui::CalcTextSize(label).x);
187 if (ImGui::BeginTable(
"##CompactRoomGraphics", 4,
188 ImGuiTableFlags_SizingStretchProp)) {
189 ImGui::TableSetupColumn(
"Property 1", ImGuiTableColumnFlags_WidthFixed,
190 label_width({
"Layout",
"Palette"}));
191 ImGui::TableSetupColumn(
"Value 1", ImGuiTableColumnFlags_WidthStretch);
192 ImGui::TableSetupColumn(
"Property 2", ImGuiTableColumnFlags_WidthFixed,
193 label_width({
"Blockset",
"Spriteset"}));
194 ImGui::TableSetupColumn(
"Value 2", ImGuiTableColumnFlags_WidthStretch);
195 draw_hex(
"Layout",
"##Lyr", room.
layout_id(), 0x07,
197 draw_hex(
"Blockset",
"##BS", room.
blockset(), 0x51,
199 draw_hex(
"Palette",
"##Pal", room.
palette(), 0x47,
201 draw_hex(
"Spriteset",
"##SS", room.
spriteset(),
205 ImGui::EndDisabled();
206 if (inspector_storage->GetBool(error_id))
208 "Edit not applied. Hover the field for its supported range.");
211 choice_group(
"##WorkbenchRoomTags", label_width({
"Tag 1",
"Tag 2"}), [&] {
212 const auto tag_name = [&](
int value) {
215 for (
int index = 0; index < 2; ++index) {
216 const int tag = index == 0 ? room.
tag1() : room.
tag2();
218 index == 0 ?
"Tag 1" :
"Tag 2",
219 index == 0 ?
"##RoomHeaderTag1" :
"##RoomHeaderTag2", tag, 0x40,
225 if (inspector_storage->GetBool(error_id)) {
227 "Edit not applied. Check that this room is editable and the value "
228 "is within the field's supported range. Hover the field for its "
232 ImGui::SeparatorText(
"Room properties");
233 if (ImGui::BeginTable(
234 "##WorkbenchRoomGraphics", 4,
235 ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchProp)) {
236 ImGui::TableSetupColumn(
"Property 1", ImGuiTableColumnFlags_WidthFixed,
237 label_width({
"Layout",
"Floor 1",
"Palette"}));
238 ImGui::TableSetupColumn(
"Value 1", ImGuiTableColumnFlags_WidthStretch,
240 ImGui::TableSetupColumn(
"Property 2", ImGuiTableColumnFlags_WidthFixed,
241 label_width({
"Blockset",
"Floor 2",
"Spriteset"}));
242 ImGui::TableSetupColumn(
"Value 2", ImGuiTableColumnFlags_WidthStretch,
244 draw_hex(
"Layout",
"##RoomHeaderLayout", room.
layout_id(), 0x07,
246 draw_hex(
"Blockset",
"##RoomHeaderBlockset", room.
blockset(), 0x51,
248 draw_hex(
"Floor 1",
"##RoomHeaderFloor1", room.
floor1(), 0x0F,
250 draw_hex(
"Floor 2",
"##RoomHeaderFloor2", room.
floor2(), 0x0F,
252 draw_hex(
"Palette",
"##RoomHeaderPalette", room.
palette(), 0x47,
254 draw_hex(
"Spriteset",
"##RoomHeaderSpriteset", room.
spriteset(),
258 if (begin_properties(
"##WorkbenchRoomMessage", label_width({
"Message"}))) {
259 draw_hex(
"Message",
"##RoomHeaderMessage", room.
message_id(), 0x0FFF,
264 "##WorkbenchRoomHeader",
265 label_width({
"Layer 2 type",
"Layer 2 mode",
"Collision"}), [&] {
266 draw_choice(
"Layer 2 type",
"##RoomHeaderBg2",
269 draw_choice(
"Layer 2 mode",
"##RoomHeaderEffect", room.
effect(), 8,
271 draw_choice(
"Collision",
"##RoomHeaderCollision", room.
collision(), 5,
274 ImGui::EndDisabled();
279 int canvas_x,
int canvas_y) {
281 std::string info_text;
282 if (
object.id_ >= 0x100) {
284 absl::StrFormat(
"0x%03X %s (X:%d Y:%d S:0x%02X)",
object.id_,
285 name.c_str(),
object.x_,
object.y_,
object.size_);
288 absl::StrFormat(
"0x%02X %s (X:%d Y:%d S:0x%02X)",
object.id_,
289 name.c_str(),
object.x_,
object.y_,
object.size_);
296 LOG_DEBUG(
"[LoadAndRender]",
"START room_id=%d", room_id);
299 LOG_DEBUG(
"[LoadAndRender]",
"ERROR: Invalid room ID");
300 return absl::InvalidArgumentError(
"Invalid room ID");
304 LOG_DEBUG(
"[LoadAndRender]",
"ERROR: ROM not loaded");
305 return absl::FailedPreconditionError(
"ROM not loaded");
309 LOG_DEBUG(
"[LoadAndRender]",
"ERROR: Room data not available");
310 return absl::FailedPreconditionError(
"Room data not available");
315 LOG_DEBUG(
"[LoadAndRender]",
"ERROR: Room data unavailable");
316 return absl::InvalidArgumentError(
"Invalid room ID");
318 auto& room = *room_ptr;
319 LOG_DEBUG(
"[LoadAndRender]",
"Got room reference");
322 LOG_ERROR(
"[LoadAndRender]",
"GameData not available");
323 return absl::FailedPreconditionError(
"GameData not available");
326 if (!dungeon_main.empty()) {
328 static_cast<uint64_t
>(room.ResolveDungeonPaletteId());
333 LOG_DEBUG(
"[LoadAndRender]",
"Palette loaded: group_id=%zu",
337 LOG_DEBUG(
"[LoadAndRender]",
"Calling room.RenderRoomGraphics()...");
340 "RenderRoomGraphics() complete - room buffers self-contained");
343 return absl::OkStatus();
349 ImGui::BeginDisabled();
352 constexpr ImGuiTableFlags kPropsTableFlags =
353 ImGuiTableFlags_NoPadOuterX | ImGuiTableFlags_NoBordersInBody;
355 if (ImGui::BeginTable(
"##RoomPropsTable", 2, kPropsTableFlags)) {
356 const float nav_col_width = (ImGui::GetFrameHeight() * 4.0f) +
357 (ImGui::GetStyle().ItemSpacing.x * 3.0f) +
358 (ImGui::GetStyle().FramePadding.x * 2.0f);
359 ImGui::TableSetupColumn(
"NavCol", ImGuiTableColumnFlags_WidthFixed,
361 ImGui::TableSetupColumn(
"PropsCol", ImGuiTableColumnFlags_WidthStretch);
363 ImGui::TableNextRow();
364 ImGui::TableNextColumn();
366 ImGui::TableNextColumn();
370 ImGui::TableNextRow();
371 ImGui::TableNextColumn();
373 ImGui::TableNextColumn();
381 ImGui::EndDisabled();
384 ImGui::CollapsingHeader(
"Destinations##RoomDestinations")) {
394 auto room_if_valid = [](
int candidate) -> std::optional<int> {
410 auto make_tooltip = [&](
const std::optional<int>& target,
411 const char* direction) -> std::string {
412 if (!target.has_value()) {
415 return absl::StrFormat(
416 "%s: [%03X] %s", direction, *target,
420 auto nav_button = [&](
const char* id, ImGuiDir dir,
421 const std::optional<int>& target,
422 const std::string& tooltip) {
423 const bool enabled = target.has_value();
425 ImGui::BeginDisabled();
427 const bool pressed = ImGui::ArrowButton(
id, dir);
429 ImGui::EndDisabled();
431 if (enabled && ImGui::IsItemHovered() && !tooltip.empty()) {
432 ImGui::SetTooltip(
"%s", tooltip.c_str());
434 if (pressed && enabled) {
443 ImGui::PushID(room_id);
445 nav_button(
"##RoomNavWest", ImGuiDir_Left, west, make_tooltip(west,
"West"));
447 nav_button(
"##RoomNavNorth", ImGuiDir_Up, north,
448 make_tooltip(north,
"North"));
450 nav_button(
"##RoomNavSouth", ImGuiDir_Down, south,
451 make_tooltip(south,
"South"));
453 nav_button(
"##RoomNavEast", ImGuiDir_Right, east, make_tooltip(east,
"East"));
460 ImGui::AlignTextToFramePadding();
463 ImGui::SameLine(0, 6);
467 if (ImGui::IsItemHovered()) {
468 ImGui::SetTooltip(tr(
469 "Room changes are pending in the editor. Apply Room writes them to "
470 "the loaded ROM buffer."));
496 ImGui::TextDisabled(tr(
"Floor: %d | Effect: %d | Tag1: %d | Tag2: %d"),
498 if (ImGui::CollapsingHeader(tr(
"Chest contents"))) {
509 ImGui::PushID(
"RecentRooms");
511 if (ImGui::IsItemHovered()) {
512 ImGui::SetTooltip(tr(
"Recently visited rooms"));
517 if (recent_room == room_id) {
520 ImGui::SameLine(0, 3);
521 const std::string button_label =
522 absl::StrFormat(
"%03X##RecentRoom%d", recent_room, rendered);
523 if (ImGui::SmallButton(button_label.c_str())) {
527 if (ImGui::IsItemHovered()) {
529 "[%03X] %s", recent_room,
543 if (ImGui::Button(tr(
"View")))
544 ImGui::OpenPopup(
"##RoomViewOptions");
545 if (ImGui::BeginPopup(
"##RoomViewOptions")) {
556 int current_filter = interaction.GetLayerFilter();
558 auto radio = [&](
const char* label,
int filter) {
559 if (ImGui::RadioButton(label, current_filter == filter)) {
560 interaction.SetLayerFilter(filter);
std::vector< int > recently_visited_rooms_
zelda3::GameData * game_data_
DungeonRoomStore * rooms() const
void DrawRecentRoomBreadcrumbs(int room_id)
absl::Status LoadAndRenderRoomGraphics(int room_id)
void DrawRoomNavigation(int room_id)
absl::Status EditRoomMetadata(int room_id, const RoomMetadataEdit &edit)
bool compact_header_mode_
DungeonObjectInteraction object_interaction_
const project::YazeProject * project_
void DrawCompactLayerToggles(int room_id)
std::function< void(bool)> pin_callback_
void DrawLayerControls(zelda3::Room &room, int room_id)
bool IsObjectInteractionEnabled() const
bool header_read_only() const
zelda3::RoomLayerManager & GetRoomLayerManager(int room_id)
uint8_t current_entrance_blockset_
gfx::PaletteGroup current_palette_group_
bool CanNavigateRooms() const
uint64_t current_palette_group_id_
DungeonRoomStore * rooms_
const project::YazeProject * project() const
void NavigateToRoom(int target_room)
void DrawRoomHeader(zelda3::Room &room, int room_id)
std::function< void(int)> room_navigation_callback_
void DisplayObjectInfo(const gui::CanvasRuntime &rt, const zelda3::RoomObject &object, int canvas_x, int canvas_y)
std::function< void(int, int)> room_swap_callback_
void DrawRoomPropertyTable(zelda3::Room &room, int room_id)
void SetLayersMerged(bool merged)
zelda3::Room * TryEnsureRoom(int room_id)
static constexpr int kLayer3
static constexpr int kLayer1
static constexpr int kLayer2
static constexpr int kLayerAll
static ThemeManager & Get()
uint16_t message_id() const
bool HasUnsavedChanges() const
CollisionKey collision() const
uint8_t spriteset() const
uint8_t layout_id() const
#define ICON_MD_EXPAND_LESS
#define ICON_MD_SELECT_ALL
#define ICON_MD_EXPAND_MORE
#define LOG_DEBUG(category, format,...)
#define LOG_ERROR(category, format,...)
std::string GetLayer2ModeName(int value)
const char * GetCollisionName(int value)
const char * GetBg2ModeName(int value)
std::string GetRoomTagLabel(const project::YazeProject *project, int id)
std::string GetRoomLabel(const project::YazeProject *project, int room_id)
Editors are the view controllers for the application.
void DrawDungeonOverlayControls(DungeonCanvasViewer &viewer)
int NeighborRoomId(int room_id, zelda3::DoorDirection dir)
void DrawDungeonDestinationEditor(DungeonCanvasViewer &viewer)
void DrawDungeonRoomPropertyFields(DungeonCanvasViewer &viewer, zelda3::Room &room, int room_id, RoomPropertyLayout layout)
void DrawDungeonChestEditor(int room_id, zelda3::Room &room, DungeonCanvasViewer &viewer)
void DrawDungeonLayerControls(DungeonCanvasViewer &viewer, int room_id, float height)
bool ThemedIconButton(const char *icon, const char *tooltip, const ImVec2 &size, bool is_active, bool is_disabled, const char *panel_id, const char *anim_id)
Draw a standard icon button with theme-aware colors.
ImVec4 ConvertColorToImVec4(const Color &color)
void AutoRegisterLastItem(const std::string &widget_type, const std::string &explicit_label, const std::string &description)
Automatically register the last ImGui item.
ImVec2 ScaledSize(float base_width, float base_height)
Returns a DPI-scaled version of a fixed pixel size.
void DrawText(const CanvasRuntime &rt, const std::string &text, int x, int y)
bool InputScalarDeferred(const char *label, ImGuiDataType data_type, void *data, const char *format, ImGuiInputTextFlags flags, InputScalarTargetIdentity target_identity)
constexpr int kMaxDungeonSpriteset
gfx::PaletteGroup BuildDungeonRenderPaletteGroupFromGameData(const gfx::SnesPalette &dungeon_palette, const GameData *game_data)
std::string GetObjectName(int object_id)
constexpr int kNumberOfRooms
const std::string RoomEffect[8]
@ South
Bottom wall (horizontal door, 4x3 tiles)
@ North
Top wall (horizontal door, 4x3 tiles)
@ East
Right wall (vertical door, 3x4 tiles)
@ West
Left wall (vertical door, 3x4 tiles)
PaletteGroup dungeon_main
gfx::PaletteGroupMap palette_groups