yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
dungeon_canvas_room_chrome.cc
Go to the documentation of this file.
2#include "util/i18n/tr.h"
3
4#include <optional>
5#include <string>
6
7#include "absl/status/status.h"
8#include "absl/strings/str_format.h"
12#include "app/gui/core/icons.h"
13#include "app/gui/core/input.h"
18#include "imgui/imgui.h"
19#include "util/log.h"
20#include "util/macro.h"
21
22namespace yaze::editor {
23
25 const zelda3::RoomObject& object,
26 int canvas_x, int canvas_y) {
27 std::string name = zelda3::GetObjectName(object.id_);
28 std::string info_text;
29 if (object.id_ >= 0x100) {
30 info_text =
31 absl::StrFormat("0x%03X %s (X:%d Y:%d S:0x%02X)", object.id_,
32 name.c_str(), object.x_, object.y_, object.size_);
33 } else {
34 info_text =
35 absl::StrFormat("0x%02X %s (X:%d Y:%d S:0x%02X)", object.id_,
36 name.c_str(), object.x_, object.y_, object.size_);
37 }
38
39 gui::DrawText(rt, info_text, canvas_x, canvas_y - 12);
40}
41
43 LOG_DEBUG("[LoadAndRender]", "START room_id=%d", room_id);
44
45 if (room_id < 0 || room_id >= zelda3::kNumberOfRooms) {
46 LOG_DEBUG("[LoadAndRender]", "ERROR: Invalid room ID");
47 return absl::InvalidArgumentError("Invalid room ID");
48 }
49
50 if (!rom_ || !rom_->is_loaded()) {
51 LOG_DEBUG("[LoadAndRender]", "ERROR: ROM not loaded");
52 return absl::FailedPreconditionError("ROM not loaded");
53 }
54
55 if (!rooms_) {
56 LOG_DEBUG("[LoadAndRender]", "ERROR: Room data not available");
57 return absl::FailedPreconditionError("Room data not available");
58 }
59
60 auto* room_ptr = rooms_->TryEnsureRoom(room_id);
61 if (!room_ptr) {
62 LOG_DEBUG("[LoadAndRender]", "ERROR: Room data unavailable");
63 return absl::InvalidArgumentError("Invalid room ID");
64 }
65 auto& room = *room_ptr;
66 LOG_DEBUG("[LoadAndRender]", "Got room reference");
67
68 if (!game_data_) {
69 LOG_ERROR("[LoadAndRender]", "GameData not available");
70 return absl::FailedPreconditionError("GameData not available");
71 }
72 const auto& dungeon_main = game_data_->palette_groups.dungeon_main;
73 if (!dungeon_main.empty()) {
75 static_cast<uint64_t>(room.ResolveDungeonPaletteId());
76
77 auto full_palette = dungeon_main[current_palette_group_id_];
80 LOG_DEBUG("[LoadAndRender]", "Palette loaded: group_id=%zu",
82 }
83
84 LOG_DEBUG("[LoadAndRender]", "Calling room.RenderRoomGraphics()...");
85 room.ReloadGraphics(current_entrance_blockset_);
86 LOG_DEBUG("[LoadAndRender]",
87 "RenderRoomGraphics() complete - room buffers self-contained");
88
89 LOG_DEBUG("[LoadAndRender]", "SUCCESS");
90 return absl::OkStatus();
91}
92
94 ImGui::Separator();
96 ImGui::BeginDisabled();
97 }
98
99 constexpr ImGuiTableFlags kPropsTableFlags =
100 ImGuiTableFlags_NoPadOuterX | ImGuiTableFlags_NoBordersInBody;
101
102 if (ImGui::BeginTable("##RoomPropsTable", 2, kPropsTableFlags)) {
103 const float nav_col_width = (ImGui::GetFrameHeight() * 4.0f) +
104 (ImGui::GetStyle().ItemSpacing.x * 3.0f) +
105 (ImGui::GetStyle().FramePadding.x * 2.0f);
106 ImGui::TableSetupColumn("NavCol", ImGuiTableColumnFlags_WidthFixed,
107 nav_col_width);
108 ImGui::TableSetupColumn("PropsCol", ImGuiTableColumnFlags_WidthStretch);
109
110 ImGui::TableNextRow();
111 ImGui::TableNextColumn();
112 DrawRoomNavigation(room_id);
113 ImGui::TableNextColumn();
114 DrawRoomPropertyTable(room, room_id);
115
117 ImGui::TableNextRow();
118 ImGui::TableNextColumn();
119 ImGui::TextDisabled(ICON_MD_SELECT_ALL " Select");
120 ImGui::TableNextColumn();
121 DrawLayerControls(room, room_id);
122 }
123
124 ImGui::EndTable();
125 }
126
127 if (header_read_only_) {
128 ImGui::EndDisabled();
129 }
130}
131
134 return;
135 }
136
137 auto room_if_valid = [](int candidate) -> std::optional<int> {
138 if (candidate < 0 || candidate >= zelda3::kNumberOfRooms) {
139 return std::nullopt;
140 }
141 return candidate;
142 };
143
144 const auto north =
145 room_if_valid(NeighborRoomId(room_id, zelda3::DoorDirection::North));
146 const auto south =
147 room_if_valid(NeighborRoomId(room_id, zelda3::DoorDirection::South));
148 const auto west =
149 room_if_valid(NeighborRoomId(room_id, zelda3::DoorDirection::West));
150 const auto east =
151 room_if_valid(NeighborRoomId(room_id, zelda3::DoorDirection::East));
152
153 auto make_tooltip = [&](const std::optional<int>& target,
154 const char* direction) -> std::string {
155 if (!target.has_value()) {
156 return "";
157 }
158 return absl::StrFormat(
159 "%s: [%03X] %s", direction, *target,
161 };
162
163 auto nav_button = [&](const char* id, ImGuiDir dir,
164 const std::optional<int>& target,
165 const std::string& tooltip) {
166 const bool enabled = target.has_value();
167 if (!enabled) {
168 ImGui::BeginDisabled();
169 }
170 const bool pressed = ImGui::ArrowButton(id, dir);
171 if (!enabled) {
172 ImGui::EndDisabled();
173 }
174 if (enabled && ImGui::IsItemHovered() && !tooltip.empty()) {
175 ImGui::SetTooltip("%s", tooltip.c_str());
176 }
177 if (pressed && enabled) {
179 room_swap_callback_(room_id, *target);
180 } else if (room_navigation_callback_) {
182 }
183 }
184 };
185
186 ImGui::PushID(room_id);
187 ImGui::BeginGroup();
188 nav_button("##RoomNavWest", ImGuiDir_Left, west, make_tooltip(west, "West"));
189 ImGui::SameLine();
190 nav_button("##RoomNavNorth", ImGuiDir_Up, north,
191 make_tooltip(north, "North"));
192 ImGui::SameLine();
193 nav_button("##RoomNavSouth", ImGuiDir_Down, south,
194 make_tooltip(south, "South"));
195 ImGui::SameLine();
196 nav_button("##RoomNavEast", ImGuiDir_Right, east, make_tooltip(east, "East"));
197 ImGui::EndGroup();
198 ImGui::PopID();
199}
200
202 int room_id) {
203 ImGui::AlignTextToFramePadding();
204 ImGui::Text(ICON_MD_TUNE " %03X", room_id);
205 if (room.HasUnsavedChanges()) {
206 ImGui::SameLine(0, 6);
207 ImGui::TextColored(gui::ConvertColorToImVec4(
208 gui::ThemeManager::Get().GetCurrentTheme().warning),
209 ICON_MD_EDIT " Pending");
210 if (ImGui::IsItemHovered()) {
211 ImGui::SetTooltip(tr(
212 "Room changes are pending in the editor. Apply Room writes them to "
213 "the loaded ROM buffer."));
214 }
215 }
216 ImGui::SameLine();
217
218 if (pin_callback_) {
220 is_pinned_ ? "Unpin Room" : "Pin Room",
221 ImVec2(0, 0), is_pinned_)) {
223 }
224 ImGui::SameLine();
225 }
226
229 show_room_details_ ? "Hide Details" : "Show Details")) {
231 }
232 ImGui::SameLine();
234
235 auto hex_input = [&](const char* label, const char* icon, uint8_t* val,
236 uint8_t max, const char* tooltip) {
237 ImGui::TextDisabled("%s", icon);
238 ImGui::SameLine(0, 2);
239
240 const std::string anim_id = std::string(label) + "_Flash";
241 const ImVec4 flash_color = gui::GetAnimator().AnimateColor(
242 "##RoomProps", anim_id, ImVec4(0, 0, 0, 0), 8.0f);
243
244 if (flash_color.w > 0.01f) {
245 ImGui::PushStyleColor(ImGuiCol_FrameBg, flash_color);
246 }
247
248 auto res = gui::InputHexByteEx(label, val, max, 32.f, true);
249 const bool changed = res.ShouldApply();
250
251 if (flash_color.w > 0.01f) {
252 ImGui::PopStyleColor();
253 }
254
255 gui::ValueChangeFlash(changed, anim_id.c_str());
256
257 if (changed) {
258 return true;
259 }
260 if (ImGui::IsItemHovered()) {
261 ImGui::SetTooltip("%s", tooltip);
262 }
263 return false;
264 };
265
266 uint8_t bs = room.blockset();
267 if (hex_input("##BS", ICON_MD_VIEW_MODULE, &bs, 81, "Blockset")) {
268 room.SetBlockset(bs);
269 if (room.rom() && room.rom()->is_loaded()) {
270 room.RenderRoomGraphics();
271 }
272 }
273 ImGui::SameLine(0, 2);
274 ImGui::TextDisabled("(%s)", DungeonRoomSelector::GetBlocksetGroupName(bs));
275 ImGui::SameLine();
276
277 uint8_t pal = room.palette();
278 if (hex_input("##Pal", ICON_MD_PALETTE, &pal, 71, "Palette")) {
279 room.SetPalette(pal);
280 if (room.rom() && room.rom()->is_loaded()) {
281 room.RenderRoomGraphics();
282 }
283 }
284 ImGui::SameLine();
285
286 uint8_t lyr = room.layout_id();
287 if (hex_input("##Lyr", ICON_MD_GRID_VIEW, &lyr, 7, "Layout")) {
288 room.SetLayoutId(lyr);
289 room.MarkLayoutDirty();
290 if (room.rom() && room.rom()->is_loaded()) {
291 room.RenderRoomGraphics();
292 }
293 }
294 ImGui::SameLine();
295
296 uint8_t ss = room.spriteset();
297 if (hex_input("##SS", ICON_MD_PEST_CONTROL, &ss, 143, "Spriteset")) {
298 room.SetSpriteset(ss);
299 if (room.rom() && room.rom()->is_loaded()) {
300 room.RenderRoomGraphics();
301 }
302 }
303
304 if (show_room_details_) {
305 ImGui::TextDisabled(tr("Floor: %d | Effect: %d | Tag1: %d | Tag2: %d"),
306 room.floor1(), room.effect(), room.tag1(), room.tag2());
307 }
308}
309
311 if (!CanNavigateRooms() || recently_visited_rooms_.size() <= 1) {
312 return;
313 }
314
315 ImGui::PushID("RecentRooms");
316 ImGui::TextDisabled(ICON_MD_HISTORY);
317 if (ImGui::IsItemHovered()) {
318 ImGui::SetTooltip(tr("Recently visited rooms"));
319 }
320
321 int rendered = 0;
322 for (int recent_room : recently_visited_rooms_) {
323 if (recent_room == room_id) {
324 continue;
325 }
326 ImGui::SameLine(0, 3);
327 const std::string button_label =
328 absl::StrFormat("%03X##RecentRoom%d", recent_room, rendered);
329 if (ImGui::SmallButton(button_label.c_str())) {
330 NavigateToRoom(recent_room);
332 }
333 if (ImGui::IsItemHovered()) {
334 ImGui::SetTooltip(
335 "[%03X] %s", recent_room,
336 dungeon_project_labels::GetRoomLabel(project_, recent_room).c_str());
337 }
338 ++rendered;
339 }
340 ImGui::PopID();
341 if (rendered > 0) {
342 ImGui::SameLine();
343 }
344}
345
347 if (room_id < 0 || room_id >= zelda3::kNumberOfRooms) {
348 return;
349 }
350
351 const auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
352 const float compact_gap =
353 std::max(2.0f, gui::LayoutHelpers::GetStandardSpacing() * 0.25f);
354 const float compact_padding =
355 std::clamp(gui::LayoutHelpers::GetButtonPadding(), 2.0f, 6.0f);
356
357 gui::StyleVarGuard compact_style({
358 {ImGuiStyleVar_FramePadding,
359 ImVec2(compact_padding, compact_padding * 0.5f)},
360 {ImGuiStyleVar_ItemSpacing, ImVec2(compact_gap, 0.0f)},
361 });
362
363 auto as_button_color = [](ImVec4 color, float alpha) {
364 color.w = alpha;
365 return color;
366 };
367
368 const ImVec4 inactive_color =
369 as_button_color(gui::ConvertColorToImVec4(theme.frame_bg), 0.55f);
370 const ImVec4 inactive_hover =
371 as_button_color(gui::ConvertColorToImVec4(theme.frame_bg_hovered), 0.7f);
372 const ImVec4 inactive_active =
373 as_button_color(gui::ConvertColorToImVec4(theme.frame_bg_active), 0.85f);
374
375 auto draw_toggle = [&](const char* label, bool enabled, ImVec4 active_color,
376 const char* tooltip, auto&& on_toggle) {
377 const ImVec4 button = enabled ? active_color : inactive_color;
378 const ImVec4 hovered =
379 enabled ? as_button_color(
380 gui::ConvertColorToImVec4(theme.button_hovered), 0.95f)
381 : inactive_hover;
382 const ImVec4 pressed =
383 enabled ? as_button_color(
384 gui::ConvertColorToImVec4(theme.button_active), 1.0f)
385 : inactive_active;
386
387 gui::StyleColorGuard button_colors({
388 {ImGuiCol_Button, button},
389 {ImGuiCol_ButtonHovered, hovered},
390 {ImGuiCol_ButtonActive, pressed},
391 });
392
393 if (ImGui::SmallButton(label)) {
394 on_toggle();
395 }
396 if (ImGui::IsItemHovered()) {
397 ImGui::SetTooltip("%s", tooltip);
398 }
399 };
400
401 const bool bg1_visible = IsBG1Visible(room_id);
402 draw_toggle("BG1##LayerToggleBG1", bg1_visible,
403 as_button_color(gui::ConvertColorToImVec4(theme.info), 0.9f),
404 "Toggle BG1 (main layer) visibility",
405 [&]() { SetBG1Visible(room_id, !bg1_visible); });
406
407 ImGui::SameLine();
408 const bool bg2_visible = IsBG2Visible(room_id);
409 draw_toggle("BG2##LayerToggleBG2", bg2_visible,
410 as_button_color(gui::ConvertColorToImVec4(theme.warning), 0.9f),
411 "Toggle BG2 (overlay layer) visibility",
412 [&]() { SetBG2Visible(room_id, !bg2_visible); });
413
414 ImGui::SameLine();
415 const bool sprites_visible = entity_visibility_.show_sprites;
416 draw_toggle(ICON_MD_PEST_CONTROL "##LayerToggleSprites", sprites_visible,
417 as_button_color(gui::ConvertColorToImVec4(theme.success), 0.9f),
418 "Toggle sprite visibility", [&]() {
419 entity_visibility_.show_sprites =
420 !entity_visibility_.show_sprites;
421 });
422
423 ImGui::SameLine();
424 draw_toggle(ICON_MD_GRID_ON "##LayerToggleGrid", show_grid_,
425 as_button_color(gui::ConvertColorToImVec4(theme.secondary), 0.9f),
426 "Toggle grid overlay", [&]() { show_grid_ = !show_grid_; });
427
428 ImGui::SameLine();
429 draw_toggle(
430 ICON_MD_CROP_FREE "##LayerToggleBounds", show_object_bounds_,
431 as_button_color(gui::ConvertColorToImVec4(theme.selection_primary), 0.9f),
432 "Toggle object bounds overlay",
433 [&]() { show_object_bounds_ = !show_object_bounds_; });
434
435 ImGui::SameLine();
436 const bool pots_visible = entity_visibility_.show_pot_items;
437 draw_toggle(ICON_MD_INVENTORY_2 "##LayerTogglePots", pots_visible,
438 as_button_color(gui::ConvertColorToImVec4(theme.success), 0.9f),
439 "Toggle pot item markers", [&]() {
440 entity_visibility_.show_pot_items =
441 !entity_visibility_.show_pot_items;
442 });
443
444 ImGui::SameLine();
445 draw_toggle(ICON_MD_FILTER_CENTER_FOCUS "##LayerToggleCollision",
447 as_button_color(gui::ConvertColorToImVec4(theme.warning), 0.9f),
448 "Toggle custom collision overlay", [&]() {
449 show_custom_collision_overlay_ =
450 !show_custom_collision_overlay_;
451 });
452}
453
455 int room_id) {
456 auto& interaction = object_interaction_;
457
458 interaction.SetLayersMerged(GetRoomLayerManager(room_id).AreLayersMerged());
459 int current_filter = interaction.GetLayerFilter();
460
461 auto radio = [&](const char* label, int filter) {
462 if (ImGui::RadioButton(label, current_filter == filter)) {
463 interaction.SetLayerFilter(filter);
464 }
465 ImGui::SameLine();
466 };
467
468 radio("All", ObjectSelection::kLayerAll);
469 radio("L1", ObjectSelection::kLayer1);
470 radio("L2", ObjectSelection::kLayer2);
471 radio("L3", ObjectSelection::kLayer3);
472}
473
474} // namespace yaze::editor
bool is_loaded() const
Definition rom.h:144
absl::Status LoadAndRenderRoomGraphics(int room_id)
DungeonObjectInteraction object_interaction_
const project::YazeProject * project_
std::function< void(bool)> pin_callback_
void DrawLayerControls(zelda3::Room &room, int room_id)
zelda3::RoomLayerManager & GetRoomLayerManager(int room_id)
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)
static const char * GetBlocksetGroupName(uint8_t blockset)
zelda3::Room * TryEnsureRoom(int room_id)
static constexpr int kLayerAll
ImVec4 AnimateColor(const std::string &panel_id, const std::string &anim_id, ImVec4 target, float speed=5.0f)
Definition animator.cc:64
static float GetButtonPadding()
static float GetStandardSpacing()
RAII guard for ImGui style colors.
Definition style_guard.h:27
RAII guard for ImGui style vars.
Definition style_guard.h:68
const Theme & GetCurrentTheme() const
static ThemeManager & Get()
void MarkLayoutDirty()
Definition room.h:454
uint8_t blockset() const
Definition room.h:902
bool HasUnsavedChanges() const
Definition room.h:588
void SetLayoutId(uint8_t id)
Definition room.h:917
TagKey tag2() const
Definition room.h:889
uint8_t palette() const
Definition room.h:905
auto rom() const
Definition room.h:958
void RenderRoomGraphics()
Definition room.cc:987
TagKey tag1() const
Definition room.h:888
uint8_t spriteset() const
Definition room.h:904
EffectKey effect() const
Definition room.h:887
void SetSpriteset(uint8_t ss)
Definition room.h:689
uint8_t floor1() const
Definition room.h:926
void SetBlockset(uint8_t bs)
Definition room.h:681
void SetPalette(uint8_t pal)
Definition room.h:674
uint8_t layout_id() const
Definition room.h:912
#define ICON_MD_GRID_VIEW
Definition icons.h:897
#define ICON_MD_VIEW_MODULE
Definition icons.h:2093
#define ICON_MD_FILTER_CENTER_FOCUS
Definition icons.h:764
#define ICON_MD_EXPAND_LESS
Definition icons.h:702
#define ICON_MD_TUNE
Definition icons.h:2022
#define ICON_MD_EDIT
Definition icons.h:645
#define ICON_MD_GRID_ON
Definition icons.h:896
#define ICON_MD_PIN
Definition icons.h:1470
#define ICON_MD_PEST_CONTROL
Definition icons.h:1429
#define ICON_MD_SELECT_ALL
Definition icons.h:1680
#define ICON_MD_PALETTE
Definition icons.h:1370
#define ICON_MD_INVENTORY_2
Definition icons.h:1012
#define ICON_MD_PUSH_PIN
Definition icons.h:1529
#define ICON_MD_CROP_FREE
Definition icons.h:495
#define ICON_MD_EXPAND_MORE
Definition icons.h:703
#define ICON_MD_HISTORY
Definition icons.h:946
#define LOG_DEBUG(category, format,...)
Definition log.h:103
#define LOG_ERROR(category, format,...)
Definition log.h:109
#define ASSIGN_OR_RETURN(type_variable_name, expression)
Definition macro.h:62
std::string GetRoomLabel(const project::YazeProject *project, int room_id)
Editors are the view controllers for the application.
int NeighborRoomId(int room_id, zelda3::DoorDirection dir)
absl::StatusOr< PaletteGroup > CreatePaletteGroupFromLargePalette(SnesPalette &palette, int num_colors)
Create a PaletteGroup by dividing a large palette into sub-palettes.
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)
Definition color.h:134
void ValueChangeFlash(bool changed, const char *id)
Provide visual "flash" feedback when a value changes.
void DrawText(const CanvasRuntime &rt, const std::string &text, int x, int y)
Animator & GetAnimator()
Definition animator.cc:318
InputHexResult InputHexByteEx(const char *label, uint8_t *data, float input_width, bool no_step)
Definition input.cc:399
std::string GetObjectName(int object_id)
constexpr int kNumberOfRooms
@ 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)
gfx::PaletteGroupMap palette_groups
Definition game_data.h:92