7#include "absl/strings/str_format.h"
17#include "imgui/imgui.h"
32 panel_manager->
RegisterPanel({.card_id =
"screen.dungeon_maps",
33 .display_name =
"Dungeon Maps",
34 .window_title =
" Dungeon Map Editor",
37 .shortcut_hint =
"Alt+1",
39 .enabled_condition = [
this]() {
return rom()->
is_loaded(); },
40 .disabled_tooltip =
"Load a ROM first"});
41 panel_manager->RegisterPanel({.card_id =
"screen.inventory_menu",
42 .display_name =
"Inventory Menu",
43 .window_title =
" Inventory Menu",
46 .shortcut_hint =
"Alt+2",
48 .enabled_condition = [
this]() {
return rom()->
is_loaded(); },
49 .disabled_tooltip =
"Load a ROM first"});
50 panel_manager->RegisterPanel({.card_id =
"screen.overworld_map",
51 .display_name =
"Overworld Map",
52 .window_title =
" Overworld Map",
55 .shortcut_hint =
"Alt+3",
57 .enabled_condition = [
this]() {
return rom()->
is_loaded(); },
58 .disabled_tooltip =
"Load a ROM first"});
59 panel_manager->RegisterPanel({.card_id =
"screen.title_screen",
60 .display_name =
"Title Screen",
61 .window_title =
" Title Screen",
64 .shortcut_hint =
"Alt+4",
66 .enabled_condition = [
this]() {
return rom()->
is_loaded(); },
67 .disabled_tooltip =
"Load a ROM first"});
68 panel_manager->RegisterPanel({.card_id =
"screen.naming_screen",
69 .display_name =
"Naming Screen",
70 .window_title =
" Naming Screen",
73 .shortcut_hint =
"Alt+5",
75 .enabled_condition = [
this]() {
return rom()->
is_loaded(); },
76 .disabled_tooltip =
"Load a ROM first"});
79 panel_manager->RegisterEditorPanel(std::make_unique<DungeonMapsPanel>(
81 panel_manager->RegisterEditorPanel(std::make_unique<InventoryMenuPanel>(
83 panel_manager->RegisterEditorPanel(std::make_unique<OverworldMapScreenPanel>(
85 panel_manager->RegisterEditorPanel(std::make_unique<TitleScreenPanel>(
87 panel_manager->RegisterEditorPanel(std::make_unique<NamingScreenPanel>(
91 panel_manager->ShowPanel(
"screen.title_screen");
110 for (
int i = 0; i < 4; i++) {
111 sheets_[i]->SetPalette(*
game_data()->palette_groups.dungeon_main.mutable_palette(3));
119 const int tile8_width = 128;
120 const int tile8_height = 128;
121 std::vector<uint8_t> tile8_data(tile8_width * tile8_height);
124 for (
int sheet_idx = 0; sheet_idx < 4; sheet_idx++) {
125 const auto& sheet = *
sheets_[sheet_idx];
126 int dest_y_offset = sheet_idx * 32;
128 for (
int y = 0; y < 32; y++) {
129 for (
int x = 0; x < 128; x++) {
130 int src_index = y * 128 + x;
131 int dest_index = (dest_y_offset + y) * 128 + x;
133 if (src_index < sheet.size() && dest_index < tile8_data.size()) {
134 tile8_data[dest_index] = sheet.data()[src_index];
149 return absl::OkStatus();
165 static bool create =
false;
172 ImGui::TextColored(ImVec4(1, 0, 0, 1),
"Error loading inventory: %s",
180 if (ImGui::BeginTable(
"InventoryScreen", 4, ImGuiTableFlags_Resizable)) {
181 ImGui::TableSetupColumn(
"Canvas");
182 ImGui::TableSetupColumn(
"Tilesheet");
183 ImGui::TableSetupColumn(
"Item Icons");
184 ImGui::TableSetupColumn(
"Palette");
185 ImGui::TableHeadersRow();
187 ImGui::TableNextColumn();
198 ImGui::TableNextColumn();
201 frame_opts.
canvas_size = ImVec2(128 * 2 + 2, (192 * 2) + 4);
210 ImGui::TableNextColumn();
213 ImGui::TableNextColumn();
229 if (ImGui::BeginTable(
"InventoryToolset", 8, ImGuiTableFlags_SizingFixedFit,
231 ImGui::TableSetupColumn(
"#drawTool");
232 ImGui::TableSetupColumn(
"#sep1");
233 ImGui::TableSetupColumn(
"#zoomOut");
234 ImGui::TableSetupColumn(
"#zoomIN");
235 ImGui::TableSetupColumn(
"#sep2");
236 ImGui::TableSetupColumn(
"#bg2Tool");
237 ImGui::TableSetupColumn(
"#bg3Tool");
238 ImGui::TableSetupColumn(
"#itemTool");
240 ImGui::TableNextColumn();
244 ImGui::TableNextColumn();
248 ImGui::TableNextColumn();
250 ImGui::TableNextColumn();
254 ImGui::TableNextColumn();
258 ImGui::TableNextColumn();
260 ImGui::TableNextColumn();
264 ImGui::TableNextColumn();
274 if (ImGui::BeginChild(
"##ItemIconsList", ImVec2(0, 0),
true,
275 ImGuiWindowFlags_HorizontalScrollbar)) {
276 ImGui::Text(
"Item Icons (2x2 tiles each)");
282 "No item icons loaded. Icons will be loaded when the "
283 "inventory is initialized.");
289 if (ImGui::BeginTable(
"##IconsTable", 2,
290 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) {
291 ImGui::TableSetupColumn(
"Icon Name");
292 ImGui::TableSetupColumn(
"Tile Data");
293 ImGui::TableHeadersRow();
295 for (
size_t i = 0; i < icons.size(); i++) {
296 const auto& icon = icons[i];
298 ImGui::TableNextRow();
299 ImGui::TableNextColumn();
302 if (ImGui::Selectable(icon.name.c_str(),
false,
303 ImGuiSelectableFlags_SpanAllColumns)) {
307 ImGui::TableNextColumn();
309 ImGui::Text(
"TL:%04X TR:%04X", icon.tile_tl, icon.tile_tr);
311 ImGui::Text(
"BL:%04X BR:%04X", icon.tile_bl, icon.tile_br);
319 "NOTE: Individual icon editing will be implemented in the future "
320 "Oracle of Secrets menu editor. Each icon is composed of 4 tile words "
321 "representing a 2x2 arrangement of 8x8 tiles in SNES tile format "
322 "(vhopppcc cccccccc).");
336 auto boss_room = current_dungeon.boss_room;
339 std::vector<int> tile_ids_to_render;
340 std::vector<ImVec2> tile_positions;
345 if (current_dungeon.floor_rooms[
floor_number][j] != 0x0F) {
346 int tile16_id = current_dungeon.floor_gfx[
floor_number][j];
347 int posX = ((j % 5) * 32);
348 int posY = ((j / 5) * 32);
351 tile_ids_to_render.push_back(tile16_id);
352 tile_positions.emplace_back(posX * 2, posY * 2);
357 for (
size_t idx = 0; idx < tile_ids_to_render.size(); ++idx) {
358 int tile16_id = tile_ids_to_render[idx];
359 ImVec2 pos = tile_positions[idx];
363 const int tile_x = (tile16_id % tiles_per_row) * 16;
364 const int tile_y = (tile16_id / tiles_per_row) * 16;
366 std::vector<uint8_t> tile_data(16 * 16);
367 int tile_data_offset = 0;
384 if (cached_tile && cached_tile->is_active()) {
386 if (!cached_tile->texture()) {
397 if (current_dungeon.floor_rooms[
floor_number][j] != 0x0F) {
398 int posX = ((j % 5) * 32);
399 int posY = ((j / 5) * 32);
401 if (current_dungeon.floor_rooms[
floor_number][j] == boss_room) {
427 if (ImGui::BeginTabBar(
"##DungeonMapTabs")) {
429 current_dungeon.nbr_of_floor + current_dungeon.nbr_of_basement;
430 for (
int i = 0; i < nbr_floors; i++) {
431 int basement_num = current_dungeon.nbr_of_basement - i;
432 std::string tab_name = absl::StrFormat(
"Basement %d", basement_num);
433 if (i >= current_dungeon.nbr_of_basement) {
434 tab_name = absl::StrFormat(
"Floor %d",
435 i - current_dungeon.nbr_of_basement + 1);
437 if (ImGui::BeginTabItem(tab_name.data())) {
451 const auto button_size = ImVec2(130, 0);
453 if (ImGui::Button(
"Add Floor", button_size) &&
454 current_dungeon.nbr_of_floor < 8) {
455 current_dungeon.nbr_of_floor++;
459 if (ImGui::Button(
"Remove Floor", button_size) &&
460 current_dungeon.nbr_of_floor > 0) {
461 current_dungeon.nbr_of_floor--;
465 if (ImGui::Button(
"Add Basement", button_size) &&
466 current_dungeon.nbr_of_basement < 8) {
467 current_dungeon.nbr_of_basement++;
471 if (ImGui::Button(
"Remove Basement", button_size) &&
472 current_dungeon.nbr_of_basement > 0) {
473 current_dungeon.nbr_of_basement--;
477 if (ImGui::Button(
"Copy Floor", button_size)) {
481 if (ImGui::Button(
"Paste Floor", button_size)) {
505 if (ImGui::BeginChild(
"##DungeonMapTiles", ImVec2(0, 0),
true)) {
509 tilesheet_opts.
canvas_size = ImVec2((256 * 2) + 2, (192 * 2) + 4);
524 ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
559 auto current_tile_rt =
568 const int tiles_per_row =
574 std::vector<uint8_t> tile_data(64);
575 for (
int py = 0; py < 8; py++) {
576 for (
int px = 0; px < 8; px++) {
577 int src_x = tile_x + px;
578 int src_y = tile_y + py;
580 int dst_index = py * 8 + px;
591 std::move(new_tile8));
595 if (cached_tile8 && cached_tile8->is_active()) {
597 if (!cached_tile8->texture()) {
616 auto* selected_tile =
618 if (selected_tile && selected_tile->is_active()) {
620 if (!selected_tile->texture()) {
637 if (ImGui::Button(
"Modify Tile16")) {
678 static std::vector<std::string> dungeon_names = {
679 "Sewers/Sanctuary",
"Hyrule Castle",
"Eastern Palace",
680 "Desert Palace",
"Tower of Hera",
"Agahnim's Tower",
681 "Palace of Darkness",
"Swamp Palace",
"Skull Woods",
682 "Thieves' Town",
"Ice Palace",
"Misery Mire",
683 "Turtle Rock",
"Ganon's Tower"};
685 if (ImGui::BeginTable(
"DungeonMapsTable", 4,
686 ImGuiTableFlags_Resizable |
687 ImGuiTableFlags_Reorderable |
688 ImGuiTableFlags_Hideable)) {
689 ImGui::TableSetupColumn(
"Dungeon");
690 ImGui::TableSetupColumn(
"Map");
691 ImGui::TableSetupColumn(
"Rooms Gfx");
692 ImGui::TableSetupColumn(
"Tiles Gfx");
693 ImGui::TableHeadersRow();
695 ImGui::TableNextColumn();
696 for (
int i = 0; i < dungeon_names.size(); i++) {
700 if (ImGui::IsItemClicked()) {
705 ImGui::TableNextColumn();
708 ImGui::TableNextColumn();
711 ImGui::TableNextColumn();
724 ImGui::Text(
"For use with custom inserted graphics assembly patches.");
725 if (ImGui::Button(
"Load GFX from BIN file"))
734 if (!bin_file.empty()) {
735 std::ifstream file(bin_file, std::ios::binary);
736 if (file.is_open()) {
738 std::vector<uint8_t> bin_data((std::istreambuf_iterator<char>(file)),
739 std::istreambuf_iterator<char>());
745 std::vector<std::vector<uint8_t>> gfx_sheets;
746 for (
int i = 0; i < 4; i++) {
747 gfx_sheets.emplace_back(converted_bin.begin() + (i * 0x1000),
748 converted_bin.begin() + ((i + 1) * 0x1000));
749 sheets_[i] = std::make_unique<gfx::Bitmap>(128, 32, 8, gfx_sheets[i]);
750 sheets_[i]->SetPalette(*
game_data()->palette_groups.dungeon_main.mutable_palette(3));
757 status_ = absl::InternalError(
"Failed to load dungeon map tile16");
769 ImGui::TextColored(ImVec4(1, 0, 0, 1),
"Error loading title screen: %s",
777 ImGui::Text(
"Title screen not loaded. Ensure ROM is loaded.");
789 ImGui::OpenPopup(
"SaveSuccess");
796 if (ImGui::BeginPopup(
"SaveSuccess")) {
797 ImGui::Text(
"Title screen saved successfully!");
820 if (ImGui::BeginTable(
"TitleScreenTable", 2,
821 ImGuiTableFlags_Resizable | ImGuiTableFlags_Borders)) {
822 ImGui::TableSetupColumn(
"Title Screen (Composite)");
823 ImGui::TableSetupColumn(
"Tile Selector");
824 ImGui::TableHeadersRow();
827 ImGui::TableNextColumn();
831 ImGui::TableNextColumn();
844 if (composite_bitmap.is_active()) {
853 int tile_x =
static_cast<int>(click_pos.x) / 8;
854 int tile_y =
static_cast<int>(click_pos.y) / 8;
856 if (tile_x >= 0 && tile_x < 32 && tile_y >= 0 && tile_y < 32) {
857 int tilemap_index = tile_y * 32 + tile_x;
899 if (bg1_bitmap.is_active()) {
908 int tile_x =
static_cast<int>(click_pos.x) / 8;
909 int tile_y =
static_cast<int>(click_pos.y) / 8;
911 if (tile_x >= 0 && tile_x < 32 && tile_y >= 0 && tile_y < 32) {
912 int tilemap_index = tile_y * 32 + tile_x;
944 if (bg2_bitmap.is_active()) {
953 int tile_x =
static_cast<int>(click_pos.x) / 8;
954 int tile_y =
static_cast<int>(click_pos.y) / 8;
956 if (tile_x >= 0 && tile_x < 32 && tile_y >= 0 && tile_y < 32) {
957 int tilemap_index = tile_y * 32 + tile_x;
989 if (tiles8_bitmap.is_active()) {
998 int tile_x =
static_cast<int>(click_pos.x) / 8;
999 int tile_y =
static_cast<int>(click_pos.y) / 8;
1000 int tiles_per_row = 128 / 8;
1018 ImGui::SetNextItemWidth(100);
1030 ImGui::TextColored(ImVec4(1, 0, 0, 1),
"Error loading overworld map: %s",
1038 ImGui::Text(
"Overworld map not loaded. Ensure ROM is loaded.");
1050 ImGui::OpenPopup(
"OWSaveSuccess");
1068 if (ImGui::Button(
"Load Custom Map...")) {
1070 if (!path.empty()) {
1073 ImGui::OpenPopup(
"CustomMapLoadError");
1078 if (ImGui::Button(
"Save Custom Map...")) {
1080 if (!path.empty()) {
1083 ImGui::OpenPopup(
"CustomMapSaveSuccess");
1092 if (ImGui::BeginPopup(
"CustomMapLoadError")) {
1093 ImGui::Text(
"Error loading custom map: %s",
status_.message().data());
1096 if (ImGui::BeginPopup(
"CustomMapSaveSuccess")) {
1097 ImGui::Text(
"Custom map saved successfully!");
1102 if (ImGui::BeginPopup(
"OWSaveSuccess")) {
1103 ImGui::Text(
"Overworld map saved successfully!");
1108 if (ImGui::BeginTable(
"OWMapTable", 3,
1109 ImGuiTableFlags_Resizable | ImGuiTableFlags_Borders)) {
1110 ImGui::TableSetupColumn(
"Map Canvas");
1111 ImGui::TableSetupColumn(
"Tileset");
1112 ImGui::TableSetupColumn(
"Palette");
1113 ImGui::TableHeadersRow();
1116 ImGui::TableNextColumn();
1121 if (map_bitmap.is_active()) {
1130 int tile_x =
static_cast<int>(click_pos.x) / 8;
1131 int tile_y =
static_cast<int>(click_pos.y) / 8;
1133 if (tile_x >= 0 && tile_x < 64 && tile_y >= 0 && tile_y < 64) {
1134 int tile_index = tile_x + (tile_y * 64);
1158 ImGui::TableNextColumn();
1163 if (tiles8_bitmap.is_active()) {
1171 int tile_x =
static_cast<int>(click_pos.x) / 8;
1172 int tile_y =
static_cast<int>(click_pos.y) / 8;
1181 ImGui::TableNextColumn();
1192 static bool show_bg1 =
true;
1193 static bool show_bg2 =
true;
1194 static bool show_bg3 =
true;
1196 static bool drawing_bg1 =
true;
1197 static bool drawing_bg2 =
false;
1198 static bool drawing_bg3 =
false;
1200 ImGui::Checkbox(
"Show BG1", &show_bg1);
1202 ImGui::Checkbox(
"Show BG2", &show_bg2);
1204 ImGui::Checkbox(
"Draw BG1", &drawing_bg1);
1206 ImGui::Checkbox(
"Draw BG2", &drawing_bg2);
1208 ImGui::Checkbox(
"Draw BG3", &drawing_bg3);
project::ResourceLabelManager * resource_label()
zelda3::GameData * game_data() const
EditorDependencies dependencies_
void RegisterPanel(size_t session_id, const PanelDescriptor &base_info)
void DrawTitleScreenEditor()
void DrawDungeonMapsRoomGfx()
Draw dungeon room graphics editor with enhanced tile16 editing.
gfx::SnesPalette palette_
void DrawTitleScreenBlocksetSelector()
bool title_screen_loaded_
gui::Canvas ow_map_canvas_
gfx::Tilemap tile16_blockset_
void DrawTitleScreenBG2Canvas()
gui::Canvas tilemap_canvas_
void DrawInventoryItemIcons()
std::array< gfx::TileInfo, 4 > current_tile16_info
absl::Status Load() override
absl::Status Update() override
gui::Canvas screen_canvas_
void DrawTitleScreenBG1Canvas()
gui::Canvas title_bg1_canvas_
zelda3::OverworldMapScreen ow_map_screen_
void Initialize() override
int selected_title_tile16_
gui::Canvas current_tile_canvas_
void DrawDungeonMapsEditor()
Draw dungeon maps editor with enhanced ROM hacking features.
gui::Canvas title_bg2_canvas_
void DrawInventoryToolset()
gfx::Tilemap tile8_tilemap_
void DrawTitleScreenCompositeCanvas()
zelda3::TitleScreen title_screen_
void DrawOverworldMapEditor()
zelda3::Inventory inventory_
EditingMode current_mode_
zelda3::DungeonMapLabels dungeon_map_labels_
bool paste_button_pressed
gui::Canvas ow_tileset_canvas_
gui::Canvas title_blockset_canvas_
void DrawDungeonMapToolset()
gui::Canvas tilesheet_canvas_
void DrawNamingScreenEditor()
void DrawDungeonMapScreen(int i)
void DrawDungeonMapsTabs()
std::vector< zelda3::DungeonMap > dungeon_maps_
void DrawInventoryMenuEditor()
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
Represents a bitmap image optimized for SNES ROM hacking.
const uint8_t * data() const
const SnesPalette & palette() const
void Create(int width, int height, int depth, std::span< uint8_t > data)
Create a bitmap with the given dimensions and data.
void set_data(const std::vector< uint8_t > &data)
void SetPalette(const SnesPalette &palette)
Set the palette for the bitmap using SNES palette format.
void Get16x16Tile(int tile_x, int tile_y, std::vector< uint8_t > &tile_data, int &tile_data_offset)
Extract a 16x16 tile from the bitmap (SNES metatile size)
RAII timer for automatic timing management.
void DrawBitmap(Bitmap &bitmap, int border_offset, float scale)
void DrawOutlineWithColor(int x, int y, int w, int h, ImVec4 color)
ImVector< ImVec2 > * mutable_points()
int GetTileIdFromMousePos()
bool DrawTileSelector(int size, int size_y=0)
bool DrawTilePainter(const Bitmap &bitmap, int size, float scale=1.0f)
bool IsMouseHovering() const
void DrawBitmapTable(const BitmapTable &gfx_bin)
void DrawBackground(ImVec2 canvas_size=ImVec2(0, 0))
const ImVector< ImVec2 > & points() const
void DrawGrid(float grid_step=64.0f, int tile_id_offset=8)
void DrawText(const std::string &text, int x, int y)
static std::string ShowSaveFileDialog(const std::string &default_name="", const std::string &default_extension="")
ShowSaveFileDialog opens a save file dialog and returns the selected filepath. Uses global feature fl...
static std::string ShowOpenFileDialog()
ShowOpenFileDialog opens a file dialog and returns the selected filepath. Uses global feature flag to...
absl::Status Create(Rom *rom, GameData *game_data=nullptr)
Initialize and load inventory screen data from ROM.
auto & mutable_lw_tiles()
absl::Status SaveCustomMap(const std::string &file_path, bool use_dark_world)
Save map data to external binary file.
auto & mutable_dw_tiles()
absl::Status LoadCustomMap(const std::string &file_path)
Load custom map from external binary file.
absl::Status Save(Rom *rom)
Save changes back to ROM.
absl::Status RenderMapLayer(bool use_dark_world)
Render map tiles into bitmap.
absl::Status Create(Rom *rom)
Initialize and load overworld map data from ROM.
auto & composite_bitmap()
auto & mutable_bg1_buffer()
absl::Status Create(Rom *rom, GameData *game_data=nullptr)
Initialize and load title screen data from ROM.
auto & mutable_bg2_buffer()
absl::Status RenderCompositeLayer(bool show_bg1, bool show_bg2)
Render composite layer with BG1 on top of BG2 with transparency.
absl::Status Save(Rom *rom)
absl::Status RenderBG2Layer()
Render BG2 tilemap into bitmap pixels Converts tile IDs from tiles_bg2_buffer_ into pixel data.
absl::Status RenderBG1Layer()
Render BG1 tilemap into bitmap pixels Converts tile IDs from tiles_bg1_buffer_ into pixel data.
#define ICON_MD_MORE_VERT
#define ICON_MD_INVENTORY
#define PRINT_IF_ERROR(expression)
#define ASSIGN_OR_RETURN(type_variable_name, expression)
constexpr uint32_t kRedPen
void RenderTile16(IRenderer *renderer, Tilemap &tilemap, int tile_id)
void ModifyTile16(Tilemap &tilemap, const std::vector< uint8_t > &data, const TileInfo &top_left, const TileInfo &top_right, const TileInfo &bottom_left, const TileInfo &bottom_right, int sheet_offset, int tile_id)
void UpdateTile16(IRenderer *renderer, Tilemap &tilemap, int tile_id)
std::vector< uint8_t > SnesTo8bppSheet(std::span< uint8_t > sheet, int bpp, int num_sheets)
void EndCanvas(Canvas &canvas)
bool InputHexWord(const char *label, uint16_t *data, float input_width, bool no_step)
bool DrawTileSelector(const CanvasRuntime &rt, int size, int size_y, ImVec2 *out_selected_pos)
void BeginCanvas(Canvas &canvas, ImVec2 child_size)
IMGUI_API absl::Status InlinePaletteEditor(gfx::SnesPalette &palette, const std::string &title, ImGuiColorEditFlags flags)
Full inline palette editor with color picker and copy options.
bool InputTileInfo(const char *label, gfx::TileInfo *tile_info)
IMGUI_API bool DisplayPalette(gfx::SnesPalette &palette, bool loaded)
void DrawBitmap(const CanvasRuntime &rt, gfx::Bitmap &bitmap, int border_offset, float scale)
bool InputHexByte(const char *label, uint8_t *data, float input_width, bool no_step)
std::string HexByte(uint8_t byte, HexStringParams params)
absl::Status LoadDungeonMapTile16(gfx::Tilemap &tile16_blockset, Rom &rom, GameData *game_data, const std::vector< uint8_t > &gfx_data, bool bin_mode)
Load the dungeon map tile16 from the ROM.
absl::Status SaveDungeonMapTile16(gfx::Tilemap &tile16_blockset, Rom &rom)
Save the dungeon map tile16 to the ROM.
absl::StatusOr< std::vector< DungeonMap > > LoadDungeonMaps(Rom &rom, DungeonMapLabels &dungeon_map_labels)
Load the dungeon maps from the ROM.
#define RETURN_IF_ERROR(expr)
PanelManager * panel_manager
PaletteGroup dungeon_main
auto mutable_palette(int i)
void CacheTile(int tile_id, const Bitmap &bitmap)
Cache a tile bitmap by copying it.
Bitmap * GetTile(int tile_id)
Get a cached tile by ID.
Pair tile_size
Size of individual tiles (8x8 or 16x16)
TileCache tile_cache
Smart tile cache with LRU eviction.
Pair map_size
Size of tilemap in tiles.
Bitmap atlas
Master bitmap containing all tiles.
std::vector< std::array< gfx::TileInfo, 4 > > tile_info
Tile metadata (4 tiles per 16x16)
std::optional< float > grid_step
void SelectableLabelWithNameEdit(bool selected, const std::string &type, const std::string &key, const std::string &defaultValue)
gfx::PaletteGroupMap palette_groups