yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
door_editor_content.cc
Go to the documentation of this file.
2#include "util/i18n/tr.h"
3
4#include <algorithm>
5#include <string>
6
7#include "absl/strings/str_format.h"
12#include "app/gui/core/input.h"
15#include "imgui/imgui.h"
16#include "rom/rom.h"
18#include "zelda3/dungeon/room.h"
19
20namespace yaze::editor {
21
22namespace {
23
24ImVec4 DoorTypeAccent(const AgentUITheme& theme, zelda3::DoorType type) {
25 const int type_val = static_cast<int>(type);
26 if (type_val <= 0x12) {
27 return theme.dungeon_object_door;
28 }
29 if (type_val <= 0x1E) {
30 return theme.status_warning;
31 }
32 return theme.status_success;
33}
34
35// Semantic icon prefix used by both the placement picker and the "Selected
36// Door" swap combo. Keeps supported types scannable by category.
37const char* DoorTypeIcon(zelda3::DoorType type) {
38 switch (type) {
43 return ICON_MD_DOOR_FRONT;
57 return ICON_MD_VIEW_DAY;
59 return ICON_MD_VISIBILITY;
63 return ICON_MD_KEY;
68 return ICON_MD_STAIRS;
70 return ICON_MD_BOLT;
74 return ICON_MD_WHATSHOT;
76 return ICON_MD_WATER_DROP;
78 return ICON_MD_FLAG;
81 return ICON_MD_SWAP_HORIZ;
82 default:
84 }
85}
86
88 switch (type) {
90 return "Normal";
92 return "Lower";
94 return "Exit Low";
96 return "Cave";
98 return "2-Side";
100 return "Eye";
102 return "S-Key";
104 return "B-Key";
106 return "Up";
108 return "Down";
110 return "Up Low";
112 return "Down Low";
114 return "Dash";
116 return "Bomb";
118 return "Bomb Exit";
120 return "B-Key Lock";
122 return "Blast";
124 return "Curtain";
126 return "Bottom";
128 return "Top";
130 return "Exit";
132 return "Exit Low";
134 return "Lit Low";
136 return "Water";
138 return "Marker";
140 return "Layer";
142 return "Swap";
144 return "1-Side";
146 return "2-Side L";
148 return "Explicit";
150 return "Bottom L";
152 return "Top L";
153 }
154 return "Door";
155}
156
157} // namespace
158
165
173
177
178void DoorEditorContent::Draw(bool* /*p_open*/) {
179 DrawContent(true);
180}
181
183 gui::AutoWidgetScope scope("Dungeon/DoorEditor");
185 constexpr const char* categories[] = {"All doors", "Room links", "Exits",
186 "Key doors"};
188 std::vector<std::pair<int, std::string>> entries;
189 for (auto type : zelda3::GetPlaceableDoorTypes()) {
191 continue;
193 continue;
194 if (browser_state_.category == 3 &&
202 continue;
203 const int id = static_cast<int>(type);
204 const std::string name(zelda3::GetDoorTypeName(type));
206 entries.emplace_back(id, name);
207 }
208 if (browser_state_.sort == 1)
209 std::stable_sort(entries.begin(), entries.end(),
210 [](const auto& a, const auto& b) {
211 return workbench::DungeonBrowserFold(a.second) <
212 workbench::DungeonBrowserFold(b.second);
213 });
214 std::array<SDL_Color, 256> colors{};
215 const zelda3::Room* room =
216 rooms_ && current_room_id_ >= 0 &&
217 current_room_id_ < static_cast<int>(rooms_->size())
218 ? &(*rooms_)[current_room_id_]
219 : nullptr;
220 if (room) {
221 const auto& palette = room->rendered_palette();
222 std::copy_n(palette.begin(), std::min(palette.size(), colors.size()),
223 colors.begin());
224 }
225 const bool can_place = CanEditRoom();
226 if (ImGui::BeginChild("##DoorLibrary", ImVec2(0, 0), false)) {
227 ImGui::BeginDisabled(!can_place);
228 for (const auto& [id, name] : entries) {
229 const auto type = static_cast<zelda3::DoorType>(id);
230 ImGui::PushID(id);
231 const bool picked = workbench::DrawDungeonBrowserRow(
232 id, name, selected_door_type_ == type, [&](ImVec2 pos, ImVec2 size) {
233 if (!room)
234 return false;
235 const auto& preview = GetOrCreateDoorPreview(type, *room);
237 ImGui::GetWindowDrawList(), preview.pixels,
238 SDL_Rect{0, 0, preview.width, preview.height}, pos,
239 ImVec2(pos.x + size.x, pos.y + size.y), colors,
241 });
242 gui::AutoRegisterLastItem("selectable",
243 absl::StrFormat("place_door_%02X", id));
244 if (picked && can_place) {
245 selected_door_type_ = type;
247 }
248 ImGui::PopID();
249 }
250 ImGui::EndDisabled();
251 if (entries.empty())
252 ImGui::TextDisabled("No matches");
253 }
254 ImGui::EndChild();
255}
256
258 if (!canvas_viewer_ || !rooms_ || canvas_viewer_->rooms() != rooms_ ||
259 current_room_id_ < 0 ||
260 current_room_id_ >= static_cast<int>(rooms_->size()) ||
262 return false;
263 }
264 const auto* context = canvas_viewer_->object_interaction()
266 .tile_handler()
267 .context();
268 return context && context->current_room_id == current_room_id_;
269}
270
275
278 const zelda3::Room& room) {
280 door_preview_cache_.clear();
282 }
283 const int type_key = static_cast<int>(type);
284 auto it = door_preview_cache_.find(type_key);
285 if (it != door_preview_cache_.end()) {
286 return it->second;
287 }
288
289 CachedDoorPreview cached;
290 Rom* rom = canvas_viewer_ ? canvas_viewer_->rom() : nullptr;
291 const auto& gfx_buffer = room.get_gfx_buffer();
292 if (rom && rom->is_loaded() && !gfx_buffer.empty()) {
293 zelda3::ObjectDrawer drawer(rom, current_room_id_, gfx_buffer.data());
294 gfx::BackgroundBuffer bg1(512, 512);
295 gfx::BackgroundBuffer bg2(512, 512);
298
300 door_def.type = type;
302 door_def.position = 0;
303
304 drawer.DrawDoor(door_def, 0, bg1, bg2, nullptr, nullptr, nullptr);
305
306 auto [tile_x, tile_y] = door_def.GetTileCoords();
307 auto dims = door_def.GetDimensions();
308 const int start_x = tile_x * 8;
309 const int start_y = tile_y * 8;
310 const int w = std::max(1, dims.width_tiles * 8);
311 const int h = std::max(1, dims.height_tiles * 8);
312
313 cached.width = w;
314 cached.height = h;
315 cached.pixels.assign(static_cast<size_t>(w) * static_cast<size_t>(h), 255);
316
317 const auto& bg1_bmp = bg1.bitmap();
318 const auto& bg2_bmp = bg2.bitmap();
319 const auto& bg1_data = bg1_bmp.vector();
320 const auto& bg2_data = bg2_bmp.vector();
321
322 for (int y = 0; y < h; ++y) {
323 for (int x = 0; x < w; ++x) {
324 const int bmp_x = start_x + x;
325 const int bmp_y = start_y + y;
326 if (bmp_x >= 0 && bmp_x < 512 && bmp_y >= 0 && bmp_y < 512) {
327 const int idx = bmp_y * 512 + bmp_x;
328 uint8_t px = 255;
329 if (idx < static_cast<int>(bg1_data.size()) && bg1_data[idx] != 255) {
330 px = bg1_data[idx];
331 } else if (idx < static_cast<int>(bg2_data.size()) &&
332 bg2_data[idx] != 255) {
333 px = bg2_data[idx];
334 }
335 cached.pixels[y * w + x] = px;
336 }
337 }
338 }
339 }
340
341 auto [inserted_it, _] =
342 door_preview_cache_.emplace(type_key, std::move(cached));
343 return inserted_it->second;
344}
345
346void DoorEditorContent::DrawContent(bool show_room_doors) {
348 const bool can_edit = CanEditRoom();
355 .door_handler()
356 .GetDoorType();
357 }
358 gui::AutoWidgetScope automation_scope("Dungeon/DoorEditor");
359
360 const auto& theme = AgentUI::GetTheme();
361 static constexpr auto kDoorTypes = zelda3::GetPlaceableDoorTypes();
362
363 const zelda3::Room* active_room =
364 (rooms_ && current_room_id_ >= 0 &&
365 current_room_id_ < static_cast<int>(rooms_->size()))
367 : nullptr;
368
369 std::array<SDL_Color, 256> color_table{};
370 if (active_room) {
371 const auto& pal = active_room->rendered_palette();
372 for (size_t p_idx = 0; p_idx < std::min<size_t>(pal.size(), 256); ++p_idx) {
373 color_table[p_idx] = pal[p_idx];
374 }
375 }
376
377 if (show_room_doors && HasMatchingViewer() &&
380 EntityType::Door) {
381 gui::SectionHeader(ICON_MD_SELECT_ALL, "Selected Door", theme.text_info);
383 ImGui::Button(ICON_MD_OPEN_IN_NEW " Inspect Selected", ImVec2(-1, 0))) {
385 }
386 ImGui::Spacing();
387 }
388
389 gui::SectionHeader(ICON_MD_DOOR_FRONT, "Door Styles", theme.text_info);
390 ImGui::TextWrapped("Select a door style, then click a wall to place it.");
391
393 ImGui::TextColored(
394 theme.status_warning, ICON_MD_PLACE " Active: %s",
395 std::string(zelda3::GetDoorTypeName(selected_door_type_)).c_str());
396 ImGui::SameLine();
397 if (ImGui::SmallButton(ICON_MD_CANCEL " Cancel")) {
399 }
400 }
401
402 constexpr float kDoorCardHeight = 48.0f;
403 constexpr float kDoorCardSpacing = 6.0f;
404 constexpr float kMinDoorCardWidth = 94.0f;
405
406 const float grid_height =
407 show_room_doors ? 150.0f
408 : std::max(1.0f, ImGui::GetContentRegionAvail().y);
409 ImGui::BeginChild("##DoorTypeGrid", ImVec2(0, grid_height), true,
410 ImGuiWindowFlags_AlwaysVerticalScrollbar);
411 ImGui::BeginDisabled(!can_edit);
412 const float panel_width = ImGui::GetContentRegionAvail().x;
413 const int items_per_row =
414 std::max(1, static_cast<int>((panel_width + kDoorCardSpacing) /
415 (kMinDoorCardWidth + kDoorCardSpacing)));
416 const float card_width = std::max(
417 kMinDoorCardWidth,
418 (panel_width - (items_per_row - 1) * kDoorCardSpacing) / items_per_row);
419
420 ImDrawList* draw_list = ImGui::GetWindowDrawList();
421
422 int col = 0;
423 for (size_t i = 0; i < kDoorTypes.size(); ++i) {
424 const auto door_type = kDoorTypes[i];
425 const bool is_selected = selected_door_type_ == door_type;
426 const int type_val = static_cast<int>(door_type);
427 ImGui::PushID(static_cast<int>(i));
428
429 const ImVec2 card_pos = ImGui::GetCursorScreenPos();
430
431 gui::StyleColorGuard btn_colors({
432 {ImGuiCol_Button,
433 is_selected ? theme.selection_pulsing : theme.panel_bg_darker},
434 });
435
436 const bool picked =
437 ImGui::Button("##door_card", ImVec2(card_width, kDoorCardHeight));
439 absl::StrFormat("place_door_%02X", type_val));
440 if (picked && can_edit) {
441 selected_door_type_ = door_type;
443 if (ResolveCanvasViewer()) {
445 true, selected_door_type_);
446 }
447 }
448
449 const ImVec2 preview_box_min(card_pos.x + 4.0f, card_pos.y + 4.0f);
450 const ImVec2 preview_box_max(card_pos.x + 36.0f,
451 card_pos.y + kDoorCardHeight - 4.0f);
452 bool drew = false;
453 if (active_room) {
454 const auto& preview = GetOrCreateDoorPreview(door_type, *active_room);
455 SDL_Rect bounds{0, 0, preview.width, preview.height};
456 if (bounds.w > 0 && bounds.h > 0 && !preview.pixels.empty()) {
458 draw_list, preview.pixels, bounds, preview_box_min, preview_box_max,
459 color_table,
461 }
462 }
463 if (!drew) {
464 draw_list->AddRect(preview_box_min, preview_box_max,
465 ImGui::GetColorU32(theme.status_inactive), 2.0f);
466 draw_list->AddText(
467 ImVec2(preview_box_min.x + 7.0f, preview_box_min.y + 8.0f),
468 ImGui::GetColorU32(theme.status_inactive), DoorTypeIcon(door_type));
469 }
470
471 const std::string id_text = absl::StrFormat("%02X", type_val);
472 const std::string name_text = ShortDoorTypeLabel(door_type);
473 draw_list->AddText(ImVec2(card_pos.x + 40.0f, card_pos.y + 6.0f),
474 ImGui::GetColorU32(theme.text_info), id_text.c_str());
475 draw_list->AddText(
476 ImVec2(card_pos.x + 40.0f, card_pos.y + 24.0f),
477 ImGui::GetColorU32(is_selected ? theme.text_primary
478 : theme.text_secondary_gray),
479 name_text.c_str());
480
481 if (is_selected) {
482 draw_list->AddRect(
483 card_pos,
484 ImVec2(card_pos.x + card_width, card_pos.y + kDoorCardHeight),
485 ImGui::GetColorU32(theme.selection_primary), 4.0f, 0, 2.0f);
486 }
487
488 if (ImGui::IsItemHovered()) {
489 ImGui::SetTooltip(tr("%s (0x%02X)\nClick to select for placement"),
490 std::string(zelda3::GetDoorTypeName(door_type)).c_str(),
491 type_val);
492 }
493
494 ImGui::PopID();
495 col++;
496 if (col < items_per_row && i < kDoorTypes.size() - 1) {
497 ImGui::SameLine(0, kDoorCardSpacing);
498 } else {
499 col = 0;
500 }
501 }
502
503 ImGui::EndDisabled();
504 ImGui::EndChild();
505
506 if (!show_room_doors || !rooms_ || current_room_id_ < 0 ||
507 current_room_id_ >= static_cast<int>(rooms_->size())) {
508 return;
509 }
510
511 const auto& room = (*rooms_)[current_room_id_];
512 const auto& doors = room.GetDoors();
513
514 ImGui::Spacing();
515 gui::SectionHeader(ICON_MD_LIST, "Doors In Room", theme.text_info);
516 if (doors.empty()) {
517 ImGui::TextColored(theme.text_secondary_gray,
518 ICON_MD_INFO " No doors in this room");
519 return;
520 }
521
522 int selected_door_index = -1;
523 if (HasMatchingViewer() &&
525 const auto selected_entity =
527 if (selected_entity.type == EntityType::Door) {
528 selected_door_index = static_cast<int>(selected_entity.index);
529 }
530 }
531
532 if (selected_door_index >= 0 &&
533 selected_door_index < static_cast<int>(doors.size())) {
534 ImGui::BeginDisabled(!can_edit);
535 uint8_t raw_type = static_cast<uint8_t>(doors[selected_door_index].type);
536 ImGui::AlignTextToFramePadding();
537 ImGui::TextUnformatted(tr("Selected raw type"));
538 ImGui::SameLine();
539 ImGui::SetNextItemWidth(72.0f);
540 const bool type_changed = gui::InputScalarDeferred(
541 "##SelectedDoorRawType", ImGuiDataType_U8, &raw_type, "%02X",
542 ImGuiInputTextFlags_CharsHexadecimal,
543 {reinterpret_cast<uintptr_t>(rooms_),
544 static_cast<uint64_t>(current_room_id_),
545 static_cast<uint64_t>(selected_door_index)});
546 gui::AutoRegisterLastItem("input_scalar", "selected_door_raw_type",
547 "Selected door raw type byte");
548 ImGui::EndDisabled();
549 if (type_changed && can_edit) {
552 .door_handler()
553 .MutateDoorType(selected_door_index,
554 static_cast<zelda3::DoorType>(raw_type));
555 }
556 }
557
558 ImGui::BeginChild("##DoorList", ImVec2(0, 0), true);
559 for (size_t i = 0; i < doors.size(); ++i) {
560 const auto& door = doors[i];
561 const auto [tile_x, tile_y] = door.GetTileCoords();
562 const std::string type_name(zelda3::GetDoorTypeName(door.type));
563 const std::string dir_name(zelda3::GetDoorDirectionName(door.direction));
564
565 ImGui::PushID(static_cast<int>(i));
566 const std::string row_label = absl::StrFormat("[%zu] %s", i, type_name);
567 const bool row_selected = ImGui::Selectable(
568 row_label.c_str(), selected_door_index == static_cast<int>(i));
569 gui::AutoRegisterLastItem("selectable", absl::StrFormat("door_row_%zu", i),
570 "Select a door in the current dungeon room");
571 if (row_selected) {
572 if (HasMatchingViewer()) {
573 canvas_viewer_->object_interaction().SelectEntity(EntityType::Door, i);
574 }
575 }
576 ImGui::SameLine();
577 ImGui::TextColored(theme.text_secondary_gray, "(%s @ %d,%d)",
578 dir_name.c_str(), tile_x, tile_y);
579 ImGui::PopID();
580 }
581 ImGui::EndChild();
582}
583
584} // namespace yaze::editor
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:52
auto data() const
Definition rom.h:213
bool is_loaded() const
Definition rom.h:199
InteractionContext * context() const
Get the interaction context.
workbench::DungeonBrowserState browser_state_
const CachedDoorPreview & GetOrCreateDoorPreview(zelda3::DoorType type, const zelda3::Room &room)
DungeonCanvasViewer * ResolveCanvasViewer()
std::function< DungeonCanvasViewer *()> canvas_viewer_provider_
void OnClose() override
Called when panel is hidden.
void Draw(bool *p_open) override
Draw the panel content.
std::unordered_map< int, CachedDoorPreview > door_preview_cache_
std::function< void()> open_selection_inspector_callback_
void DrawContent(bool show_room_doors)
zelda3::DoorType GetDoorType() const
Get current door type for placement.
bool MutateDoorType(size_t index, zelda3::DoorType new_type)
Change the type of a door in place, re-encoding ROM bytes.
DungeonObjectInteraction & object_interaction()
InteractionCoordinator & entity_coordinator()
Get the interaction coordinator for entity handling.
void SelectEntity(EntityType type, size_t index)
void SetDoorPlacementMode(bool enabled, zelda3::DoorType type=zelda3::DoorType::NormalDoor)
static constexpr uint8_t kBackgroundBufferTransparentIndex
static bool DrawSpritePreview(ImDrawList *draw_list, const std::vector< uint8_t > &preview, const SDL_Rect &bounds, const ImVec2 &box_min, const ImVec2 &box_max, const std::array< SDL_Color, 256 > &color_table, uint8_t transparent_index=kSpritePreviewTransparentIndex)
RAII scope that enables automatic widget registration.
RAII guard for ImGui style colors.
Definition style_guard.h:29
Draws dungeon objects to background buffers using game patterns.
void DrawDoor(const DoorDef &door, int door_index, gfx::BackgroundBuffer &bg1, gfx::BackgroundBuffer &bg2, const DungeonState *state=nullptr, gfx::BackgroundBuffer *layout_bg1=nullptr, gfx::BackgroundBuffer *layout_bg2=nullptr)
Draw a door to background buffers.
const std::array< uint8_t, 0x10000 > & get_gfx_buffer() const
Definition room.h:1120
const std::vector< SDL_Color > & rendered_palette() const
Definition room.h:1138
uint64_t graphics_revision() const
Definition room.h:1125
#define ICON_MD_VIEW_DAY
Definition icons.h:2088
#define ICON_MD_EXIT_TO_APP
Definition icons.h:699
#define ICON_MD_INFO
Definition icons.h:993
#define ICON_MD_CANCEL
Definition icons.h:364
#define ICON_MD_DOOR_SLIDING
Definition icons.h:614
#define ICON_MD_PLACE
Definition icons.h:1477
#define ICON_MD_SWAP_HORIZ
Definition icons.h:1896
#define ICON_MD_STAIRS
Definition icons.h:1847
#define ICON_MD_VISIBILITY
Definition icons.h:2101
#define ICON_MD_WHATSHOT
Definition icons.h:2153
#define ICON_MD_LIST
Definition icons.h:1094
#define ICON_MD_DOOR_FRONT
Definition icons.h:613
#define ICON_MD_BOLT
Definition icons.h:282
#define ICON_MD_FLAG
Definition icons.h:784
#define ICON_MD_SELECT_ALL
Definition icons.h:1680
#define ICON_MD_KEY
Definition icons.h:1026
#define ICON_MD_OPEN_IN_NEW
Definition icons.h:1354
#define ICON_MD_WATER_DROP
Definition icons.h:2131
const AgentUITheme & GetTheme()
ImVec4 DoorTypeAccent(const AgentUITheme &theme, zelda3::DoorType type)
bool DungeonBrowserMatches(std::string_view query, int id, std::string_view name)
void DrawDungeonBrowserControls(DungeonBrowserState &state, std::span< const char *const > categories)
bool DrawDungeonBrowserRow(int id, const std::string &name, bool selected, DrawPreview preview, std::string_view badge={})
Editors are the view controllers for the application.
void AutoRegisterLastItem(const std::string &widget_type, const std::string &explicit_label, const std::string &description)
Automatically register the last ImGui item.
void SectionHeader(const char *icon, const char *label, const ImVec4 &color)
bool InputScalarDeferred(const char *label, ImGuiDataType data_type, void *data, const char *format, ImGuiInputTextFlags flags, InputScalarTargetIdentity target_identity)
Definition input.cc:360
DoorType
Door types from ALTTP.
Definition door_types.h:33
@ NormalDoorOneSidedShutter
Normal door (lower layer; with one-sided shutters)
@ TopShutterLower
Top-sided shutter door (lower layer)
@ FancyDungeonExitLower
Fancy dungeon exit (lower layer)
@ FancyDungeonExit
Fancy dungeon exit.
@ SmallKeyDoor
Small key door.
@ ExitLower
Exit (lower layer)
@ SmallKeyStairsDown
Small key stairs (downwards)
@ BombableCaveExit
Bombable cave exit.
@ SmallKeyStairsUp
Small key stairs (upwards)
@ DungeonSwapMarker
Dungeon swap marker.
@ NormalDoor
Normal door (upper layer)
@ BombableDoor
Bombable door.
@ LayerSwapMarker
Layer swap marker.
@ ExplicitRoomDoor
Explicit room door.
@ BottomShutterLower
Bottom-sided shutter door (lower layer)
@ ExplodingWall
Exploding wall.
@ TopSidedShutter
Top-sided shutter door.
@ LitCaveExitLower
Lit cave exit (lower layer)
@ DoubleSidedShutterLower
Double-sided shutter (lower layer)
@ UnopenableBigKeyDoor
Unopenable, double-sided big key door.
@ NormalDoorLower
Normal door (lower layer)
@ BottomSidedShutter
Bottom-sided shutter door.
@ SmallKeyStairsDownLower
Small key stairs (lower layer; downwards)
@ CurtainDoor
Curtain door.
@ WaterfallDoor
Waterfall door.
@ BigKeyDoor
Big key door.
@ EyeWatchDoor
Eye watch door.
@ SmallKeyStairsUpLower
Small key stairs (lower layer; upwards)
@ ExitMarker
Exit marker.
@ DoubleSidedShutter
Double sided shutter door.
constexpr std::array< DoorType, 32 > GetPlaceableDoorTypes()
Get supported door types suitable for editor placement controls.
Definition door_types.h:364
constexpr bool IsRoomConnectionDoorType(DoorType type)
Return true when a door can represent an adjacent-room connection.
Definition door_types.h:333
constexpr std::string_view GetDoorDirectionName(DoorDirection dir)
Get human-readable name for door direction.
Definition door_types.h:204
constexpr bool IsExitDoorType(DoorType type)
Return true for terminal exits that do not pair with another room.
Definition door_types.h:315
constexpr std::string_view GetDoorTypeName(DoorType type)
Get human-readable name for door type.
Definition door_types.h:110
@ North
Top wall (horizontal door, 4x3 tiles)
Centralized theme colors for Agent UI components.
Definition agent_theme.h:19
std::pair< int, int > GetTileCoords() const
DoorDimensions GetDimensions() const
Automatic widget registration helpers for ImGui Test Engine integration.