- Added a new document, E5-debugging-guide.md, providing comprehensive strategies for debugging and testing the `yaze` application, including logging practices and testing frameworks. - Updated E2-development-guide.md to include a new section on debugging and testing, detailing quick debugging methods using command-line flags for specific editors and UI cards. - Enhanced the main application to support command-line flags for opening specific editors and cards on startup, improving the developer experience. - Refactored the Controller class to handle startup editor and card initialization based on command-line inputs.
337 lines
9.7 KiB
C++
337 lines
9.7 KiB
C++
#include "sprite_editor.h"
|
|
|
|
#include "app/gfx/performance_profiler.h"
|
|
#include "gui/ui_helpers.h"
|
|
#include "util/file_util.h"
|
|
#include "app/editor/sprite/zsprite.h"
|
|
#include "app/gfx/arena.h"
|
|
#include "app/gui/icons.h"
|
|
#include "app/gui/input.h"
|
|
#include "app/zelda3/sprite/sprite.h"
|
|
#include "util/hex.h"
|
|
|
|
namespace yaze {
|
|
namespace editor {
|
|
|
|
using ImGui::BeginTable;
|
|
using ImGui::Button;
|
|
using ImGui::EndTable;
|
|
using ImGui::Selectable;
|
|
using ImGui::Separator;
|
|
using ImGui::TableHeadersRow;
|
|
using ImGui::TableNextColumn;
|
|
using ImGui::TableNextRow;
|
|
using ImGui::TableSetupColumn;
|
|
using ImGui::Text;
|
|
|
|
void SpriteEditor::Initialize() {
|
|
// Register cards with EditorCardManager during initialization (once)
|
|
auto& card_manager = gui::EditorCardManager::Get();
|
|
|
|
card_manager.RegisterCard({
|
|
.card_id = "sprite.vanilla_editor",
|
|
.display_name = "Vanilla Sprites",
|
|
.icon = ICON_MD_SMART_TOY,
|
|
.category = "Sprite",
|
|
.shortcut_hint = "Alt+Shift+1",
|
|
.visibility_flag = &show_vanilla_editor_,
|
|
.priority = 10
|
|
});
|
|
|
|
card_manager.RegisterCard({
|
|
.card_id = "sprite.custom_editor",
|
|
.display_name = "Custom Sprites",
|
|
.icon = ICON_MD_ADD_CIRCLE,
|
|
.category = "Sprite",
|
|
.shortcut_hint = "Alt+Shift+2",
|
|
.visibility_flag = &show_custom_editor_,
|
|
.priority = 20
|
|
});
|
|
}
|
|
|
|
absl::Status SpriteEditor::Load() {
|
|
gfx::ScopedTimer timer("SpriteEditor::Load");
|
|
return absl::OkStatus();
|
|
}
|
|
|
|
absl::Status SpriteEditor::Update() {
|
|
if (rom()->is_loaded() && !sheets_loaded_) {
|
|
// Load the values for current_sheets_ array
|
|
sheets_loaded_ = true;
|
|
}
|
|
|
|
DrawToolset();
|
|
gui::VerticalSpacing(2.0f);
|
|
|
|
// Create session-aware cards (non-static for multi-session support)
|
|
gui::EditorCard vanilla_card(MakeCardTitle("Vanilla Sprites").c_str(), ICON_MD_PEST_CONTROL_RODENT);
|
|
gui::EditorCard custom_card(MakeCardTitle("Custom Sprites").c_str(), ICON_MD_ADD_MODERATOR);
|
|
|
|
if (show_vanilla_editor_) {
|
|
if (vanilla_card.Begin(&show_vanilla_editor_)) {
|
|
DrawVanillaSpriteEditor();
|
|
}
|
|
vanilla_card.End(); // ALWAYS call End after Begin
|
|
}
|
|
|
|
if (show_custom_editor_) {
|
|
if (custom_card.Begin(&show_custom_editor_)) {
|
|
DrawCustomSprites();
|
|
}
|
|
custom_card.End(); // ALWAYS call End after Begin
|
|
}
|
|
|
|
return status_.ok() ? absl::OkStatus() : status_;
|
|
}
|
|
|
|
void SpriteEditor::DrawToolset() {
|
|
static gui::Toolset toolbar;
|
|
toolbar.Begin();
|
|
|
|
if (toolbar.AddAction(ICON_MD_PEST_CONTROL_RODENT, "Vanilla Sprites")) {
|
|
show_vanilla_editor_ = !show_vanilla_editor_;
|
|
}
|
|
if (toolbar.AddAction(ICON_MD_ADD_MODERATOR, "Custom Sprites")) {
|
|
show_custom_editor_ = !show_custom_editor_;
|
|
}
|
|
|
|
toolbar.End();
|
|
}
|
|
|
|
|
|
void SpriteEditor::DrawVanillaSpriteEditor() {
|
|
if (ImGui::BeginTable("##SpriteCanvasTable", 3, ImGuiTableFlags_Resizable,
|
|
ImVec2(0, 0))) {
|
|
TableSetupColumn("Sprites List", ImGuiTableColumnFlags_WidthFixed, 256);
|
|
TableSetupColumn("Canvas", ImGuiTableColumnFlags_WidthStretch,
|
|
ImGui::GetContentRegionAvail().x);
|
|
TableSetupColumn("Tile Selector", ImGuiTableColumnFlags_WidthFixed, 256);
|
|
TableHeadersRow();
|
|
TableNextRow();
|
|
|
|
TableNextColumn();
|
|
DrawSpritesList();
|
|
|
|
TableNextColumn();
|
|
static int next_tab_id = 0;
|
|
|
|
if (ImGui::BeginTabBar("SpriteTabBar", kSpriteTabBarFlags)) {
|
|
if (ImGui::TabItemButton(ICON_MD_ADD, kSpriteTabBarFlags)) {
|
|
if (std::find(active_sprites_.begin(), active_sprites_.end(),
|
|
current_sprite_id_) != active_sprites_.end()) {
|
|
// Room is already open
|
|
next_tab_id++;
|
|
}
|
|
active_sprites_.push_back(next_tab_id++); // Add new tab
|
|
}
|
|
|
|
// Submit our regular tabs
|
|
for (int n = 0; n < active_sprites_.Size;) {
|
|
bool open = true;
|
|
|
|
if (active_sprites_[n] > sizeof(zelda3::kSpriteDefaultNames) / 4) {
|
|
active_sprites_.erase(active_sprites_.Data + n);
|
|
continue;
|
|
}
|
|
|
|
if (ImGui::BeginTabItem(
|
|
zelda3::kSpriteDefaultNames[active_sprites_[n]].data(), &open,
|
|
ImGuiTabItemFlags_None)) {
|
|
DrawSpriteCanvas();
|
|
ImGui::EndTabItem();
|
|
}
|
|
|
|
if (!open)
|
|
active_sprites_.erase(active_sprites_.Data + n);
|
|
else
|
|
n++;
|
|
}
|
|
|
|
ImGui::EndTabBar();
|
|
}
|
|
|
|
TableNextColumn();
|
|
if (sheets_loaded_) {
|
|
DrawCurrentSheets();
|
|
}
|
|
ImGui::EndTable();
|
|
}
|
|
}
|
|
|
|
void SpriteEditor::DrawSpriteCanvas() {
|
|
static bool flip_x = false;
|
|
static bool flip_y = false;
|
|
if (ImGui::BeginChild(gui::GetID("##SpriteCanvas"),
|
|
ImGui::GetContentRegionAvail(), true)) {
|
|
sprite_canvas_.DrawBackground();
|
|
sprite_canvas_.DrawContextMenu();
|
|
sprite_canvas_.DrawGrid();
|
|
sprite_canvas_.DrawOverlay();
|
|
|
|
// Draw a table with OAM configuration
|
|
// X, Y, Tile, Palette, Priority, Flip X, Flip Y
|
|
if (ImGui::BeginTable("##OAMTable", 7, ImGuiTableFlags_Resizable,
|
|
ImVec2(0, 0))) {
|
|
TableSetupColumn("X", ImGuiTableColumnFlags_WidthStretch);
|
|
TableSetupColumn("Y", ImGuiTableColumnFlags_WidthStretch);
|
|
TableSetupColumn("Tile", ImGuiTableColumnFlags_WidthStretch);
|
|
TableSetupColumn("Palette", ImGuiTableColumnFlags_WidthStretch);
|
|
TableSetupColumn("Priority", ImGuiTableColumnFlags_WidthStretch);
|
|
TableSetupColumn("Flip X", ImGuiTableColumnFlags_WidthStretch);
|
|
TableSetupColumn("Flip Y", ImGuiTableColumnFlags_WidthStretch);
|
|
TableHeadersRow();
|
|
TableNextRow();
|
|
|
|
TableNextColumn();
|
|
gui::InputHexWord("", &oam_config_.x);
|
|
|
|
TableNextColumn();
|
|
gui::InputHexWord("", &oam_config_.y);
|
|
|
|
TableNextColumn();
|
|
gui::InputHexByte("", &oam_config_.tile);
|
|
|
|
TableNextColumn();
|
|
gui::InputHexByte("", &oam_config_.palette);
|
|
|
|
TableNextColumn();
|
|
gui::InputHexByte("", &oam_config_.priority);
|
|
|
|
TableNextColumn();
|
|
if (ImGui::Checkbox("##XFlip", &flip_x)) {
|
|
oam_config_.flip_x = flip_x;
|
|
}
|
|
|
|
TableNextColumn();
|
|
if (ImGui::Checkbox("##YFlip", &flip_y)) {
|
|
oam_config_.flip_y = flip_y;
|
|
}
|
|
|
|
ImGui::EndTable();
|
|
}
|
|
|
|
DrawAnimationFrames();
|
|
|
|
DrawCustomSpritesMetadata();
|
|
|
|
ImGui::EndChild();
|
|
}
|
|
}
|
|
|
|
void SpriteEditor::DrawCurrentSheets() {
|
|
if (ImGui::BeginChild(gui::GetID("sheet_label"),
|
|
ImVec2(ImGui::GetContentRegionAvail().x, 0), true,
|
|
ImGuiWindowFlags_NoDecoration)) {
|
|
for (int i = 0; i < 8; i++) {
|
|
std::string sheet_label = absl::StrFormat("Sheet %d", i);
|
|
gui::InputHexByte(sheet_label.c_str(), ¤t_sheets_[i]);
|
|
if (i % 2 == 0) ImGui::SameLine();
|
|
}
|
|
|
|
graphics_sheet_canvas_.DrawBackground();
|
|
graphics_sheet_canvas_.DrawContextMenu();
|
|
graphics_sheet_canvas_.DrawTileSelector(32);
|
|
for (int i = 0; i < 8; i++) {
|
|
graphics_sheet_canvas_.DrawBitmap(
|
|
gfx::Arena::Get().gfx_sheets().at(current_sheets_[i]), 1,
|
|
(i * 0x40) + 1, 2);
|
|
}
|
|
graphics_sheet_canvas_.DrawGrid();
|
|
graphics_sheet_canvas_.DrawOverlay();
|
|
}
|
|
ImGui::EndChild();
|
|
}
|
|
|
|
void SpriteEditor::DrawSpritesList() {
|
|
if (ImGui::BeginChild(gui::GetID("##SpritesList"),
|
|
ImVec2(ImGui::GetContentRegionAvail().x, 0), true,
|
|
ImGuiWindowFlags_NoDecoration)) {
|
|
int i = 0;
|
|
for (const auto each_sprite_name : zelda3::kSpriteDefaultNames) {
|
|
rom()->resource_label()->SelectableLabelWithNameEdit(
|
|
current_sprite_id_ == i, "Sprite Names", util::HexByte(i),
|
|
zelda3::kSpriteDefaultNames[i].data());
|
|
if (ImGui::IsItemClicked()) {
|
|
current_sprite_id_ = i;
|
|
if (!active_sprites_.contains(i)) {
|
|
active_sprites_.push_back(i);
|
|
}
|
|
}
|
|
i++;
|
|
}
|
|
ImGui::EndChild();
|
|
}
|
|
}
|
|
|
|
void SpriteEditor::DrawAnimationFrames() {
|
|
if (ImGui::Button("Add Frame")) {
|
|
// Add a new frame
|
|
}
|
|
if (ImGui::Button("Remove Frame")) {
|
|
// Remove the current frame
|
|
}
|
|
}
|
|
|
|
void SpriteEditor::DrawCustomSprites() {
|
|
if (BeginTable("##CustomSpritesTable", 3,
|
|
ImGuiTableFlags_Resizable | ImGuiTableFlags_Borders |
|
|
ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable,
|
|
ImVec2(0, 0))) {
|
|
TableSetupColumn("Metadata", ImGuiTableColumnFlags_WidthFixed, 256);
|
|
TableSetupColumn("Canvas", ImGuiTableColumnFlags_WidthFixed, 256);
|
|
TableSetupColumn("TIlesheets", ImGuiTableColumnFlags_WidthFixed, 256);
|
|
|
|
TableHeadersRow();
|
|
TableNextRow();
|
|
TableNextColumn();
|
|
|
|
Separator();
|
|
DrawCustomSpritesMetadata();
|
|
|
|
TableNextColumn();
|
|
DrawSpriteCanvas();
|
|
|
|
TableNextColumn();
|
|
DrawCurrentSheets();
|
|
|
|
EndTable();
|
|
}
|
|
}
|
|
|
|
void SpriteEditor::DrawCustomSpritesMetadata() {
|
|
// ZSprite Maker format open file dialog
|
|
if (ImGui::Button("Open ZSprite")) {
|
|
// Open ZSprite file
|
|
std::string file_path = util::FileDialogWrapper::ShowOpenFileDialog();
|
|
if (!file_path.empty()) {
|
|
zsprite::ZSprite zsprite;
|
|
status_ = zsprite.Load(file_path);
|
|
if (status_.ok()) {
|
|
custom_sprites_.push_back(zsprite);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const auto custom_sprite : custom_sprites_) {
|
|
Selectable("%s", custom_sprite.sprName.c_str());
|
|
if (ImGui::IsItemClicked()) {
|
|
current_sprite_id_ = 256 + stoi(custom_sprite.property_sprid.Text);
|
|
if (!active_sprites_.contains(current_sprite_id_)) {
|
|
active_sprites_.push_back(current_sprite_id_);
|
|
}
|
|
}
|
|
Separator();
|
|
}
|
|
|
|
for (const auto custom_sprite : custom_sprites_) {
|
|
// Draw the custom sprite metadata
|
|
Text("Sprite ID: %s", custom_sprite.property_sprid.Text.c_str());
|
|
Text("Sprite Name: %s", custom_sprite.property_sprname.Text.c_str());
|
|
Text("Sprite Palette: %s", custom_sprite.property_palette.Text.c_str());
|
|
Separator();
|
|
}
|
|
}
|
|
|
|
} // namespace editor
|
|
} // namespace yaze
|