6#include "absl/status/status.h"
7#include "absl/strings/str_cat.h"
8#include "absl/strings/str_format.h"
9#include "absl/strings/str_join.h"
20#include "imgui/imgui.h"
27using ImGui::BeginChild;
28using ImGui::BeginCombo;
29using ImGui::BeginGroup;
30using ImGui::BeginTabItem;
31using ImGui::BeginTable;
35using ImGui::EndTabItem;
37using ImGui::GetContentRegionAvail;
39using ImGui::IsItemClicked;
43using ImGui::Selectable;
44using ImGui::Separator;
45using ImGui::SetNextItemWidth;
46using ImGui::SliderFloat;
47using ImGui::TableHeadersRow;
48using ImGui::TableNextColumn;
49using ImGui::TableNextRow;
50using ImGui::TableSetupColumn;
53using gfx::kPaletteGroupNames;
61constexpr int kTileSize = 16;
67 const ImGuiStyle& style = ImGui::GetStyle();
68 return ImGui::CalcTextSize(
"Slot 00").x +
70 4 * style.ItemInnerSpacing.x + 2 * style.CellPadding.x;
75 float scale = kDefaultScale) {
88 canvas.
DrawBackground(ImVec2(display_width + 1, display_height + 1));
95 canvas.
DrawGrid(
static_cast<int>(8 * scale));
110 Text(
"Sheet 0x%02X is not loaded", sheet_id);
115 !preview.
surface() || preview.
vector().size() != pixels.size() ||
116 !std::equal(pixels.begin(), pixels.end(), preview.
vector().begin());
118 preview.
Create(128, 32, 8,
119 std::vector<uint8_t>(pixels.begin(), pixels.end()));
124 if (palette && preview.
palette() != *palette) {
128 }
else if (changed && preview.
texture()) {
148 if (BeginTabItem(tr(
"Blocksets"))) {
150 static_cast<uint8_t
>(0x24));
152 false,
"blockset", std::to_string(
Ws().selected_blockset),
153 absl::StrFormat(
"Blockset 0x%02X",
Ws().selected_blockset));
158 if (BeginTabItem(tr(
"Roomsets"))) {
160 static_cast<uint8_t
>(81));
162 false,
"roomset", std::to_string(
Ws().selected_roomset),
163 absl::StrFormat(
"Roomset 0x%02X",
Ws().selected_roomset));
168 if (BeginTabItem(tr(
"Spritesets"))) {
170 static_cast<uint8_t
>(143));
172 false,
"spriteset", std::to_string(
Ws().selected_spriteset),
173 absl::StrFormat(
"Spriteset 0x%02X",
Ws().selected_spriteset));
182 return absl::OkStatus();
186 if (!ImGui::TreeNodeEx(
"Used by##SpritesetUsage",
187 ImGuiTreeNodeFlags_DefaultOpen)) {
191 HOVER_HINT(
"Re-read overworld areas and room headers from the ROM buffer");
199 ImGui::TextDisabled(tr(
"Load a ROM to see usage."));
204 auto hex_list = [](
const std::vector<int>& ids) {
205 return ids.empty() ? std::string(
"-")
206 : absl::StrJoin(ids,
" ", [](std::string* out,
int v) {
207 out->append(absl::StrFormat(
"%02X", v));
212 static constexpr const char* kStates[] = {
213 "OW areas, state 0 (start)",
"OW areas, state 1",
"OW areas, state 2"};
214 for (
int state = 0; state < 3; ++state) {
215 ImGui::TextWrapped(
"%s (%zu): %s", kStates[state],
216 usage.ow_areas_by_state[state].size(),
217 hex_list(usage.ow_areas_by_state[state]).c_str());
219 ImGui::TextWrapped(
"Rooms, header 0x%02X (%zu): %s",
221 usage.rooms.size(), hex_list(usage.rooms).c_str());
224 tr(
"Dungeon rooms use spritesets 0x40-0x8F (header + 0x40)."));
231 Text(tr(
"No game data loaded"));
235 if (
Ws().selected_blockset >=
game_data()->main_blockset_ids.size()) {
236 Text(
"Main graphics group is unavailable");
239 PushID(
"BlocksetViewer");
242 if (BeginTable(
"##BlocksetTable", sheet_only ? 1 : 2,
243 ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable,
246 TableSetupColumn(
"Inputs", ImGuiTableColumnFlags_WidthFixed,
247 SlotInputsColumnWidth());
250 TableSetupColumn(
"Sheets", ImGuiTableColumnFlags_WidthStretch);
263 for (
int idx = 0; idx < 8; idx++) {
271 int unique_id = (ws.selected_blockset << 16) | (idx << 8) | sheet_id;
284 Text(tr(
"No game data loaded"));
288 if (
Ws().selected_roomset >=
game_data()->room_blockset_ids.size()) {
289 ImGui::TextUnformatted(
"Roomset is unavailable");
292 PushID(
"RoomsetViewer");
294 Text(tr(
"Roomsets overwrite slots 3-6 of the main blockset"));
296 if (BeginTable(
"##RoomsTable", 3,
297 ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable,
299 TableSetupColumn(
"List", ImGuiTableColumnFlags_WidthFixed, 120);
300 TableSetupColumn(
"Inputs", ImGuiTableColumnFlags_WidthFixed,
301 SlotInputsColumnWidth());
302 TableSetupColumn(
"Sheets", ImGuiTableColumnFlags_WidthStretch);
308 if (BeginChild(
"##RoomsetListChild", ImVec2(0, 300))) {
309 for (
int idx = 0; idx < 0x52; idx++) {
311 std::string roomset_label = absl::StrFormat(
"0x%02X", idx);
312 bool is_selected = (ws.selected_roomset ==
static_cast<uint8_t
>(idx));
313 if (Selectable(roomset_label.c_str(), is_selected)) {
314 ws.selected_roomset =
static_cast<uint8_t
>(idx);
327 for (
int idx = 0; idx < 4; idx++) {
330 Text(
"Slot %d: Inherit from main group", idx + 3);
333 Text(
"Slot %d / Sheet %02X", idx + 3, sheet_id);
341 (0x1000) | (ws.selected_roomset << 8) | (idx << 4) | sheet_id;
353 Text(tr(
"No game data loaded"));
357 PushID(
"SpritesetViewer");
360 if (BeginTable(
"##SpritesTable", sheet_only ? 1 : 2,
361 ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable,
364 TableSetupColumn(
"Inputs", ImGuiTableColumnFlags_WidthFixed,
365 SlotInputsColumnWidth());
367 TableSetupColumn(
"Sheets", ImGuiTableColumnFlags_WidthStretch);
374 Text(tr(
"Sprite sheet IDs (base 115+):"));
375 for (
int idx = 0; idx < 4; idx++) {
378 (
"Slot " + std::to_string(idx)).c_str(),
379 &
game_data()->spriteset_ids[ws.selected_spriteset][idx]);
386 for (
int idx = 0; idx < 4; idx++) {
388 int sheet_id = 115 + sheet_offset;
396 (0x2000) | (ws.selected_spriteset << 8) | (idx << 4) | sheet_offset;
409 if (palette.
empty()) {
412 for (
size_t color_idx = 0; color_idx < palette.
size(); color_idx++) {
413 PushID(
static_cast<int>(color_idx));
414 if ((color_idx % 8) != 0) {
415 SameLine(0.0f, GetStyle().ItemSpacing.y);
420 ImGuiColorEditFlags_NoAlpha |
421 ImGuiColorEditFlags_NoPicker |
422 ImGuiColorEditFlags_NoTooltip);
440 SliderFloat(
"##ViewScale", &ws.view_scale, 1.0f, 4.0f,
"%.1fx");
449 static constexpr int kNumPaletteCategories = 14;
452 gfx::kPaletteCategoryNames[ws.selected_palette_category].data())) {
453 for (
int cat = 0; cat < kNumPaletteCategories; cat++) {
455 bool is_selected = (ws.selected_palette_category == category);
456 if (Selectable(gfx::kPaletteCategoryNames[category].data(),
458 ws.selected_palette_category = category;
459 ws.selected_palette_index = 0;
463 ImGui::SetItemDefaultFocus();
476 ImGui::Checkbox(tr(
"Override palette"), &ws.override_palette);
477 if (ImGui::IsItemHovered()) {
479 tr(
"When off, each sheet renders against its slot's role-default "
480 "palette (overworld main/aux/animated, sprites, hud, dungeon).\n"
481 "When on, the palette picked above is forced onto every sheet."));
497 const auto& ws =
Ws();
499 switch (ws.selected_palette_category) {
500 case PaletteCategory::kSword:
502 groups.swords.mutable_palette(ws.selected_palette_index);
504 case PaletteCategory::kShield:
506 groups.shields.mutable_palette(ws.selected_palette_index);
508 case PaletteCategory::kClothes:
510 groups.armors.mutable_palette(ws.selected_palette_index);
512 case PaletteCategory::kWorldColors:
514 groups.overworld_main.mutable_palette(ws.selected_palette_index);
516 case PaletteCategory::kAreaColors:
518 groups.overworld_aux.mutable_palette(ws.selected_palette_index);
520 case PaletteCategory::kGlobalSprites:
522 groups.global_sprites.mutable_palette(ws.selected_palette_index);
524 case PaletteCategory::kSpritesAux1:
526 groups.sprites_aux1.mutable_palette(ws.selected_palette_index);
528 case PaletteCategory::kSpritesAux2:
530 groups.sprites_aux2.mutable_palette(ws.selected_palette_index);
532 case PaletteCategory::kSpritesAux3:
534 groups.sprites_aux3.mutable_palette(ws.selected_palette_index);
536 case PaletteCategory::kDungeons:
538 groups.dungeon_main.mutable_palette(ws.selected_palette_index);
540 case PaletteCategory::kWorldMap:
541 case PaletteCategory::kDungeonMap:
543 groups.overworld_mini_map.mutable_palette(ws.selected_palette_index);
545 case PaletteCategory::kTriforce:
546 case PaletteCategory::kCrystal:
548 groups.object_3d.mutable_palette(ws.selected_palette_index);
project::ResourceLabelManager * resource_label()
std::array< gfx::Bitmap, 4 > roomset_previews_
std::string host_surface_hint_
std::array< gui::Canvas, 8 > blockset_canvases_
zelda3::GameData * game_data() const
void DrawAssignmentControls(GroupKind kind)
std::optional< std::map< int, zelda3::OverworldAreaGfxInfo > > usage_areas_
zelda3::GameData * game_data_
std::array< gui::Canvas, 4 > roomset_canvases_
void UpdateCurrentPalette()
std::array< gfx::Bitmap, 4 > spriteset_previews_
GfxGroupWorkspaceState & Ws()
gfx::SnesPalette * current_palette_
std::optional< std::map< int, zelda3::RoomGfxInfo > > usage_rooms_
void DrawSpritesetViewer(bool sheet_only=false)
void DrawSpritesetUsage(int id)
std::array< gfx::Bitmap, 8 > blockset_previews_
std::array< gui::Canvas, 4 > spriteset_canvases_
void DrawPaletteControls()
void DrawBlocksetViewer(bool sheet_only=false)
gfx::Bitmap * PrepareSheetPreview(int sheet_id, gfx::SheetRole role, gfx::Bitmap &preview)
Represents a bitmap image optimized for SNES ROM hacking.
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.
TextureHandle texture() const
const std::vector< uint8_t > & vector() const
void SetPalette(const SnesPalette &palette)
Set the palette for the bitmap using SNES palette format.
SDL_Surface * surface() const
void UpdateTexture()
Updates the underlying SDL_Texture when it already exists.
Represents a palette of colors for the Super Nintendo Entertainment System (SNES).
Modern, robust canvas for drawing and manipulating graphics.
void DrawBitmap(Bitmap &bitmap, int border_offset, float scale)
CanvasConfig & GetConfig()
void DrawBackground(ImVec2 canvas_size=ImVec2(0, 0), std::optional< ImVec2 > viewport_size=std::nullopt)
void DrawGrid(float grid_step=64.0f, int tile_id_offset=8)
static float GetHexInputWidth()
static float GetSliderWidth()
static float GetComboWidth()
bool HasSheet(uint16_t sheet) const
True when the buffer holds all 4096 bytes of sheet.
static constexpr uint16_t kSheetCount
absl::Span< const uint8_t > Sheet(uint16_t sheet) const
The sheet's 4096 pixels, or an empty span when it is not held.
#define HOVER_HINT(string)
float SlotInputsColumnWidth()
void DrawScaledSheet(gui::Canvas &canvas, gfx::Bitmap &sheet, int unique_id, float scale=kDefaultScale)
constexpr int kSheetDisplayHeight
void DrawPaletteFromPaletteGroup(gfx::SnesPalette &palette)
constexpr float kDefaultScale
void EnsureSheetTextureQueued(gfx::Bitmap &sheet)
const gfx::SnesPalette * ResolveRoleDefaultPalette(gfx::SheetRole role, gfx::PaletteGroupMap &palette_groups)
PaletteCategory
Categories for organizing palette groups in the UI.
constexpr int kTilesheetHeight
constexpr int kTilesheetWidth
SheetRole RoleForRoomsetSlot(int slot_index)
SheetRole RoleForBlocksetSlot(int slot_index)
SheetRole RoleForSpritesetSlot(int slot_index)
IMGUI_API bool SnesColorButton(absl::string_view id, gfx::SnesColor &color, ImGuiColorEditFlags flags, const ImVec2 &size_arg)
bool BeginThemedTabBar(const char *id, ImGuiTabBarFlags flags)
A stylized tab bar with "Mission Control" branding.
bool InputHexByte(const char *label, uint8_t *data, float input_width, bool no_step)
GfxGroupUsage FindSpritesetUsage(int spriteset, const std::map< int, OverworldAreaGfxInfo > &areas, const std::map< int, RoomGfxInfo > &rooms)
std::map< int, OverworldAreaGfxInfo > CollectOverworldAreaGfx(Rom &rom, GameData *game_data)
constexpr uint16_t kDungeonSpritesetBase
std::map< int, RoomGfxInfo > CollectRoomGfx(const Rom &rom)
void SelectableLabelWithNameEdit(bool selected, const std::string &type, const std::string &key, const std::string &defaultValue)
std::array< std::array< uint8_t, 4 >, kNumSpritesets > spriteset_ids
std::array< std::array< uint8_t, 4 >, kNumRoomBlocksets > room_blockset_ids
gfx::PaletteGroupMap palette_groups
std::array< std::array< uint8_t, 8 >, kNumMainBlocksets > main_blockset_ids
GraphicsSheetStore sheet_store