7#include "absl/strings/str_format.h"
17#include "imgui/imgui.h"
33 {.card_id =
"screen.dungeon_maps",
34 .display_name =
"Dungeon Maps",
35 .window_title =
" Dungeon Map Editor",
38 .shortcut_hint =
"Alt+1",
40 .enabled_condition = [
this]() {
return rom()->
is_loaded(); },
41 .disabled_tooltip =
"Load a ROM first"});
42 panel_manager->RegisterPanel(
43 {.card_id =
"screen.inventory_menu",
44 .display_name =
"Inventory Menu",
45 .window_title =
" Inventory Menu",
48 .shortcut_hint =
"Alt+2",
50 .enabled_condition = [
this]() {
return rom()->
is_loaded(); },
51 .disabled_tooltip =
"Load a ROM first"});
52 panel_manager->RegisterPanel(
53 {.card_id =
"screen.overworld_map",
54 .display_name =
"Overworld Map",
55 .window_title =
" Overworld Map",
58 .shortcut_hint =
"Alt+3",
60 .enabled_condition = [
this]() {
return rom()->
is_loaded(); },
61 .disabled_tooltip =
"Load a ROM first"});
62 panel_manager->RegisterPanel(
63 {.card_id =
"screen.title_screen",
64 .display_name =
"Title Screen",
65 .window_title =
" Title Screen",
68 .shortcut_hint =
"Alt+4",
70 .enabled_condition = [
this]() {
return rom()->
is_loaded(); },
71 .disabled_tooltip =
"Load a ROM first"});
72 panel_manager->RegisterPanel(
73 {.card_id =
"screen.naming_screen",
74 .display_name =
"Naming Screen",
75 .window_title =
" Naming Screen",
78 .shortcut_hint =
"Alt+5",
80 .enabled_condition = [
this]() {
return rom()->
is_loaded(); },
81 .disabled_tooltip =
"Load a ROM first"});
84 panel_manager->RegisterEditorPanel(std::make_unique<DungeonMapsPanel>(
86 panel_manager->RegisterEditorPanel(std::make_unique<InventoryMenuPanel>(
88 panel_manager->RegisterEditorPanel(std::make_unique<OverworldMapScreenPanel>(
90 panel_manager->RegisterEditorPanel(std::make_unique<TitleScreenPanel>(
92 panel_manager->RegisterEditorPanel(std::make_unique<NamingScreenPanel>(
96 panel_manager->ShowPanel(
"screen.title_screen");
119 for (
int i = 0; i < 4; i++) {
121 *
game_data()->palette_groups.dungeon_main.mutable_palette(3));
129 const int tile8_width = 128;
130 const int tile8_height = 128;
131 std::vector<uint8_t> tile8_data(tile8_width * tile8_height);
134 for (
int sheet_idx = 0; sheet_idx < 4; sheet_idx++) {
135 const auto& sheet = *
sheets_[sheet_idx];
136 int dest_y_offset = sheet_idx * 32;
138 for (
int y = 0; y < 32; y++) {
139 for (
int x = 0; x < 128; x++) {
140 int src_index = y * 128 + x;
141 int dest_index = (dest_y_offset + y) * 128 + x;
143 if (src_index < sheet.size() && dest_index < tile8_data.size()) {
144 tile8_data[dest_index] = sheet.data()[src_index];
160 return absl::OkStatus();
176 static bool create =
false;
183 ImGui::TextColored(ImVec4(1, 0, 0, 1),
"Error loading inventory: %s",
191 if (ImGui::BeginTable(
"InventoryScreen", 4, ImGuiTableFlags_Resizable)) {
192 ImGui::TableSetupColumn(
"Canvas");
193 ImGui::TableSetupColumn(
"Tilesheet");
194 ImGui::TableSetupColumn(
"Item Icons");
195 ImGui::TableSetupColumn(
"Palette");
196 ImGui::TableHeadersRow();
198 ImGui::TableNextColumn();
209 ImGui::TableNextColumn();
212 frame_opts.
canvas_size = ImVec2(128 * 2 + 2, (192 * 2) + 4);
221 ImGui::TableNextColumn();
224 ImGui::TableNextColumn();
240 if (ImGui::BeginTable(
"InventoryToolset", 8, ImGuiTableFlags_SizingFixedFit,
242 ImGui::TableSetupColumn(
"#drawTool");
243 ImGui::TableSetupColumn(
"#sep1");
244 ImGui::TableSetupColumn(
"#zoomOut");
245 ImGui::TableSetupColumn(
"#zoomIN");
246 ImGui::TableSetupColumn(
"#sep2");
247 ImGui::TableSetupColumn(
"#bg2Tool");
248 ImGui::TableSetupColumn(
"#bg3Tool");
249 ImGui::TableSetupColumn(
"#itemTool");
251 ImGui::TableNextColumn();
255 ImGui::TableNextColumn();
259 ImGui::TableNextColumn();
261 ImGui::TableNextColumn();
265 ImGui::TableNextColumn();
269 ImGui::TableNextColumn();
271 ImGui::TableNextColumn();
275 ImGui::TableNextColumn();
285 if (ImGui::BeginChild(
"##ItemIconsList", ImVec2(0, 0),
true,
286 ImGuiWindowFlags_HorizontalScrollbar)) {
287 ImGui::Text(
"Item Icons (2x2 tiles each)");
293 "No item icons loaded. Icons will be loaded when the "
294 "inventory is initialized.");
300 if (ImGui::BeginTable(
"##IconsTable", 2,
301 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) {
302 ImGui::TableSetupColumn(
"Icon Name");
303 ImGui::TableSetupColumn(
"Tile Data");
304 ImGui::TableHeadersRow();
306 for (
size_t i = 0; i < icons.size(); i++) {
307 const auto& icon = icons[i];
309 ImGui::TableNextRow();
310 ImGui::TableNextColumn();
313 if (ImGui::Selectable(icon.name.c_str(),
false,
314 ImGuiSelectableFlags_SpanAllColumns)) {
318 ImGui::TableNextColumn();
320 ImGui::Text(
"TL:%04X TR:%04X", icon.tile_tl, icon.tile_tr);
322 ImGui::Text(
"BL:%04X BR:%04X", icon.tile_bl, icon.tile_br);
330 "NOTE: Individual icon editing will be implemented in the future "
331 "Oracle of Secrets menu editor. Each icon is composed of 4 tile words "
332 "representing a 2x2 arrangement of 8x8 tiles in SNES tile format "
333 "(vhopppcc cccccccc).");
347 auto boss_room = current_dungeon.boss_room;
350 std::vector<int> tile_ids_to_render;
351 std::vector<ImVec2> tile_positions;
356 if (current_dungeon.floor_rooms[
floor_number][j] != 0x0F) {
357 int tile16_id = current_dungeon.floor_gfx[
floor_number][j];
358 int posX = ((j % 5) * 32);
359 int posY = ((j / 5) * 32);
362 tile_ids_to_render.push_back(tile16_id);
363 tile_positions.emplace_back(posX * 2, posY * 2);
368 for (
size_t idx = 0; idx < tile_ids_to_render.size(); ++idx) {
369 int tile16_id = tile_ids_to_render[idx];
370 ImVec2 pos = tile_positions[idx];
374 const int tile_x = (tile16_id % tiles_per_row) * 16;
375 const int tile_y = (tile16_id / tiles_per_row) * 16;
377 std::vector<uint8_t> tile_data(16 * 16);
378 int tile_data_offset = 0;
395 if (cached_tile && cached_tile->is_active()) {
397 if (!cached_tile->texture()) {
408 if (current_dungeon.floor_rooms[
floor_number][j] != 0x0F) {
409 int posX = ((j % 5) * 32);
410 int posY = ((j / 5) * 32);
412 if (current_dungeon.floor_rooms[
floor_number][j] == boss_room) {
438 if (ImGui::BeginTabBar(
"##DungeonMapTabs")) {
440 current_dungeon.nbr_of_floor + current_dungeon.nbr_of_basement;
441 for (
int i = 0; i < nbr_floors; i++) {
442 int basement_num = current_dungeon.nbr_of_basement - i;
443 std::string tab_name = absl::StrFormat(
"Basement %d", basement_num);
444 if (i >= current_dungeon.nbr_of_basement) {
445 tab_name = absl::StrFormat(
"Floor %d",
446 i - current_dungeon.nbr_of_basement + 1);
448 if (ImGui::BeginTabItem(tab_name.data())) {
462 const auto button_size = ImVec2(130, 0);
464 if (ImGui::Button(
"Add Floor", button_size) &&
465 current_dungeon.nbr_of_floor < 8) {
466 current_dungeon.nbr_of_floor++;
470 if (ImGui::Button(
"Remove Floor", button_size) &&
471 current_dungeon.nbr_of_floor > 0) {
472 current_dungeon.nbr_of_floor--;
476 if (ImGui::Button(
"Add Basement", button_size) &&
477 current_dungeon.nbr_of_basement < 8) {
478 current_dungeon.nbr_of_basement++;
482 if (ImGui::Button(
"Remove Basement", button_size) &&
483 current_dungeon.nbr_of_basement > 0) {
484 current_dungeon.nbr_of_basement--;
488 if (ImGui::Button(
"Copy Floor", button_size)) {
492 if (ImGui::Button(
"Paste Floor", button_size)) {
516 if (ImGui::BeginChild(
"##DungeonMapTiles", ImVec2(0, 0),
true)) {
520 tilesheet_opts.
canvas_size = ImVec2((256 * 2) + 2, (192 * 2) + 4);
535 ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
570 auto current_tile_rt =
579 const int tiles_per_row =
585 std::vector<uint8_t> tile_data(64);
586 for (
int py = 0; py < 8; py++) {
587 for (
int px = 0; px < 8; px++) {
588 int src_x = tile_x + px;
589 int src_y = tile_y + py;
591 int dst_index = py * 8 + px;
602 std::move(new_tile8));
606 if (cached_tile8 && cached_tile8->is_active()) {
608 if (!cached_tile8->texture()) {
627 auto* selected_tile =
629 if (selected_tile && selected_tile->is_active()) {
631 if (!selected_tile->texture()) {
648 if (ImGui::Button(
"Modify Tile16")) {
689 static std::vector<std::string> dungeon_names = {
690 "Sewers/Sanctuary",
"Hyrule Castle",
"Eastern Palace",
691 "Desert Palace",
"Tower of Hera",
"Agahnim's Tower",
692 "Palace of Darkness",
"Swamp Palace",
"Skull Woods",
693 "Thieves' Town",
"Ice Palace",
"Misery Mire",
694 "Turtle Rock",
"Ganon's Tower"};
696 if (ImGui::BeginTable(
"DungeonMapsTable", 4,
697 ImGuiTableFlags_Resizable |
698 ImGuiTableFlags_Reorderable |
699 ImGuiTableFlags_Hideable)) {
700 ImGui::TableSetupColumn(
"Dungeon");
701 ImGui::TableSetupColumn(
"Map");
702 ImGui::TableSetupColumn(
"Rooms Gfx");
703 ImGui::TableSetupColumn(
"Tiles Gfx");
704 ImGui::TableHeadersRow();
706 ImGui::TableNextColumn();
707 for (
int i = 0; i < dungeon_names.size(); i++) {
711 if (ImGui::IsItemClicked()) {
716 ImGui::TableNextColumn();
719 ImGui::TableNextColumn();
722 ImGui::TableNextColumn();
735 ImGui::Text(
"For use with custom inserted graphics assembly patches.");
736 if (ImGui::Button(
"Load GFX from BIN file"))
745 if (!bin_file.empty()) {
746 std::ifstream file(bin_file, std::ios::binary);
747 if (file.is_open()) {
749 std::vector<uint8_t> bin_data((std::istreambuf_iterator<char>(file)),
750 std::istreambuf_iterator<char>());
756 std::vector<std::vector<uint8_t>> gfx_sheets;
757 for (
int i = 0; i < 4; i++) {
758 gfx_sheets.emplace_back(converted_bin.begin() + (i * 0x1000),
759 converted_bin.begin() + ((i + 1) * 0x1000));
760 sheets_[i] = std::make_unique<gfx::Bitmap>(128, 32, 8, gfx_sheets[i]);
762 *
game_data()->palette_groups.dungeon_main.mutable_palette(3));
769 status_ = absl::InternalError(
"Failed to load dungeon map tile16");
781 ImGui::TextColored(ImVec4(1, 0, 0, 1),
"Error loading title screen: %s",
789 ImGui::Text(
"Title screen not loaded. Ensure ROM is loaded.");
801 ImGui::OpenPopup(
"SaveSuccess");
808 if (ImGui::BeginPopup(
"SaveSuccess")) {
809 ImGui::Text(
"Title screen saved successfully!");
832 if (ImGui::BeginTable(
"TitleScreenTable", 2,
833 ImGuiTableFlags_Resizable | ImGuiTableFlags_Borders)) {
834 ImGui::TableSetupColumn(
"Title Screen (Composite)");
835 ImGui::TableSetupColumn(
"Tile Selector");
836 ImGui::TableHeadersRow();
839 ImGui::TableNextColumn();
843 ImGui::TableNextColumn();
856 if (composite_bitmap.is_active()) {
865 int tile_x =
static_cast<int>(click_pos.x) / 8;
866 int tile_y =
static_cast<int>(click_pos.y) / 8;
868 if (tile_x >= 0 && tile_x < 32 && tile_y >= 0 && tile_y < 32) {
869 int tilemap_index = tile_y * 32 + tile_x;
911 if (bg1_bitmap.is_active()) {
920 int tile_x =
static_cast<int>(click_pos.x) / 8;
921 int tile_y =
static_cast<int>(click_pos.y) / 8;
923 if (tile_x >= 0 && tile_x < 32 && tile_y >= 0 && tile_y < 32) {
924 int tilemap_index = tile_y * 32 + tile_x;
956 if (bg2_bitmap.is_active()) {
965 int tile_x =
static_cast<int>(click_pos.x) / 8;
966 int tile_y =
static_cast<int>(click_pos.y) / 8;
968 if (tile_x >= 0 && tile_x < 32 && tile_y >= 0 && tile_y < 32) {
969 int tilemap_index = tile_y * 32 + tile_x;
1001 if (tiles8_bitmap.is_active()) {
1010 int tile_x =
static_cast<int>(click_pos.x) / 8;
1011 int tile_y =
static_cast<int>(click_pos.y) / 8;
1012 int tiles_per_row = 128 / 8;
1030 ImGui::SetNextItemWidth(100);
1042 ImGui::TextColored(ImVec4(1, 0, 0, 1),
"Error loading overworld map: %s",
1050 ImGui::Text(
"Overworld map not loaded. Ensure ROM is loaded.");
1062 ImGui::OpenPopup(
"OWSaveSuccess");
1080 if (ImGui::Button(
"Load Custom Map...")) {
1082 if (!path.empty()) {
1085 ImGui::OpenPopup(
"CustomMapLoadError");
1090 if (ImGui::Button(
"Save Custom Map...")) {
1092 if (!path.empty()) {
1095 ImGui::OpenPopup(
"CustomMapSaveSuccess");
1104 if (ImGui::BeginPopup(
"CustomMapLoadError")) {
1105 ImGui::Text(
"Error loading custom map: %s",
status_.message().data());
1108 if (ImGui::BeginPopup(
"CustomMapSaveSuccess")) {
1109 ImGui::Text(
"Custom map saved successfully!");
1114 if (ImGui::BeginPopup(
"OWSaveSuccess")) {
1115 ImGui::Text(
"Overworld map saved successfully!");
1120 if (ImGui::BeginTable(
"OWMapTable", 3,
1121 ImGuiTableFlags_Resizable | ImGuiTableFlags_Borders)) {
1122 ImGui::TableSetupColumn(
"Map Canvas");
1123 ImGui::TableSetupColumn(
"Tileset");
1124 ImGui::TableSetupColumn(
"Palette");
1125 ImGui::TableHeadersRow();
1128 ImGui::TableNextColumn();
1133 if (map_bitmap.is_active()) {
1142 int tile_x =
static_cast<int>(click_pos.x) / 8;
1143 int tile_y =
static_cast<int>(click_pos.y) / 8;
1145 if (tile_x >= 0 && tile_x < 64 && tile_y >= 0 && tile_y < 64) {
1146 int tile_index = tile_x + (tile_y * 64);
1170 ImGui::TableNextColumn();
1175 if (tiles8_bitmap.is_active()) {
1183 int tile_x =
static_cast<int>(click_pos.x) / 8;
1184 int tile_y =
static_cast<int>(click_pos.y) / 8;
1193 ImGui::TableNextColumn();
1204 static bool show_bg1 =
true;
1205 static bool show_bg2 =
true;
1206 static bool show_bg3 =
true;
1208 static bool drawing_bg1 =
true;
1209 static bool drawing_bg2 =
false;
1210 static bool drawing_bg3 =
false;
1212 ImGui::Checkbox(
"Show BG1", &show_bg1);
1214 ImGui::Checkbox(
"Show BG2", &show_bg2);
1216 ImGui::Checkbox(
"Draw BG1", &drawing_bg1);
1218 ImGui::Checkbox(
"Draw BG2", &drawing_bg2);
1220 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