- Updated EditorManager to support new card-based editors, including Emulator, Hex, and Assembly editors. - Added centralized registration of editor cards with EditorCardManager, improving visibility management. - Implemented methods to retrieve editor types from categories and manage card visibility dynamically. - Enhanced initialization processes for various editors to ensure proper card registration and default visibility settings. Benefits: - Improved user experience by organizing editor cards and providing quick access to new editor functionalities. - Streamlined editor management, making it easier to switch between different editing contexts and enhancing overall workflow efficiency.
960 lines
32 KiB
C++
960 lines
32 KiB
C++
#include "graphics_editor.h"
|
|
|
|
#include <filesystem>
|
|
|
|
#include "absl/status/status.h"
|
|
#include "absl/status/statusor.h"
|
|
#include "absl/strings/str_cat.h"
|
|
#include "app/gui/ui_helpers.h"
|
|
#include "util/file_util.h"
|
|
#include "app/core/window.h"
|
|
#include "app/gfx/arena.h"
|
|
#include "app/gfx/bitmap.h"
|
|
#include "app/gfx/compression.h"
|
|
#include "app/gfx/scad_format.h"
|
|
#include "app/gfx/snes_palette.h"
|
|
#include "app/gfx/snes_tile.h"
|
|
#include "app/gui/canvas.h"
|
|
#include "app/gui/color.h"
|
|
#include "app/gui/icons.h"
|
|
#include "app/gui/input.h"
|
|
#include "app/gui/modules/asset_browser.h"
|
|
#include "app/gui/style.h"
|
|
#include "app/rom.h"
|
|
#include "app/gfx/performance/performance_profiler.h"
|
|
#include "imgui/imgui.h"
|
|
#include "imgui/misc/cpp/imgui_stdlib.h"
|
|
#include "imgui_memory_editor.h"
|
|
#include "util/log.h"
|
|
|
|
namespace yaze {
|
|
namespace editor {
|
|
|
|
using gfx::kPaletteGroupAddressesKeys;
|
|
using ImGui::Button;
|
|
using ImGui::InputInt;
|
|
using ImGui::InputText;
|
|
using ImGui::SameLine;
|
|
using ImGui::TableNextColumn;
|
|
|
|
constexpr ImGuiTableFlags kGfxEditTableFlags =
|
|
ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable |
|
|
ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable |
|
|
ImGuiTableFlags_SizingFixedFit;
|
|
|
|
void GraphicsEditor::Initialize() {
|
|
auto& card_manager = gui::EditorCardManager::Get();
|
|
|
|
card_manager.RegisterCard({.card_id = "graphics.sheet_editor", .display_name = "Sheet Editor",
|
|
.icon = ICON_MD_EDIT, .category = "Graphics",
|
|
.shortcut_hint = "Ctrl+Shift+1", .priority = 10});
|
|
card_manager.RegisterCard({.card_id = "graphics.sheet_browser", .display_name = "Sheet Browser",
|
|
.icon = ICON_MD_VIEW_LIST, .category = "Graphics",
|
|
.shortcut_hint = "Ctrl+Shift+2", .priority = 20});
|
|
card_manager.RegisterCard({.card_id = "graphics.player_animations", .display_name = "Player Animations",
|
|
.icon = ICON_MD_PERSON, .category = "Graphics",
|
|
.shortcut_hint = "Ctrl+Shift+3", .priority = 30});
|
|
card_manager.RegisterCard({.card_id = "graphics.prototype_viewer", .display_name = "Prototype Viewer",
|
|
.icon = ICON_MD_CONSTRUCTION, .category = "Graphics",
|
|
.shortcut_hint = "Ctrl+Shift+4", .priority = 40});
|
|
|
|
// Show sheet editor by default when Graphics Editor is activated
|
|
card_manager.ShowCard("graphics.sheet_editor");
|
|
}
|
|
|
|
absl::Status GraphicsEditor::Load() {
|
|
gfx::ScopedTimer timer("GraphicsEditor::Load");
|
|
|
|
// Initialize all graphics sheets with appropriate palettes from ROM
|
|
// This ensures textures are created for editing
|
|
if (rom()->is_loaded()) {
|
|
auto& sheets = gfx::Arena::Get().gfx_sheets();
|
|
|
|
// Apply default palettes to all sheets based on common SNES ROM structure
|
|
// Sheets 0-112: Use overworld/dungeon palettes
|
|
// Sheets 113-127: Use sprite palettes
|
|
// Sheets 128-222: Use auxiliary/menu palettes
|
|
|
|
LOG_INFO("GraphicsEditor", "Initializing textures for %d graphics sheets", kNumGfxSheets);
|
|
|
|
int sheets_queued = 0;
|
|
for (int i = 0; i < kNumGfxSheets; i++) {
|
|
if (!sheets[i].is_active() || !sheets[i].surface()) {
|
|
continue; // Skip inactive or surface-less sheets
|
|
}
|
|
|
|
// Palettes are now applied during ROM loading in LoadAllGraphicsData()
|
|
// Just queue texture creation for sheets that don't have textures yet
|
|
if (!sheets[i].texture()) {
|
|
gfx::Arena::Get().QueueTextureCommand(
|
|
gfx::Arena::TextureCommandType::CREATE, &sheets[i]);
|
|
sheets_queued++;
|
|
}
|
|
}
|
|
|
|
LOG_INFO("GraphicsEditor", "Queued texture creation for %d graphics sheets", sheets_queued);
|
|
}
|
|
|
|
return absl::OkStatus();
|
|
}
|
|
|
|
absl::Status GraphicsEditor::Update() {
|
|
auto& card_manager = gui::EditorCardManager::Get();
|
|
|
|
static gui::EditorCard sheet_editor_card("Sheet Editor", ICON_MD_EDIT);
|
|
static gui::EditorCard sheet_browser_card("Sheet Browser", ICON_MD_VIEW_LIST);
|
|
static gui::EditorCard player_anims_card("Player Animations", ICON_MD_PERSON);
|
|
static gui::EditorCard prototype_card("Prototype Viewer", ICON_MD_CONSTRUCTION);
|
|
|
|
sheet_editor_card.SetDefaultSize(900, 700);
|
|
sheet_browser_card.SetDefaultSize(400, 600);
|
|
player_anims_card.SetDefaultSize(500, 600);
|
|
prototype_card.SetDefaultSize(600, 500);
|
|
|
|
// Get visibility flags from card manager and pass to Begin()
|
|
if (sheet_editor_card.Begin(card_manager.GetVisibilityFlag("graphics.sheet_editor"))) {
|
|
status_ = UpdateGfxEdit();
|
|
sheet_editor_card.End();
|
|
}
|
|
|
|
if (sheet_browser_card.Begin(card_manager.GetVisibilityFlag("graphics.sheet_browser"))) {
|
|
if (asset_browser_.Initialized == false) {
|
|
asset_browser_.Initialize(gfx::Arena::Get().gfx_sheets());
|
|
}
|
|
asset_browser_.Draw(gfx::Arena::Get().gfx_sheets());
|
|
sheet_browser_card.End();
|
|
}
|
|
|
|
if (player_anims_card.Begin(card_manager.GetVisibilityFlag("graphics.player_animations"))) {
|
|
status_ = UpdateLinkGfxView();
|
|
player_anims_card.End();
|
|
}
|
|
|
|
if (prototype_card.Begin(card_manager.GetVisibilityFlag("graphics.prototype_viewer"))) {
|
|
status_ = UpdateScadView();
|
|
prototype_card.End();
|
|
}
|
|
|
|
CLEAR_AND_RETURN_STATUS(status_)
|
|
return absl::OkStatus();
|
|
}
|
|
|
|
absl::Status GraphicsEditor::UpdateGfxEdit() {
|
|
if (ImGui::BeginTable("##GfxEditTable", 3, kGfxEditTableFlags,
|
|
ImVec2(0, 0))) {
|
|
for (const auto& name :
|
|
{"Tilesheets", "Current Graphics", "Palette Controls"})
|
|
ImGui::TableSetupColumn(name);
|
|
|
|
ImGui::TableHeadersRow();
|
|
ImGui::TableNextColumn();
|
|
status_ = UpdateGfxSheetList();
|
|
|
|
ImGui::TableNextColumn();
|
|
if (rom()->is_loaded()) {
|
|
DrawGfxEditToolset();
|
|
status_ = UpdateGfxTabView();
|
|
}
|
|
|
|
ImGui::TableNextColumn();
|
|
if (rom()->is_loaded()) {
|
|
status_ = UpdatePaletteColumn();
|
|
}
|
|
}
|
|
ImGui::EndTable();
|
|
|
|
return absl::OkStatus();
|
|
}
|
|
|
|
/**
|
|
* @brief Draw the graphics editing toolset with enhanced ROM hacking features
|
|
*
|
|
* Enhanced Features:
|
|
* - Multi-tool selection for different editing modes
|
|
* - Real-time zoom controls for precise pixel editing
|
|
* - Sheet copy/paste operations for ROM graphics management
|
|
* - Color picker integration with SNES palette system
|
|
* - Tile size controls for 8x8 and 16x16 SNES tiles
|
|
*
|
|
* Performance Notes:
|
|
* - Toolset updates are batched to minimize ImGui overhead
|
|
* - Color buttons use cached palette data for fast rendering
|
|
* - Zoom controls update canvas scaling without full redraw
|
|
*/
|
|
void GraphicsEditor::DrawGfxEditToolset() {
|
|
if (ImGui::BeginTable("##GfxEditToolset", 9, ImGuiTableFlags_SizingFixedFit,
|
|
ImVec2(0, 0))) {
|
|
for (const auto& name :
|
|
{"Select", "Pencil", "Fill", "Copy Sheet", "Paste Sheet", "Zoom Out",
|
|
"Zoom In", "Current Color", "Tile Size"})
|
|
ImGui::TableSetupColumn(name);
|
|
|
|
TableNextColumn();
|
|
if (Button(ICON_MD_SELECT_ALL)) {
|
|
gfx_edit_mode_ = GfxEditMode::kSelect;
|
|
}
|
|
|
|
TableNextColumn();
|
|
if (Button(ICON_MD_DRAW)) {
|
|
gfx_edit_mode_ = GfxEditMode::kPencil;
|
|
}
|
|
HOVER_HINT("Draw with current color");
|
|
|
|
TableNextColumn();
|
|
if (Button(ICON_MD_FORMAT_COLOR_FILL)) {
|
|
gfx_edit_mode_ = GfxEditMode::kFill;
|
|
}
|
|
HOVER_HINT("Fill with current color");
|
|
|
|
TableNextColumn();
|
|
if (Button(ICON_MD_CONTENT_COPY)) {
|
|
status_ = absl::UnimplementedError("PNG export functionality removed");
|
|
}
|
|
HOVER_HINT("Copy to Clipboard");
|
|
|
|
TableNextColumn();
|
|
if (Button(ICON_MD_CONTENT_PASTE)) {
|
|
status_ = absl::UnimplementedError("PNG import functionality removed");
|
|
}
|
|
HOVER_HINT("Paste from Clipboard");
|
|
|
|
TableNextColumn();
|
|
if (Button(ICON_MD_ZOOM_OUT)) {
|
|
if (current_scale_ >= 0.0f) {
|
|
current_scale_ -= 1.0f;
|
|
}
|
|
}
|
|
|
|
TableNextColumn();
|
|
if (Button(ICON_MD_ZOOM_IN)) {
|
|
if (current_scale_ <= 16.0f) {
|
|
current_scale_ += 1.0f;
|
|
}
|
|
}
|
|
|
|
TableNextColumn();
|
|
// Enhanced palette color picker with SNES-specific features
|
|
auto bitmap = gfx::Arena::Get().gfx_sheets()[current_sheet_];
|
|
auto palette = bitmap.palette();
|
|
|
|
// Display palette colors in a grid layout for better ROM hacking workflow
|
|
for (int i = 0; i < palette.size(); i++) {
|
|
if (i > 0 && i % 8 == 0) {
|
|
ImGui::NewLine(); // New row every 8 colors (SNES palette standard)
|
|
}
|
|
ImGui::SameLine();
|
|
|
|
// Convert SNES color to ImGui format with proper scaling
|
|
auto color = ImVec4(palette[i].rgb().x / 255.0f, palette[i].rgb().y / 255.0f,
|
|
palette[i].rgb().z / 255.0f, 1.0f);
|
|
|
|
// Enhanced color button with tooltip showing SNES color value
|
|
if (ImGui::ColorButton(absl::StrFormat("Palette Color %d", i).c_str(),
|
|
color, ImGuiColorEditFlags_NoTooltip)) {
|
|
current_color_ = color;
|
|
}
|
|
|
|
// Add tooltip with SNES color information
|
|
if (ImGui::IsItemHovered()) {
|
|
ImGui::SetTooltip("SNES Color: $%04X\nRGB: (%d, %d, %d)",
|
|
palette[i].snes(),
|
|
static_cast<int>(palette[i].rgb().x),
|
|
static_cast<int>(palette[i].rgb().y),
|
|
static_cast<int>(palette[i].rgb().z));
|
|
}
|
|
}
|
|
|
|
TableNextColumn();
|
|
gui::InputHexByte("Tile Size", &tile_size_);
|
|
|
|
ImGui::EndTable();
|
|
}
|
|
}
|
|
|
|
absl::Status GraphicsEditor::UpdateGfxSheetList() {
|
|
ImGui::BeginChild(
|
|
"##GfxEditChild", ImVec2(0, 0), true,
|
|
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysVerticalScrollbar);
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
|
|
// TODO: Update the interaction for multi select on sheets
|
|
static ImGuiSelectionBasicStorage selection;
|
|
ImGuiMultiSelectFlags flags =
|
|
ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_BoxSelect1d;
|
|
ImGuiMultiSelectIO* ms_io =
|
|
ImGui::BeginMultiSelect(flags, selection.Size, kNumGfxSheets);
|
|
selection.ApplyRequests(ms_io);
|
|
ImGuiListClipper clipper;
|
|
clipper.Begin(kNumGfxSheets);
|
|
if (ms_io->RangeSrcItem != -1)
|
|
clipper.IncludeItemByIndex(
|
|
(int)ms_io->RangeSrcItem); // Ensure RangeSrc item is not clipped.
|
|
|
|
int key = 0;
|
|
for (auto& value : gfx::Arena::Get().gfx_sheets()) {
|
|
ImGui::BeginChild(absl::StrFormat("##GfxSheet%02X", key).c_str(),
|
|
ImVec2(0x100 + 1, 0x40 + 1), true,
|
|
ImGuiWindowFlags_NoDecoration);
|
|
ImGui::PopStyleVar();
|
|
|
|
graphics_bin_canvas_.DrawBackground(ImVec2(0x100 + 1, 0x40 + 1));
|
|
graphics_bin_canvas_.DrawContextMenu();
|
|
if (value.is_active()) {
|
|
// Ensure texture exists for active sheets
|
|
if (!value.texture() && value.surface()) {
|
|
gfx::Arena::Get().QueueTextureCommand(
|
|
gfx::Arena::TextureCommandType::CREATE, &value);
|
|
}
|
|
|
|
auto texture = value.texture();
|
|
if (texture) {
|
|
graphics_bin_canvas_.draw_list()->AddImage(
|
|
(ImTextureID)(intptr_t)texture,
|
|
ImVec2(graphics_bin_canvas_.zero_point().x + 2,
|
|
graphics_bin_canvas_.zero_point().y + 2),
|
|
ImVec2(graphics_bin_canvas_.zero_point().x +
|
|
value.width() * sheet_scale_,
|
|
graphics_bin_canvas_.zero_point().y +
|
|
value.height() * sheet_scale_));
|
|
|
|
if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) {
|
|
current_sheet_ = key;
|
|
open_sheets_.insert(key);
|
|
}
|
|
|
|
// Add a slightly transparent rectangle behind the text
|
|
ImVec2 text_pos(graphics_bin_canvas_.zero_point().x + 2,
|
|
graphics_bin_canvas_.zero_point().y + 2);
|
|
ImVec2 text_size =
|
|
ImGui::CalcTextSize(absl::StrFormat("%02X", key).c_str());
|
|
ImVec2 rect_min(text_pos.x, text_pos.y);
|
|
ImVec2 rect_max(text_pos.x + text_size.x, text_pos.y + text_size.y);
|
|
|
|
graphics_bin_canvas_.draw_list()->AddRectFilled(rect_min, rect_max,
|
|
IM_COL32(0, 125, 0, 128));
|
|
|
|
graphics_bin_canvas_.draw_list()->AddText(
|
|
text_pos, IM_COL32(125, 255, 125, 255),
|
|
absl::StrFormat("%02X", key).c_str());
|
|
}
|
|
key++;
|
|
}
|
|
graphics_bin_canvas_.DrawGrid(16.0f);
|
|
graphics_bin_canvas_.DrawOverlay();
|
|
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
|
|
ImGui::EndChild();
|
|
}
|
|
ImGui::PopStyleVar();
|
|
ms_io = ImGui::EndMultiSelect();
|
|
selection.ApplyRequests(ms_io);
|
|
ImGui::EndChild();
|
|
return absl::OkStatus();
|
|
}
|
|
|
|
absl::Status GraphicsEditor::UpdateGfxTabView() {
|
|
gfx::ScopedTimer timer("graphics_editor_update_gfx_tab_view");
|
|
|
|
static int next_tab_id = 0;
|
|
constexpr ImGuiTabBarFlags kGfxEditTabBarFlags =
|
|
ImGuiTabBarFlags_AutoSelectNewTabs | ImGuiTabBarFlags_Reorderable |
|
|
ImGuiTabBarFlags_FittingPolicyResizeDown |
|
|
ImGuiTabBarFlags_TabListPopupButton;
|
|
|
|
if (ImGui::BeginTabBar("##GfxEditTabBar", kGfxEditTabBarFlags)) {
|
|
if (ImGui::TabItemButton(ICON_MD_ADD, ImGuiTabItemFlags_Trailing |
|
|
ImGuiTabItemFlags_NoTooltip)) {
|
|
open_sheets_.insert(next_tab_id++);
|
|
}
|
|
|
|
for (auto& sheet_id : open_sheets_) {
|
|
bool open = true;
|
|
if (ImGui::BeginTabItem(absl::StrFormat("%d", sheet_id).c_str(), &open,
|
|
ImGuiTabItemFlags_None)) {
|
|
current_sheet_ = sheet_id;
|
|
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
|
|
release_queue_.push(sheet_id);
|
|
}
|
|
if (ImGui::IsItemHovered()) {
|
|
if (ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
|
|
release_queue_.push(sheet_id);
|
|
child_window_sheets_.insert(sheet_id);
|
|
}
|
|
}
|
|
|
|
const auto child_id =
|
|
absl::StrFormat("##GfxEditPaletteChildWindow%d", sheet_id);
|
|
ImGui::BeginChild(child_id.c_str(), ImVec2(0, 0), true,
|
|
ImGuiWindowFlags_NoDecoration |
|
|
ImGuiWindowFlags_AlwaysVerticalScrollbar |
|
|
ImGuiWindowFlags_AlwaysHorizontalScrollbar);
|
|
|
|
gfx::Bitmap& current_bitmap =
|
|
gfx::Arena::Get().mutable_gfx_sheets()->at(sheet_id);
|
|
|
|
auto draw_tile_event = [&]() {
|
|
current_sheet_canvas_.DrawTileOnBitmap(tile_size_, ¤t_bitmap,
|
|
current_color_);
|
|
// Notify Arena that this sheet has been modified for cross-editor synchronization
|
|
gfx::Arena::Get().NotifySheetModified(sheet_id);
|
|
};
|
|
|
|
current_sheet_canvas_.UpdateColorPainter(
|
|
nullptr, gfx::Arena::Get().mutable_gfx_sheets()->at(sheet_id),
|
|
current_color_, draw_tile_event, tile_size_, current_scale_);
|
|
|
|
// Notify Arena that this sheet has been modified for cross-editor synchronization
|
|
gfx::Arena::Get().NotifySheetModified(sheet_id);
|
|
|
|
ImGui::EndChild();
|
|
ImGui::EndTabItem();
|
|
}
|
|
|
|
if (!open) release_queue_.push(sheet_id);
|
|
}
|
|
|
|
ImGui::EndTabBar();
|
|
}
|
|
|
|
// Release any tabs that were closed
|
|
while (!release_queue_.empty()) {
|
|
auto sheet_id = release_queue_.top();
|
|
open_sheets_.erase(sheet_id);
|
|
release_queue_.pop();
|
|
}
|
|
|
|
// Draw any child windows that were created
|
|
if (!child_window_sheets_.empty()) {
|
|
int id_to_release = -1;
|
|
for (const auto& id : child_window_sheets_) {
|
|
bool active = true;
|
|
ImGui::SetNextWindowPos(ImGui::GetIO().MousePos, ImGuiCond_Once);
|
|
ImGui::SetNextWindowSize(ImVec2(0x100 + 1 * 16, 0x40 + 1 * 16),
|
|
ImGuiCond_Once);
|
|
ImGui::Begin(absl::StrFormat("##GfxEditPaletteChildWindow%d", id).c_str(),
|
|
&active, ImGuiWindowFlags_AlwaysUseWindowPadding);
|
|
current_sheet_ = id;
|
|
// ImVec2(0x100, 0x40),
|
|
current_sheet_canvas_.UpdateColorPainter(
|
|
nullptr, gfx::Arena::Get().mutable_gfx_sheets()->at(id), current_color_,
|
|
[&]() {
|
|
|
|
},
|
|
tile_size_, current_scale_);
|
|
ImGui::End();
|
|
|
|
if (active == false) {
|
|
id_to_release = id;
|
|
}
|
|
}
|
|
if (id_to_release != -1) {
|
|
child_window_sheets_.erase(id_to_release);
|
|
}
|
|
}
|
|
|
|
return absl::OkStatus();
|
|
}
|
|
|
|
absl::Status GraphicsEditor::UpdatePaletteColumn() {
|
|
if (rom()->is_loaded()) {
|
|
auto palette_group = *rom()->palette_group().get_group(
|
|
kPaletteGroupAddressesKeys[edit_palette_group_name_index_]);
|
|
auto palette = palette_group.palette(edit_palette_index_);
|
|
gui::TextWithSeparators("ROM Palette Management");
|
|
|
|
// Quick palette presets for common SNES graphics types
|
|
ImGui::Text("Quick Presets:");
|
|
if (ImGui::Button("Overworld")) {
|
|
edit_palette_group_name_index_ = 0; // Dungeon Main
|
|
edit_palette_index_ = 0;
|
|
refresh_graphics_ = true;
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Dungeon")) {
|
|
edit_palette_group_name_index_ = 0; // Dungeon Main
|
|
edit_palette_index_ = 1;
|
|
refresh_graphics_ = true;
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Sprites")) {
|
|
edit_palette_group_name_index_ = 4; // Sprites Aux1
|
|
edit_palette_index_ = 0;
|
|
refresh_graphics_ = true;
|
|
}
|
|
ImGui::Separator();
|
|
|
|
// Apply current palette to current sheet
|
|
if (ImGui::Button("Apply to Current Sheet") && !open_sheets_.empty()) {
|
|
refresh_graphics_ = true;
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Apply to All Sheets")) {
|
|
// Apply current palette to all active sheets
|
|
for (int i = 0; i < kNumGfxSheets; i++) {
|
|
auto& sheet = gfx::Arena::Get().mutable_gfx_sheets()->data()[i];
|
|
if (sheet.is_active() && sheet.surface()) {
|
|
sheet.SetPaletteWithTransparent(palette, edit_palette_sub_index_);
|
|
// Notify Arena that this sheet has been modified
|
|
gfx::Arena::Get().NotifySheetModified(i);
|
|
}
|
|
}
|
|
}
|
|
ImGui::Separator();
|
|
|
|
ImGui::SetNextItemWidth(150.f);
|
|
ImGui::Combo("Palette Group", (int*)&edit_palette_group_name_index_,
|
|
kPaletteGroupAddressesKeys,
|
|
IM_ARRAYSIZE(kPaletteGroupAddressesKeys));
|
|
ImGui::SetNextItemWidth(100.f);
|
|
gui::InputHex("Palette Index", &edit_palette_index_);
|
|
ImGui::SetNextItemWidth(100.f);
|
|
gui::InputHex("Sub-Palette", &edit_palette_sub_index_);
|
|
|
|
gui::SelectablePalettePipeline(edit_palette_sub_index_, refresh_graphics_,
|
|
palette);
|
|
|
|
if (refresh_graphics_ && !open_sheets_.empty()) {
|
|
auto& current = gfx::Arena::Get().mutable_gfx_sheets()->data()[current_sheet_];
|
|
if (current.is_active() && current.surface()) {
|
|
current.SetPaletteWithTransparent(palette, edit_palette_sub_index_);
|
|
// Notify Arena that this sheet has been modified
|
|
gfx::Arena::Get().NotifySheetModified(current_sheet_);
|
|
}
|
|
refresh_graphics_ = false;
|
|
}
|
|
}
|
|
return absl::OkStatus();
|
|
}
|
|
|
|
absl::Status GraphicsEditor::UpdateLinkGfxView() {
|
|
if (ImGui::BeginTable("##PlayerAnimationTable", 3, kGfxEditTableFlags,
|
|
ImVec2(0, 0))) {
|
|
for (const auto& name : {"Canvas", "Animation Steps", "Properties"})
|
|
ImGui::TableSetupColumn(name);
|
|
|
|
ImGui::TableHeadersRow();
|
|
|
|
ImGui::TableNextColumn();
|
|
link_canvas_.DrawBackground();
|
|
link_canvas_.DrawGrid(16.0f);
|
|
|
|
int i = 0;
|
|
for (auto& link_sheet : link_sheets_) {
|
|
int x_offset = 0;
|
|
int y_offset = gfx::kTilesheetHeight * i * 4;
|
|
link_canvas_.DrawContextMenu();
|
|
link_canvas_.DrawBitmap(link_sheet, x_offset, y_offset, 4);
|
|
i++;
|
|
}
|
|
link_canvas_.DrawOverlay();
|
|
link_canvas_.DrawGrid();
|
|
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("Placeholder");
|
|
|
|
ImGui::TableNextColumn();
|
|
if (ImGui::Button("Load Link Graphics (Experimental)")) {
|
|
if (rom()->is_loaded()) {
|
|
// Load Links graphics from the ROM
|
|
ASSIGN_OR_RETURN(link_sheets_, LoadLinkGraphics(*rom()));
|
|
|
|
// Split it into the pose data frames
|
|
// Create an animation step display for the poses
|
|
// Allow the user to modify the frames used in an anim step
|
|
// LinkOAM_AnimationSteps:
|
|
// #_0D85FB
|
|
}
|
|
}
|
|
}
|
|
ImGui::EndTable();
|
|
|
|
return absl::OkStatus();
|
|
}
|
|
|
|
absl::Status GraphicsEditor::UpdateScadView() {
|
|
if (open_memory_editor_) {
|
|
ImGui::Begin("Memory Editor", &open_memory_editor_);
|
|
RETURN_IF_ERROR(DrawMemoryEditor())
|
|
ImGui::End();
|
|
}
|
|
|
|
constexpr ImGuiTableFlags kGfxEditFlags = ImGuiTableFlags_Reorderable |
|
|
ImGuiTableFlags_Resizable |
|
|
ImGuiTableFlags_SizingStretchSame;
|
|
|
|
BEGIN_TABLE("#gfxEditTable", 4, kGfxEditFlags)
|
|
SETUP_COLUMN("File Import (BIN, CGX, ROM)")
|
|
SETUP_COLUMN("Palette (COL)")
|
|
ImGui::TableSetupColumn("Tilemaps and Objects (SCR, PNL, OBJ)",
|
|
ImGuiTableColumnFlags_WidthFixed);
|
|
SETUP_COLUMN("Graphics Preview")
|
|
TABLE_HEADERS()
|
|
NEXT_COLUMN() {
|
|
status_ = DrawCgxImport();
|
|
status_ = DrawClipboardImport();
|
|
status_ = DrawFileImport();
|
|
status_ = DrawExperimentalFeatures();
|
|
}
|
|
|
|
NEXT_COLUMN() { status_ = DrawPaletteControls(); }
|
|
|
|
NEXT_COLUMN()
|
|
gui::BitmapCanvasPipeline(scr_canvas_, scr_bitmap_, 0x200, 0x200, 0x20,
|
|
scr_loaded_, false, 0);
|
|
status_ = DrawScrImport();
|
|
|
|
NEXT_COLUMN()
|
|
if (super_donkey_) {
|
|
// TODO: Implement the Super Donkey 1 graphics decompression
|
|
// if (refresh_graphics_) {
|
|
// for (int i = 0; i < kNumGfxSheets; i++) {
|
|
// status_ = graphics_bin_[i].SetPalette(
|
|
// col_file_palette_group_[current_palette_index_]);
|
|
// Renderer::Get().UpdateBitmap(&graphics_bin_[i]);
|
|
// }
|
|
// refresh_graphics_ = false;
|
|
// }
|
|
// Load the full graphics space from `super_donkey_1.bin`
|
|
// gui::GraphicsBinCanvasPipeline(0x100, 0x40, 0x20, num_sheets_to_load_, 3,
|
|
// super_donkey_, graphics_bin_);
|
|
} else if (cgx_loaded_ && col_file_) {
|
|
// Load the CGX graphics
|
|
gui::BitmapCanvasPipeline(import_canvas_, cgx_bitmap_, 0x100, 16384, 0x20,
|
|
cgx_loaded_, true, 5);
|
|
} else {
|
|
// Load the BIN/Clipboard Graphics
|
|
gui::BitmapCanvasPipeline(import_canvas_, bin_bitmap_, 0x100, 16384, 0x20,
|
|
gfx_loaded_, true, 2);
|
|
}
|
|
END_TABLE()
|
|
|
|
return absl::OkStatus();
|
|
}
|
|
|
|
absl::Status GraphicsEditor::DrawCgxImport() {
|
|
gui::TextWithSeparators("Cgx Import");
|
|
InputInt("BPP", ¤t_bpp_);
|
|
|
|
InputText("##CGXFile", &cgx_file_name_);
|
|
SameLine();
|
|
|
|
if (ImGui::Button("Open CGX")) {
|
|
auto filename = util::FileDialogWrapper::ShowOpenFileDialog();
|
|
cgx_file_name_ = filename;
|
|
cgx_file_path_ = std::filesystem::absolute(filename).string();
|
|
is_open_ = true;
|
|
cgx_loaded_ = true;
|
|
}
|
|
|
|
if (ImGui::Button("Copy CGX Path")) {
|
|
ImGui::SetClipboardText(cgx_file_path_.c_str());
|
|
}
|
|
|
|
if (ImGui::Button("Load CGX Data")) {
|
|
status_ = gfx::LoadCgx(current_bpp_, cgx_file_path_, cgx_data_,
|
|
decoded_cgx_, extra_cgx_data_);
|
|
|
|
cgx_bitmap_.Create(0x80, 0x200, 8, decoded_cgx_);
|
|
if (col_file_) {
|
|
cgx_bitmap_.SetPalette(decoded_col_);
|
|
gfx::Arena::Get().QueueTextureCommand(
|
|
gfx::Arena::TextureCommandType::UPDATE, &cgx_bitmap_);
|
|
}
|
|
}
|
|
|
|
return absl::OkStatus();
|
|
}
|
|
|
|
absl::Status GraphicsEditor::DrawScrImport() {
|
|
InputText("##ScrFile", &scr_file_name_);
|
|
|
|
if (ImGui::Button("Open SCR")) {
|
|
auto filename = util::FileDialogWrapper::ShowOpenFileDialog();
|
|
scr_file_name_ = filename;
|
|
scr_file_path_ = std::filesystem::absolute(filename).string();
|
|
is_open_ = true;
|
|
scr_loaded_ = true;
|
|
}
|
|
|
|
InputInt("SCR Mod", &scr_mod_value_);
|
|
|
|
if (ImGui::Button("Load Scr Data")) {
|
|
status_ = gfx::LoadScr(scr_file_path_, scr_mod_value_, scr_data_);
|
|
|
|
decoded_scr_data_.resize(0x100 * 0x100);
|
|
status_ = gfx::DrawScrWithCgx(current_bpp_, scr_data_, decoded_scr_data_,
|
|
decoded_cgx_);
|
|
|
|
scr_bitmap_.Create(0x100, 0x100, 8, decoded_scr_data_);
|
|
if (scr_loaded_) {
|
|
scr_bitmap_.SetPalette(decoded_col_);
|
|
gfx::Arena::Get().QueueTextureCommand(
|
|
gfx::Arena::TextureCommandType::UPDATE, &scr_bitmap_);
|
|
}
|
|
}
|
|
|
|
return absl::OkStatus();
|
|
}
|
|
|
|
absl::Status GraphicsEditor::DrawPaletteControls() {
|
|
gui::TextWithSeparators("COL Import");
|
|
InputText("##ColFile", &col_file_name_);
|
|
SameLine();
|
|
|
|
if (ImGui::Button("Open COL")) {
|
|
auto filename = util::FileDialogWrapper::ShowOpenFileDialog();
|
|
col_file_name_ = filename;
|
|
col_file_path_ = std::filesystem::absolute(filename).string();
|
|
status_ = temp_rom_.LoadFromFile(col_file_path_,
|
|
/*z3_load=*/false);
|
|
auto col_data_ = gfx::GetColFileData(temp_rom_.mutable_data());
|
|
if (col_file_palette_group_.size() != 0) {
|
|
col_file_palette_group_.clear();
|
|
}
|
|
auto col_file_palette_group_status =
|
|
gfx::CreatePaletteGroupFromColFile(col_data_);
|
|
if (col_file_palette_group_status.ok()) {
|
|
col_file_palette_group_ = col_file_palette_group_status.value();
|
|
}
|
|
col_file_palette_ = gfx::SnesPalette(col_data_);
|
|
|
|
// gigaleak dev format based code
|
|
decoded_col_ = gfx::DecodeColFile(col_file_path_);
|
|
col_file_ = true;
|
|
is_open_ = true;
|
|
}
|
|
HOVER_HINT(".COL, .BAK");
|
|
|
|
if (ImGui::Button("Copy Col Path")) {
|
|
ImGui::SetClipboardText(col_file_path_.c_str());
|
|
}
|
|
|
|
if (rom()->is_loaded()) {
|
|
gui::TextWithSeparators("ROM Palette");
|
|
gui::InputHex("Palette Index", ¤t_palette_index_);
|
|
ImGui::Combo("Palette", ¤t_palette_, kPaletteGroupAddressesKeys,
|
|
IM_ARRAYSIZE(kPaletteGroupAddressesKeys));
|
|
}
|
|
|
|
if (col_file_palette_.size() != 0) {
|
|
gui::SelectablePalettePipeline(current_palette_index_, refresh_graphics_,
|
|
col_file_palette_);
|
|
}
|
|
|
|
return absl::OkStatus();
|
|
}
|
|
|
|
absl::Status GraphicsEditor::DrawObjImport() {
|
|
gui::TextWithSeparators("OBJ Import");
|
|
|
|
InputText("##ObjFile", &obj_file_path_);
|
|
SameLine();
|
|
|
|
if (ImGui::Button("Open OBJ")) {
|
|
auto filename = util::FileDialogWrapper::ShowOpenFileDialog();
|
|
obj_file_path_ = std::filesystem::absolute(filename).string();
|
|
status_ = temp_rom_.LoadFromFile(obj_file_path_);
|
|
is_open_ = true;
|
|
obj_loaded_ = true;
|
|
}
|
|
HOVER_HINT(".OBJ, .BAK");
|
|
|
|
return absl::OkStatus();
|
|
}
|
|
|
|
absl::Status GraphicsEditor::DrawTilemapImport() {
|
|
gui::TextWithSeparators("Tilemap Import");
|
|
|
|
InputText("##TMapFile", &tilemap_file_path_);
|
|
SameLine();
|
|
|
|
if (ImGui::Button("Open Tilemap")) {
|
|
auto filename = util::FileDialogWrapper::ShowOpenFileDialog();
|
|
tilemap_file_path_ = std::filesystem::absolute(filename).string();
|
|
status_ = tilemap_rom_.LoadFromFile(tilemap_file_path_);
|
|
status_ = tilemap_rom_.LoadFromFile(tilemap_file_path_);
|
|
|
|
// Extract the high and low bytes from the file.
|
|
auto decomp_sheet = gfx::lc_lz2::DecompressV2(tilemap_rom_.data(),
|
|
gfx::lc_lz2::kNintendoMode1);
|
|
tilemap_loaded_ = true;
|
|
is_open_ = true;
|
|
}
|
|
HOVER_HINT(".DAT, .BIN, .HEX");
|
|
|
|
return absl::OkStatus();
|
|
}
|
|
|
|
absl::Status GraphicsEditor::DrawFileImport() {
|
|
gui::TextWithSeparators("BIN Import");
|
|
|
|
InputText("##ROMFile", &file_path_);
|
|
SameLine();
|
|
|
|
if (ImGui::Button("Open BIN")) {
|
|
auto filename = util::FileDialogWrapper::ShowOpenFileDialog();
|
|
file_path_ = filename;
|
|
status_ = temp_rom_.LoadFromFile(file_path_);
|
|
is_open_ = true;
|
|
}
|
|
HOVER_HINT(".BIN, .HEX");
|
|
|
|
if (Button("Copy File Path")) {
|
|
ImGui::SetClipboardText(file_path_.c_str());
|
|
}
|
|
|
|
gui::InputHex("BIN Offset", ¤t_offset_);
|
|
gui::InputHex("BIN Size", &bin_size_);
|
|
|
|
if (Button("Decompress BIN")) {
|
|
if (file_path_.empty()) {
|
|
return absl::InvalidArgumentError(
|
|
"Please select a file before decompressing.");
|
|
}
|
|
RETURN_IF_ERROR(DecompressImportData(bin_size_))
|
|
}
|
|
|
|
return absl::OkStatus();
|
|
}
|
|
|
|
absl::Status GraphicsEditor::DrawClipboardImport() {
|
|
gui::TextWithSeparators("Clipboard Import");
|
|
if (Button("Paste From Clipboard")) {
|
|
const char* text = ImGui::GetClipboardText();
|
|
if (text) {
|
|
const auto clipboard_data =
|
|
std::vector<uint8_t>(text, text + strlen(text));
|
|
ImGui::MemFree((void*)text);
|
|
status_ = temp_rom_.LoadFromData(clipboard_data);
|
|
is_open_ = true;
|
|
open_memory_editor_ = true;
|
|
}
|
|
}
|
|
gui::InputHex("Offset", &clipboard_offset_);
|
|
gui::InputHex("Size", &clipboard_size_);
|
|
gui::InputHex("Num Sheets", &num_sheets_to_load_);
|
|
|
|
if (Button("Decompress Clipboard Data")) {
|
|
if (temp_rom_.is_loaded()) {
|
|
status_ = DecompressImportData(0x40000);
|
|
} else {
|
|
status_ = absl::InvalidArgumentError(
|
|
"Please paste data into the clipboard before "
|
|
"decompressing.");
|
|
}
|
|
}
|
|
|
|
return absl::OkStatus();
|
|
}
|
|
|
|
absl::Status GraphicsEditor::DrawExperimentalFeatures() {
|
|
gui::TextWithSeparators("Experimental");
|
|
if (Button("Decompress Super Donkey Full")) {
|
|
if (file_path_.empty()) {
|
|
return absl::InvalidArgumentError(
|
|
"Please select `super_donkey_1.bin` before "
|
|
"importing.");
|
|
}
|
|
RETURN_IF_ERROR(DecompressSuperDonkey())
|
|
}
|
|
ImGui::SetItemTooltip(
|
|
"Requires `super_donkey_1.bin` to be imported under the "
|
|
"BIN import section.");
|
|
return absl::OkStatus();
|
|
}
|
|
|
|
absl::Status GraphicsEditor::DrawMemoryEditor() {
|
|
std::string title = "Memory Editor";
|
|
if (is_open_) {
|
|
static MemoryEditor mem_edit;
|
|
mem_edit.DrawWindow(title.c_str(), temp_rom_.mutable_data(),
|
|
temp_rom_.size());
|
|
}
|
|
return absl::OkStatus();
|
|
}
|
|
|
|
absl::Status GraphicsEditor::DecompressImportData(int size) {
|
|
ASSIGN_OR_RETURN(import_data_, gfx::lc_lz2::DecompressV2(
|
|
temp_rom_.data(), current_offset_, size));
|
|
|
|
auto converted_sheet = gfx::SnesTo8bppSheet(import_data_, 3);
|
|
bin_bitmap_.Create(gfx::kTilesheetWidth, 0x2000, gfx::kTilesheetDepth,
|
|
converted_sheet);
|
|
|
|
if (rom()->is_loaded()) {
|
|
auto palette_group = rom()->palette_group().overworld_animated;
|
|
z3_rom_palette_ = palette_group[current_palette_];
|
|
if (col_file_) {
|
|
bin_bitmap_.SetPalette(col_file_palette_);
|
|
} else {
|
|
bin_bitmap_.SetPalette(z3_rom_palette_);
|
|
}
|
|
}
|
|
|
|
gfx::Arena::Get().QueueTextureCommand(
|
|
gfx::Arena::TextureCommandType::UPDATE, &bin_bitmap_);
|
|
gfx_loaded_ = true;
|
|
|
|
return absl::OkStatus();
|
|
}
|
|
|
|
absl::Status GraphicsEditor::DecompressSuperDonkey() {
|
|
int i = 0;
|
|
for (const auto& offset : kSuperDonkeyTiles) {
|
|
int offset_value =
|
|
std::stoi(offset, nullptr, 16); // convert hex string to int
|
|
ASSIGN_OR_RETURN(
|
|
auto decompressed_data,
|
|
gfx::lc_lz2::DecompressV2(temp_rom_.data(), offset_value, 0x1000));
|
|
auto converted_sheet = gfx::SnesTo8bppSheet(decompressed_data, 3);
|
|
gfx_sheets_[i] = gfx::Bitmap(gfx::kTilesheetWidth, gfx::kTilesheetHeight,
|
|
gfx::kTilesheetDepth, converted_sheet);
|
|
if (col_file_) {
|
|
gfx_sheets_[i].SetPalette(
|
|
col_file_palette_group_[current_palette_index_]);
|
|
} else {
|
|
// ROM palette
|
|
|
|
auto palette_group = rom()->palette_group().get_group(
|
|
kPaletteGroupAddressesKeys[current_palette_]);
|
|
z3_rom_palette_ = *palette_group->mutable_palette(current_palette_index_);
|
|
gfx_sheets_[i].SetPalette(z3_rom_palette_);
|
|
}
|
|
|
|
gfx::Arena::Get().QueueTextureCommand(
|
|
gfx::Arena::TextureCommandType::UPDATE, &gfx_sheets_[i]);
|
|
i++;
|
|
}
|
|
|
|
for (const auto& offset : kSuperDonkeySprites) {
|
|
int offset_value =
|
|
std::stoi(offset, nullptr, 16); // convert hex string to int
|
|
ASSIGN_OR_RETURN(
|
|
auto decompressed_data,
|
|
gfx::lc_lz2::DecompressV2(temp_rom_.data(), offset_value, 0x1000));
|
|
auto converted_sheet = gfx::SnesTo8bppSheet(decompressed_data, 3);
|
|
gfx_sheets_[i] = gfx::Bitmap(gfx::kTilesheetWidth, gfx::kTilesheetHeight,
|
|
gfx::kTilesheetDepth, converted_sheet);
|
|
if (col_file_) {
|
|
gfx_sheets_[i].SetPalette(
|
|
col_file_palette_group_[current_palette_index_]);
|
|
} else {
|
|
// ROM palette
|
|
auto palette_group = rom()->palette_group().get_group(
|
|
kPaletteGroupAddressesKeys[current_palette_]);
|
|
z3_rom_palette_ = *palette_group->mutable_palette(current_palette_index_);
|
|
gfx_sheets_[i].SetPalette(z3_rom_palette_);
|
|
}
|
|
|
|
gfx::Arena::Get().QueueTextureCommand(
|
|
gfx::Arena::TextureCommandType::UPDATE, &gfx_sheets_[i]);
|
|
i++;
|
|
}
|
|
super_donkey_ = true;
|
|
num_sheets_to_load_ = i;
|
|
|
|
return absl::OkStatus();
|
|
}
|
|
|
|
} // namespace editor
|
|
} // namespace yaze
|