20using ImGui::BeginChild;
25using ImGui::Selectable;
31 ImVec2 canvas_p0, ImVec2 scrolling,
34 const ImGuiIO& io = ImGui::GetIO();
35 const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y);
36 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
39 float scaled_x = entity.
x_ * scale;
40 float scaled_y = entity.
y_ * scale;
41 float scaled_size = 16.0f * scale;
44 return mouse_pos.x >= scaled_x && mouse_pos.x <= scaled_x + scaled_size &&
45 mouse_pos.y >= scaled_y && mouse_pos.y <= scaled_y + scaled_size;
51 const ImGuiIO& io = ImGui::GetIO();
54 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
57 float scaled_x = entity.
x_ * rt.
scale;
58 float scaled_y = entity.
y_ * rt.
scale;
59 float scaled_size = 16.0f * rt.
scale;
62 return mouse_pos.x >= scaled_x && mouse_pos.x <= scaled_x + scaled_size &&
63 mouse_pos.y >= scaled_y && mouse_pos.y <= scaled_y + scaled_size;
67 ImVec2 scrolling,
bool free_movement,
float scale) {
69 const ImGuiIO& io = ImGui::GetIO();
70 const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y);
71 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
74 float world_x = mouse_pos.x / scale;
75 float world_y = mouse_pos.y / scale;
78 int grid_size = free_movement ? 8 : 16;
79 int new_x =
static_cast<int>(world_x) / grid_size * grid_size;
80 int new_y =
static_cast<int>(world_y) / grid_size * grid_size;
88 bool set_done =
false;
92 if (ImGui::BeginPopup(
"Entrance Inserter")) {
93 static int entrance_id = 0;
94 if (ImGui::IsWindowAppearing()) {
102 ImGui::CloseCurrentPopup();
107 ImGui::CloseCurrentPopup();
116 static bool set_done =
false;
122 if (ImGui::BeginPopupModal(
125 NULL, ImGuiWindowFlags_AlwaysAutoResize)) {
126 if (ImGui::IsWindowAppearing()) {
130 ImGui::Text(tr(
"Entrance ID: %d"), entrance.
entrance_id_);
139 ImGui::Checkbox(tr(
"Is Hole"), &entrance.
is_hole_);
143 if (Button(tr(
"Save"))) {
145 ImGui::CloseCurrentPopup();
148 if (Button(tr(
"Delete"))) {
151 ImGui::CloseCurrentPopup();
154 if (Button(tr(
"Cancel"))) {
155 ImGui::CloseCurrentPopup();
164 if (ImGui::BeginPopup(
"Exit Inserter")) {
165 static int exit_id = 0;
166 static int room_id = 0;
167 static int x_pos = 0;
168 static int y_pos = 0;
170 if (ImGui::IsWindowAppearing()) {
177 ImGui::Text(tr(
"Insert New Exit"));
185 if (Button(tr(
"Create Exit"))) {
188 ImGui::CloseCurrentPopup();
192 if (Button(tr(
"Cancel"))) {
193 ImGui::CloseCurrentPopup();
201 static bool set_done =
false;
206 if (ImGui::BeginPopupModal(
208 NULL, ImGuiWindowFlags_AlwaysAutoResize)) {
210 static int doorType = 0;
212 static int fancyDoorType = 0;
218 static int centerY = 0;
219 static int centerX = 0;
222 static int linkPosture = 0;
223 static int spriteGFX = 0;
224 static int bgGFX = 0;
225 static int palette = 0;
226 static int sprPal = 0;
228 static int bottom = 0;
230 static int right = 0;
231 static int leftEdgeOfMap = 0;
232 static bool special_exit =
false;
233 static bool show_properties =
false;
235 if (ImGui::IsWindowAppearing()) {
257 special_exit =
false;
258 show_properties =
false;
282 Checkbox(tr(
"Show properties"), &show_properties);
283 if (show_properties) {
284 Text(tr(
"Deleted? %s"), exit.
deleted_ ?
"true" :
"false");
285 Text(tr(
"Hole? %s"), exit.
is_hole_ ?
"true" :
"false");
286 Text(tr(
"Auto-calc scroll/camera? %s"),
288 Text(tr(
"Map ID: 0x%02X"), exit.
map_id_);
294 if (ImGui::RadioButton(tr(
"None"), &doorType, 0))
297 if (ImGui::RadioButton(tr(
"Wooden"), &doorType, 1))
300 if (ImGui::RadioButton(tr(
"Bombable"), &doorType, 2))
309 if (ImGui::RadioButton(tr(
"None##Fancy"), &fancyDoorType, 0))
312 if (ImGui::RadioButton(tr(
"Sanctuary"), &fancyDoorType, 1))
315 if (ImGui::RadioButton(tr(
"Palace"), &fancyDoorType, 2))
319 if (fancyDoorType != 0) {
326 Checkbox(tr(
"Special exit"), &special_exit);
350 ImGui::CloseCurrentPopup();
357 ImGui::CloseCurrentPopup();
364 ImGui::CloseCurrentPopup();
375 if (ImGui::BeginPopup(
"Item Inserter")) {
376 static size_t new_item_id = 0;
377 Text(tr(
"Add Item"));
378 BeginChild(
"ScrollRegion", ImVec2(150, 150),
true,
379 ImGuiWindowFlags_AlwaysVerticalScrollbar);
390 ImGui::CloseCurrentPopup();
395 ImGui::CloseCurrentPopup();
403 bool set_done =
false;
404 if (ImGui::BeginPopupModal(
406 NULL, ImGuiWindowFlags_AlwaysAutoResize)) {
407 BeginChild(
"ScrollRegion", ImVec2(150, 150),
true,
408 ImGuiWindowFlags_AlwaysVerticalScrollbar);
422 ImGui::CloseCurrentPopup();
426 ImGui::CloseCurrentPopup();
432 ImGui::CloseCurrentPopup();
444 static ImGuiTextFilter filter;
445 static std::vector<SpriteItem> items;
449 for (
int i = 0; i < 256; ++i) {
454 filter.Draw(
"Filter", 180);
456 if (ImGui::BeginTable(
"##sprites", 2,
457 ImGuiTableFlags_Sortable | ImGuiTableFlags_Resizable)) {
458 ImGui::TableSetupColumn(
"ID", ImGuiTableColumnFlags_DefaultSort, 0.0f,
461 ImGui::TableHeadersRow();
464 if (ImGuiTableSortSpecs* sort_specs = ImGui::TableGetSortSpecs()) {
465 if (sort_specs->SpecsDirty) {
467 sort_specs->SpecsDirty =
false;
472 for (
const auto& item : items) {
473 if (filter.PassFilter(item.name)) {
474 ImGui::TableNextRow();
475 ImGui::TableSetColumnIndex(0);
477 ImGui::TableSetColumnIndex(1);
479 if (Selectable(item.name, selected_id == item.id,
480 ImGuiSelectableFlags_SpanAllColumns)) {
481 selected_id = item.id;
482 onSpriteSelect(item.id);
491 if (ImGui::BeginPopup(
"Sprite Inserter")) {
492 static int new_sprite_id = 0;
493 static int x_pos = 0;
494 static int y_pos = 0;
496 if (ImGui::IsWindowAppearing()) {
502 ImGui::Text(tr(
"Add New Sprite"));
505 BeginChild(
"ScrollRegion", ImVec2(250, 200),
true,
506 ImGuiWindowFlags_AlwaysVerticalScrollbar);
512 ImGui::Text(tr(
"Position:"));
516 if (Button(tr(
"Add Sprite"))) {
522 ImGui::CloseCurrentPopup();
526 if (Button(tr(
"Cancel"))) {
527 ImGui::CloseCurrentPopup();
535 static bool set_done =
false;
540 if (ImGui::BeginPopupModal(
543 NULL, ImGuiWindowFlags_AlwaysAutoResize)) {
544 static int selected_id = 0;
545 if (ImGui::IsWindowAppearing()) {
546 selected_id = sprite.
id();
549 BeginChild(
"ScrollRegion", ImVec2(350, 350),
true,
550 ImGuiWindowFlags_AlwaysVerticalScrollbar);
552 Text(
"%s", sprite.
name().c_str());
565 ImGui::CloseCurrentPopup();
570 ImGui::CloseCurrentPopup();
576 ImGui::CloseCurrentPopup();
586 const std::vector<gfx::Tile16>& tiles16,
587 const std::array<uint8_t, 0x200>& all_tiles_types) {
588 static bool set_done =
false;
595 "Diggable Tiles Editor")
597 NULL, ImGuiWindowFlags_AlwaysAutoResize)) {
598 static ImGuiTextFilter filter;
599 static int patch_mode = 0;
601 static std::string export_message;
605 Text(tr(
"Diggable Tiles: %d / 512"), diggable_count);
609 filter.Draw(
"Filter by Tile ID", 200);
611 if (Button(tr(
"Clear Filter"))) {
616 BeginChild(
"TileList", ImVec2(400, 300),
true,
617 ImGuiWindowFlags_AlwaysVerticalScrollbar);
622 for (uint16_t tile_id = 0;
626 snprintf(id_str,
sizeof(id_str),
"$%03X", tile_id);
628 if (!filter.PassFilter(id_str)) {
632 bool is_diggable = diggable_tiles->
IsDiggable(tile_id);
634 tiles16[tile_id], all_tiles_types);
637 std::optional<gui::StyleColorGuard> dig_color;
639 if (would_be_diggable) {
640 dig_color.emplace(ImGuiCol_Text, ImVec4(0.2f, 0.8f, 0.2f, 1.0f));
642 dig_color.emplace(ImGuiCol_Text, ImVec4(0.8f, 0.8f, 0.2f, 1.0f));
646 if (Checkbox(id_str, &is_diggable)) {
652 if (ImGui::IsItemHovered()) {
653 ImGui::SetTooltip(tr(
"Tile $%03X - %s"), tile_id,
654 would_be_diggable ?
"Auto-detected as diggable"
655 :
"Manually configured");
672 diggable_tiles->
Clear();
673 for (uint16_t tile_id = 0;
682 if (ImGui::IsItemHovered()) {
684 tr(
"Set diggable status based on tile types.\n"
685 "A tile is diggable if all 4 component tiles\n"
686 "have type 0x48 or 0x4A (diggable ground)."));
693 if (ImGui::IsItemHovered()) {
695 tr(
"Reset to vanilla diggable tiles:\n$034, $035, $071, "
696 "$0DA, $0E1, $0E2, $0F8, $10D, $10E, $10F"));
701 diggable_tiles->
Clear();
707 if (ImGui::CollapsingHeader(tr(
"ASM Patch Export"))) {
710 Text(tr(
"Patch Mode:"));
711 ImGui::RadioButton(tr(
"Vanilla"), &patch_mode, 0);
713 ImGui::RadioButton(tr(
"ZS Compatible"), &patch_mode, 1);
715 ImGui::RadioButton(tr(
"Custom"), &patch_mode, 2);
719 if (patch_mode == 2) {
736 *diggable_tiles, patch_config);
737 const std::string out_path =
"diggable_tiles_patch.asm";
738 std::ofstream out(out_path, std::ios::trunc);
740 out << patch_content;
742 export_message =
"Exported patch to " + out_path;
744 export_message =
"Failed to write " + out_path;
747 if (!export_message.empty()) {
748 ImGui::TextWrapped(
"%s", export_message.c_str());
759 ImGui::CloseCurrentPopup();
763 ImGui::CloseCurrentPopup();
static std::string GeneratePatch(const DiggableTiles &diggable_tiles, const DiggableTilesPatchConfig &config={})
Generate ASM patch code for the diggable tiles table.
Manages diggable tile state as a 512-bit bitfield.
int GetDiggableCount() const
Get the count of tiles marked as diggable.
void SetVanillaDefaults()
Reset to vanilla diggable tiles.
void SetDiggable(uint16_t tile_id, bool diggable)
Set or clear the diggable bit for a Map16 tile ID.
static bool IsTile16Diggable(const gfx::Tile16 &tile16, const std::array< uint8_t, 0x200 > &all_tiles_types)
Check if a Tile16 should be diggable based on its component tiles.
bool IsDiggable(uint16_t tile_id) const
Check if a Map16 tile ID is marked as diggable.
void Clear()
Clear all diggable bits.
Base class for all overworld and dungeon entities.
Represents an overworld exit that transitions from dungeon to overworld.
void UpdateMapProperties(uint16_t room_map_id, const void *context=nullptr) override
Update entity properties based on map position.
A class for managing sprites in the overworld and underworld.
void UpdateMapProperties(uint16_t map_id, const void *context=nullptr) override
Update entity properties based on map position.
auto set_deleted(bool deleted)
#define ICON_MD_FILE_DOWNLOAD
#define ICON_MD_AUTO_FIX_HIGH
#define ICON_MD_RESTART_ALT
void DrawExitInserterPopup()
bool DrawSpriteEditorPopup(zelda3::Sprite &sprite)
void DrawItemInsertPopup()
bool DrawEntranceInserterPopup()
void DrawSpriteInserterPopup()
void DrawSpriteTable(std::function< void(int)> onSpriteSelect, int &selected_id)
bool DrawOverworldEntrancePopup(zelda3::OverworldEntrance &entrance)
bool DrawItemEditorPopup(zelda3::OverworldItem &item)
bool DrawDiggableTilesEditorPopup(zelda3::DiggableTiles *diggable_tiles, const std::vector< gfx::Tile16 > &tiles16, const std::array< uint8_t, 0x200 > &all_tiles_types)
Draw popup dialog for editing diggable tiles configuration.
void MoveEntityOnGrid(zelda3::GameEntity *entity, ImVec2 canvas_p0, ImVec2 scrolling, bool free_movement, float scale)
Move entity to grid-aligned position based on mouse.
constexpr float kInputFieldSize
@ SpriteItemColumnID_Name
bool DrawExitEditorPopup(zelda3::OverworldExit &exit)
bool IsMouseHoveringOverEntity(const zelda3::GameEntity &entity, ImVec2 canvas_p0, ImVec2 scrolling, float scale)
Check if mouse is hovering over an entity.
constexpr const char * kOverworld
bool InputHexWord(const char *label, uint16_t *data, float input_width, bool no_step)
bool InputHex(const char *label, uint64_t *data)
std::string MakePopupId(size_t session_id, const std::string &editor_name, const std::string &popup_name)
Generate session-aware popup IDs to prevent conflicts in multi-editor layouts.
bool InputHexByte(const char *label, uint8_t *data, float input_width, bool no_step)
void TextWithSeparators(const absl::string_view &text)
const std::vector< std::string > kSecretItemNames
const std::string kSpriteDefaultNames[256]
constexpr int kMaxDiggableTileId
static const ImGuiTableSortSpecs * s_current_sort_specs
static void SortWithSortSpecs(ImGuiTableSortSpecs *sort_specs, std::vector< SpriteItem > &items)
Configuration for diggable tiles ASM patch generation.
uint32_t freespace_address
bool use_zs_compatible_mode