21using ImGui::BeginTable;
24using ImGui::Selectable;
25using ImGui::Separator;
26using ImGui::TableHeadersRow;
27using ImGui::TableNextColumn;
28using ImGui::TableNextRow;
29using ImGui::TableSetupColumn;
42 DrawVanillaSpriteEditor();
44 ImGui::TextDisabled(
"Load a ROM to view vanilla sprites");
48 panel_manager->RegisterEditorPanel(std::make_unique<CustomSpriteEditorPanel>(
54 return absl::OkStatus();
63 float current_time = ImGui::GetTime();
79 if (ImGui::IsKeyPressed(ImGuiKey_Space,
false) && !ImGui::GetIO().WantTextInput) {
84 if (ImGui::IsKeyPressed(ImGuiKey_LeftBracket,
false)) {
90 if (ImGui::IsKeyPressed(ImGuiKey_RightBracket,
false)) {
96 if (ImGui::GetIO().KeyCtrl && ImGui::IsKeyPressed(ImGuiKey_UpArrow,
false)) {
102 if (ImGui::GetIO().KeyCtrl && ImGui::IsKeyPressed(ImGuiKey_DownArrow,
false)) {
117 return absl::OkStatus();
129 if (ImGui::BeginTable(
"##SpriteCanvasTable", 3, ImGuiTableFlags_Resizable,
131 TableSetupColumn(
"Sprites List", ImGuiTableColumnFlags_WidthFixed, 256);
132 TableSetupColumn(
"Canvas", ImGuiTableColumnFlags_WidthStretch,
133 ImGui::GetContentRegionAvail().x);
134 TableSetupColumn(
"Tile Selector", ImGuiTableColumnFlags_WidthFixed, 256);
142 static int next_tab_id = 0;
161 if (ImGui::BeginTabItem(
163 ImGuiTabItemFlags_None)) {
186 static bool flip_x =
false;
187 static bool flip_y =
false;
188 if (ImGui::BeginChild(
gui::GetID(
"##SpriteCanvas"),
189 ImGui::GetContentRegionAvail(),
true)) {
208 ImGui::SetCursorPos(ImVec2(10, 10));
209 Text(
"Sprite: %s (0x%02X)", layout->name, layout->sprite_id);
210 Text(
"Tiles: %zu", layout->tiles.size());
217 if (ImGui::BeginTable(
"##OAMTable", 7, ImGuiTableFlags_Resizable,
219 TableSetupColumn(
"X", ImGuiTableColumnFlags_WidthStretch);
220 TableSetupColumn(
"Y", ImGuiTableColumnFlags_WidthStretch);
221 TableSetupColumn(
"Tile", ImGuiTableColumnFlags_WidthStretch);
222 TableSetupColumn(
"Palette", ImGuiTableColumnFlags_WidthStretch);
223 TableSetupColumn(
"Priority", ImGuiTableColumnFlags_WidthStretch);
224 TableSetupColumn(
"Flip X", ImGuiTableColumnFlags_WidthStretch);
225 TableSetupColumn(
"Flip Y", ImGuiTableColumnFlags_WidthStretch);
245 if (ImGui::Checkbox(
"##XFlip", &flip_x)) {
250 if (ImGui::Checkbox(
"##YFlip", &flip_y)) {
263 if (ImGui::BeginChild(
gui::GetID(
"sheet_label"),
264 ImVec2(ImGui::GetContentRegionAvail().x, 0),
true,
265 ImGuiWindowFlags_NoDecoration)) {
267 static uint8_t prev_sheets[8] = {0};
268 bool sheets_changed =
false;
270 for (
int i = 0; i < 8; i++) {
271 std::string sheet_label = absl::StrFormat(
"Sheet %d", i);
273 sheets_changed =
true;
280 if (sheets_changed || std::memcmp(prev_sheets,
current_sheets_, 8) != 0) {
289 for (
int i = 0; i < 8; i++) {
301 if (ImGui::BeginChild(
gui::GetID(
"##SpritesList"),
302 ImVec2(ImGui::GetContentRegionAvail().x, 0),
true,
303 ImGuiWindowFlags_NoDecoration)) {
309 if (ImGui::IsItemClicked()) {
325 if (ImGui::Button(
"Add Frame")) {
328 if (ImGui::Button(
"Remove Frame")) {
338 if (BeginTable(
"##CustomSpritesTable", 3,
339 ImGuiTableFlags_Resizable | ImGuiTableFlags_Borders,
341 TableSetupColumn(
"Sprite Data", ImGuiTableColumnFlags_WidthFixed, 300);
342 TableSetupColumn(
"Canvas", ImGuiTableColumnFlags_WidthStretch);
343 TableSetupColumn(
"Tilesheets", ImGuiTableColumnFlags_WidthFixed, 280);
369 if (!file_path.empty()) {
391 Text(
"Loaded Sprites:");
392 if (ImGui::BeginChild(
"SpriteList", ImVec2(0, 100),
true)) {
410 if (ImGui::BeginTabBar(
"SpriteDataTabs")) {
411 if (ImGui::BeginTabItem(
"Properties")) {
415 if (ImGui::BeginTabItem(
"Animations")) {
419 if (ImGui::BeginTabItem(
"Routines")) {
426 Text(
"No sprite selected");
433 new_sprite.
sprName =
"New Sprite";
439 new_sprite.
animations.emplace_back(0, 0, 1,
"Idle");
492 static char name_buf[256];
493 strncpy(name_buf, sprite.sprName.c_str(),
sizeof(name_buf) - 1);
494 if (ImGui::InputText(
"Name", name_buf,
sizeof(name_buf))) {
495 sprite.sprName = name_buf;
496 sprite.property_sprname.Text = name_buf;
500 static char id_buf[32];
501 strncpy(id_buf, sprite.property_sprid.Text.c_str(),
sizeof(id_buf) - 1);
502 if (ImGui::InputText(
"Sprite ID", id_buf,
sizeof(id_buf))) {
503 sprite.property_sprid.Text = id_buf;
520 int prize = sprite.property_prize.Text.empty()
522 : std::stoi(sprite.property_prize.Text);
523 if (ImGui::InputInt(
"Prize", &prize)) {
524 sprite.property_prize.Text = std::to_string(std::clamp(prize, 0, 255));
528 int palette = sprite.property_palette.Text.empty()
530 : std::stoi(sprite.property_palette.Text);
531 if (ImGui::InputInt(
"Palette", &palette)) {
532 sprite.property_palette.Text = std::to_string(std::clamp(palette, 0, 7));
536 int oamnbr = sprite.property_oamnbr.Text.empty()
538 : std::stoi(sprite.property_oamnbr.Text);
539 if (ImGui::InputInt(
"OAM Count", &oamnbr)) {
540 sprite.property_oamnbr.Text = std::to_string(std::clamp(oamnbr, 0, 255));
544 int hitbox = sprite.property_hitbox.Text.empty()
546 : std::stoi(sprite.property_hitbox.Text);
547 if (ImGui::InputInt(
"Hitbox", &hitbox)) {
548 sprite.property_hitbox.Text = std::to_string(std::clamp(hitbox, 0, 255));
552 int health = sprite.property_health.Text.empty()
554 : std::stoi(sprite.property_health.Text);
555 if (ImGui::InputInt(
"Health", &health)) {
556 sprite.property_health.Text = std::to_string(std::clamp(health, 0, 255));
560 int damage = sprite.property_damage.Text.empty()
562 : std::stoi(sprite.property_damage.Text);
563 if (ImGui::InputInt(
"Damage", &damage)) {
564 sprite.property_damage.Text = std::to_string(std::clamp(damage, 0, 255));
572 Text(
"Behavior Flags");
575 if (ImGui::BeginTable(
"BoolProps", 2, ImGuiTableFlags_None)) {
577 ImGui::TableNextColumn();
578 if (ImGui::Checkbox(
"Blockable", &sprite.property_blockable.IsChecked))
580 if (ImGui::Checkbox(
"Can Fall", &sprite.property_canfall.IsChecked))
582 if (ImGui::Checkbox(
"Collision Layer",
583 &sprite.property_collisionlayer.IsChecked))
585 if (ImGui::Checkbox(
"Custom Death", &sprite.property_customdeath.IsChecked))
587 if (ImGui::Checkbox(
"Damage Sound", &sprite.property_damagesound.IsChecked))
589 if (ImGui::Checkbox(
"Deflect Arrows",
590 &sprite.property_deflectarrows.IsChecked))
592 if (ImGui::Checkbox(
"Deflect Projectiles",
593 &sprite.property_deflectprojectiles.IsChecked))
595 if (ImGui::Checkbox(
"Fast", &sprite.property_fast.IsChecked))
597 if (ImGui::Checkbox(
"Harmless", &sprite.property_harmless.IsChecked))
599 if (ImGui::Checkbox(
"Impervious", &sprite.property_impervious.IsChecked))
603 ImGui::TableNextColumn();
604 if (ImGui::Checkbox(
"Impervious Arrow",
605 &sprite.property_imperviousarrow.IsChecked))
607 if (ImGui::Checkbox(
"Impervious Melee",
608 &sprite.property_imperviousmelee.IsChecked))
610 if (ImGui::Checkbox(
"Interaction", &sprite.property_interaction.IsChecked))
612 if (ImGui::Checkbox(
"Is Boss", &sprite.property_isboss.IsChecked))
614 if (ImGui::Checkbox(
"Persist", &sprite.property_persist.IsChecked))
616 if (ImGui::Checkbox(
"Shadow", &sprite.property_shadow.IsChecked))
618 if (ImGui::Checkbox(
"Small Shadow",
619 &sprite.property_smallshadow.IsChecked))
621 if (ImGui::Checkbox(
"Stasis", &sprite.property_statis.IsChecked))
623 if (ImGui::Checkbox(
"Statue", &sprite.property_statue.IsChecked))
625 if (ImGui::Checkbox(
"Water Sprite",
626 &sprite.property_watersprite.IsChecked))
663 (
int)sprite.editor.Frames.size() - 1);
670 int frame_count =
static_cast<int>(sprite.editor.Frames.size());
671 sprite.animations.emplace_back(0, frame_count > 0 ? frame_count - 1 : 0, 1,
676 if (ImGui::BeginChild(
"AnimList", ImVec2(0, 120),
true)) {
677 for (
size_t i = 0; i < sprite.animations.size(); i++) {
678 auto& anim = sprite.animations[i];
680 anim.frame_name.empty() ?
"Unnamed" : anim.frame_name;
696 Text(
"Animation Properties");
698 static char anim_name[128];
699 strncpy(anim_name, anim.frame_name.c_str(),
sizeof(anim_name) - 1);
700 if (ImGui::InputText(
"Name##Anim", anim_name,
sizeof(anim_name))) {
701 anim.frame_name = anim_name;
705 int start = anim.frame_start;
706 int end = anim.frame_end;
707 int speed = anim.frame_speed;
709 if (ImGui::SliderInt(
"Start Frame", &start, 0,
710 std::max(0, (
int)sprite.editor.Frames.size() - 1))) {
711 anim.frame_start =
static_cast<uint8_t
>(start);
714 if (ImGui::SliderInt(
"End Frame", &end, 0,
715 std::max(0, (
int)sprite.editor.Frames.size() - 1))) {
716 anim.frame_end =
static_cast<uint8_t
>(end);
719 if (ImGui::SliderInt(
"Speed", &speed, 1, 16)) {
720 anim.frame_speed =
static_cast<uint8_t
>(speed);
724 if (ImGui::Button(
"Delete Animation") && sprite.animations.size() > 1) {
725 sprite.animations.erase(sprite.animations.begin() +
729 (
int)sprite.animations.size() - 1);
743 sprite.editor.Frames.emplace_back();
749 sprite.editor.Frames.erase(sprite.editor.Frames.begin() +
current_frame_);
757 if (ImGui::BeginChild(
"FrameList", ImVec2(0, 80),
true,
758 ImGuiWindowFlags_HorizontalScrollbar)) {
759 for (
size_t i = 0; i < sprite.editor.Frames.size(); i++) {
760 ImGui::PushID(
static_cast<int>(i));
761 std::string label = absl::StrFormat(
"F%d", i);
763 ImGuiSelectableFlags_None, ImVec2(40, 40))) {
781 frame.Tiles.emplace_back();
786 if (ImGui::BeginChild(
"TileList", ImVec2(0, 100),
true)) {
787 for (
size_t i = 0; i < frame.Tiles.size(); i++) {
788 auto& tile = frame.Tiles[i];
789 std::string label = absl::StrFormat(
"Tile %d (ID: %d)", i, tile.id);
802 int tile_id = tile.id;
803 if (ImGui::InputInt(
"Tile ID", &tile_id)) {
804 tile.id =
static_cast<uint16_t
>(std::clamp(tile_id, 0, 511));
809 int x = tile.x, y = tile.y;
810 if (ImGui::InputInt(
"X", &x)) {
811 tile.x =
static_cast<uint8_t
>(std::clamp(x, 0, 251));
815 if (ImGui::InputInt(
"Y", &y)) {
816 tile.y =
static_cast<uint8_t
>(std::clamp(y, 0, 219));
821 int pal = tile.palette;
822 if (ImGui::SliderInt(
"Palette##Tile", &pal, 0, 7)) {
823 tile.palette =
static_cast<uint8_t
>(pal);
828 if (ImGui::Checkbox(
"16x16", &tile.size)) {
833 if (ImGui::Checkbox(
"Flip X", &tile.mirror_x)) {
838 if (ImGui::Checkbox(
"Flip Y", &tile.mirror_y)) {
843 if (ImGui::Button(
"Delete Tile")) {
868 float frame_duration = anim.frame_speed / 60.0f;
888 sprite.userRoutines.emplace_back(
"New Routine",
"; ASM code here\n");
893 if (ImGui::BeginChild(
"RoutineList", ImVec2(0, 100),
true)) {
894 for (
size_t i = 0; i < sprite.userRoutines.size(); i++) {
895 auto& routine = sprite.userRoutines[i];
910 static char routine_name[128];
911 strncpy(routine_name, routine.name.c_str(),
sizeof(routine_name) - 1);
912 if (ImGui::InputText(
"Routine Name", routine_name,
sizeof(routine_name))) {
913 routine.name = routine_name;
920 static char code_buffer[16384];
921 strncpy(code_buffer, routine.code.c_str(),
sizeof(code_buffer) - 1);
922 code_buffer[
sizeof(code_buffer) - 1] =
'\0';
923 if (ImGui::InputTextMultiline(
"##RoutineCode", code_buffer,
924 sizeof(code_buffer), ImVec2(-1, 200))) {
925 routine.code = code_buffer;
929 if (ImGui::Button(
"Delete Routine")) {
930 sprite.userRoutines.erase(sprite.userRoutines.begin() +
951 constexpr int kSheetWidth = 128;
952 constexpr int kSheetHeight = 32;
953 constexpr int kRowStride = 128;
955 for (
int sheet_idx = 0; sheet_idx < 8; sheet_idx++) {
962 if (!sheet.is_active() || sheet.size() == 0) {
971 int dest_offset = sheet_idx * (kSheetHeight * kRowStride);
973 const uint8_t* src_data = sheet.data();
975 std::min(sheet.size(),
static_cast<size_t>(kSheetWidth * kSheetHeight));
1007 for (
size_t i = 0; i < global.size() && i < 8; i++) {
1022 }
else if (aux3.size() > 0) {
1035 bool changed =
false;
1036 for (
int i = 0; i < 4; i++) {
1074 for (
const auto& entry : layout.
tiles) {
1076 tile.
x =
static_cast<uint8_t
>(entry.x_offset + 128);
1077 tile.
y =
static_cast<uint8_t
>(entry.y_offset + 128);
1078 tile.
id = entry.tile_id;
1080 tile.
size = entry.size_16x16;
1094 for (
size_t col_idx = 0; col_idx < 16 && col_idx < sub_pal.size(); col_idx++) {
1095 combined_palette.
AddColor(sub_pal[col_idx]);
1098 while (combined_palette.
size() < (pal_idx + 1) * 16) {
1119 if (frame_index < 0 || frame_index >= (
int)sprite.editor.Frames.size()) {
1123 auto& frame = sprite.editor.Frames[frame_index];
1150 for (
size_t col_idx = 0; col_idx < 16 && col_idx < sub_pal.size(); col_idx++) {
1151 combined_palette.
AddColor(sub_pal[col_idx]);
1154 while (combined_palette.
size() < (pal_idx + 1) * 16) {
1172 for (
size_t i = 0; i < frame.Tiles.size(); i++) {
1173 const auto& tile = frame.Tiles[i];
1174 int tile_size = tile.size ? 16 : 8;
1177 int8_t signed_x =
static_cast<int8_t
>(tile.x);
1178 int8_t signed_y =
static_cast<int8_t
>(tile.y);
1180 int canvas_x = 128 + signed_x;
1181 int canvas_y = 128 + signed_y;
1185 ? ImVec4(0.0f, 1.0f, 0.0f, 0.8f)
1186 : ImVec4(1.0f, 1.0f, 0.0f, 0.3f);
1194 if (ImGui::BeginChild(
gui::GetID(
"##ZSpriteCanvas"),
1195 ImGui::GetContentRegionAvail(),
true)) {
1211 ImGui::SetCursorPos(ImVec2(10, 10));
project::ResourceLabelManager * resource_label()
zelda3::GameData * game_data() const
EditorDependencies dependencies_
void RegisterEditorPanel(std::unique_ptr< EditorPanel > panel)
Register an EditorPanel instance for central drawing.
void SetPalettes(const gfx::PaletteGroup *palettes)
Set the palette group for color mapping.
void ClearBitmap(gfx::Bitmap &bitmap)
Clear the bitmap with transparent color.
void DrawFrame(gfx::Bitmap &bitmap, const zsprite::Frame &frame, int origin_x, int origin_y)
Draw all tiles in a ZSM frame.
bool IsReady() const
Check if drawer is ready to render.
void DrawOamTile(gfx::Bitmap &bitmap, const zsprite::OamTile &tile, int origin_x, int origin_y)
Draw a single ZSM OAM tile to bitmap.
void SetGraphicsBuffer(const uint8_t *buffer)
Set the graphics buffer for tile lookup.
ImVector< int > active_sprites_
int current_custom_sprite_index_
void UpdateAnimationPlayback(float delta_time)
void DrawAnimationFrames()
void DrawAnimationPanel()
void RenderZSpriteFrame(int frame_index)
void LoadSheetsForSprite(const std::array< uint8_t, 4 > &sheets)
bool preview_needs_update_
void DrawZSpriteOnCanvas()
bool vanilla_preview_needs_update_
absl::Status Update() override
void HandleEditorShortcuts()
gfx::PaletteGroup sprite_palettes_
void LoadZsmFile(const std::string &path)
void DrawUserRoutinesPanel()
void RenderVanillaSprite(const zelda3::SpriteOamLayout &layout)
void Initialize() override
void DrawVanillaSpriteEditor()
int selected_routine_index_
uint8_t current_sheets_[8]
SpriteDrawer sprite_drawer_
gui::Canvas graphics_sheet_canvas_
std::vector< zsprite::ZSprite > custom_sprites_
std::vector< uint8_t > sprite_gfx_buffer_
gfx::Bitmap sprite_preview_bitmap_
int current_animation_index_
void DrawBooleanProperties()
void DrawSpritePropertiesPanel()
absl::Status Save() override
void DrawCustomSpritesMetadata()
void SaveZsmFile(const std::string &path)
gui::Canvas sprite_canvas_
void DrawStatProperties()
std::string current_zsm_path_
absl::Status Load() override
void LoadSpriteGraphicsBuffer()
void LoadSpritePalettes()
gfx::Bitmap vanilla_preview_bitmap_
std::array< gfx::Bitmap, 223 > & gfx_sheets()
Get reference to all graphics sheets.
void Create(int width, int height, int depth, std::span< uint8_t > data)
Create a bitmap with the given dimensions and data.
void Reformat(int format)
Reformat the bitmap to use a different pixel format.
void SetPalette(const SnesPalette &palette)
Set the palette for the bitmap using SNES palette format.
RAII timer for automatic timing management.
Represents a palette of colors for the Super Nintendo Entertainment System (SNES).
void AddColor(const SnesColor &color)
void DrawBitmap(Bitmap &bitmap, int border_offset, float scale)
bool DrawTileSelector(int size, int size_y=0)
void DrawRect(int x, int y, int w, int h, ImVec4 color)
void DrawBackground(ImVec2 canvas_size=ImVec2(0, 0))
void DrawGrid(float grid_step=64.0f, int tile_id_offset=8)
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...
static const SpriteOamLayout * GetLayout(uint8_t sprite_id)
Get the OAM layout for a sprite ID.
#define ICON_MD_FOLDER_OPEN
#define ICON_MD_PLAY_ARROW
#define ICON_MD_SKIP_NEXT
#define ICON_MD_SKIP_PREVIOUS
constexpr ImGuiTabBarFlags kSpriteTabBarFlags
bool InputHexWord(const char *label, uint16_t *data, float input_width, bool no_step)
ImGuiID GetID(const std::string &id)
bool InputHexByte(const char *label, uint8_t *data, float input_width, bool no_step)
std::string HexByte(uint8_t byte, HexStringParams params)
const std::string kSpriteDefaultNames[256]
PanelManager * panel_manager
std::vector< Frame > Frames
void Reset()
Reset all sprite data to defaults.
absl::Status Load(const std::string &filename)
Load a ZSM file from disk.
std::vector< AnimationGroup > animations
PaletteGroup sprites_aux1
PaletteGroup sprites_aux2
PaletteGroup sprites_aux3
PaletteGroup global_sprites
auto palette(int i) const
void AddPalette(SnesPalette pal)
void SelectableLabelWithNameEdit(bool selected, const std::string &type, const std::string &key, const std::string &defaultValue)
gfx::PaletteGroupMap palette_groups
Complete OAM layout for a vanilla sprite.
std::vector< SpriteOamEntry > tiles