13#include "absl/status/status.h"
14#include "absl/status/statusor.h"
15#include "absl/strings/str_cat.h"
16#include "absl/strings/str_format.h"
17#include "absl/strings/str_join.h"
18#include "imgui/imgui.h"
19#include "imgui/misc/cpp/imgui_stdlib.h"
60using ImGui::InputText;
79 [
this](uint16_t sheet,
const std::string& label) {
84 [
this](
const std::string& csv_text) -> absl::StatusOr<int> {
102 [
this] {
return Redo(); });
104 [
this](
const std::vector<size_t>& targets,
111 "graphics.sheet_browser_v2");
113 [
this](
bool visible) {
116 workspace->
OpenWindow(
"graphics.sheet_browser_v2");
118 workspace->CloseWindow(
"graphics.sheet_browser_v2");
130 "Gfx Groups: blockset/roomset/spriteset selection syncs with the "
132 "editor for this ROM session (each surface has its own preview "
152 window_manager->RegisterWindowContent(
153 std::make_unique<GraphicsSheetBrowserPanel>([
this]() {
159 window_manager->RegisterWindowContent(
160 std::make_unique<GraphicsPixelEditorPanel>([
this]() {
166 window_manager->RegisterWindowContent(
167 std::make_unique<GraphicsPaletteControlsPanel>([
this]() {
173 window_manager->RegisterWindowContent(
174 std::make_unique<GraphicsUsagePreviewPanel>([
this]() {
176 const auto* current_project =
project();
178 current_project !=
nullptr
185 window_manager->RegisterWindowContent(
186 std::make_unique<GraphicsLinkSpritePanel>([
this]() {
192 window_manager->RegisterWindowContent(
193 std::make_unique<GraphicsGfxGroupPanel>([
this]() {
200 window_manager->RegisterWindowContent(
201 std::make_unique<GraphicsPalettesetPanel>([
this]() {
207 window_manager->RegisterWindowContent(
208 std::make_unique<GraphicsPrototypeViewerPanel>(
211 window_manager->RegisterWindowContent(
212 std::make_unique<GraphicsPolyhedralPanel>([
this]() {
229 if (
rom()->is_loaded()) {
237 LOG_INFO(
"GraphicsEditor",
"Initializing textures for %d graphics sheets",
240 int sheets_queued = 0;
242 if (!sheets[i].is_active() || !sheets[i].surface()) {
248 if (!sheets[i].texture()) {
251 if (sheets[i].palette().empty()) {
256 game_data()->palette_groups.dungeon_main.size() > 0) {
257 sheets[i].SetPaletteWithTransparent(
258 game_data()->palette_groups.dungeon_main.palette(0), 0);
260 }
else if (i >= 113 && i <= 127) {
263 game_data()->palette_groups.sprites_aux1.size() > 0) {
264 sheets[i].SetPaletteWithTransparent(
265 game_data()->palette_groups.sprites_aux1.palette(0), 0);
270 sheets[i].SetPaletteWithTransparent(
271 game_data()->palette_groups.hud.palette(0), 0);
282 LOG_INFO(
"GraphicsEditor",
"Queued texture creation for %d graphics sheets",
286 return absl::OkStatus();
294 sheet_opts.
tooltip = absl::StrFormat(
295 "Sheet %d (0x%02X) — use Command Palette to jump between sheets",
299 std::move(sheet_opts));
306 modified_opts.
tooltip = absl::StrFormat(
311 std::move(modified_opts));
326 {
"gfx.source_capture",
"Source mapping",
327 "Captured when opened; preview uses live pixels"});
331 {
"gfx.selected_tile",
"Source tile", target->label});
333 for (
size_t i = 0; i < target->palette.size(); ++i) {
336 colors += absl::StrFormat(
"%04X", target->palette[i].snes());
339 {
"gfx.raw_palette",
"Raw index colors (SNES)", colors});
343 {
"gfx.current_sheet_revision",
"Current sheet revision",
354 if (editor_manager ==
nullptr || snapshot ==
nullptr ||
355 !editor_manager->IsCurrentProjectContextOwnedBySession(
357 return absl::FailedPreconditionError(
358 "Sheet labels need the project that owns this ROM to be active");
362 return absl::FailedPreconditionError(
363 "Open a .yaze project to store sheet labels");
369 editor_manager->MarkCurrentProjectDirty();
370 return absl::OkStatus();
373absl::StatusOr<GraphicsEditor::PreparedGraphicsSave>
376 return absl::FailedPreconditionError(
"ROM not loaded");
379 return absl::FailedPreconditionError(
"Game data not loaded");
384 return absl::FailedPreconditionError(
385 "Graphics sheets in memory belong to another open ROM. Switch back to "
386 "the ROM they were edited in, or reopen this ROM, before saving "
398 prepared.
refusals[sheet_id] =
"out of range";
400 prepared.
refusals[sheet_id] =
"reserved by the project";
403 prepared.
refusals[sheet_id] =
"2bpp, read-only";
404 }
else if (!sheets[sheet_id].is_active()) {
405 prepared.
refusals[sheet_id] =
"not loaded";
406 }
else if (std::any_of(sheets[sheet_id].vector().begin(),
407 sheets[sheet_id].vector().end(),
408 [](uint8_t index) {
return index > 7; })) {
410 prepared.
refusals[sheet_id] =
"uses colors above index 7";
416 const auto version_constants =
420 version_constants.kOverworldGfxPtr1);
423 version_constants.kOverworldGfxPtr2);
426 version_constants.kOverworldGfxPtr3);
432 return absl::FailedPreconditionError(
433 "Commit or cancel the floating paste in Graphics before saving "
434 "(Enter to commit, Esc to cancel)");
436 return absl::FailedPreconditionError(
437 "Finish the pixel stroke before saving");
439 return absl::FailedPreconditionError(
"ROM not loaded");
444 LOG_INFO(
"GraphicsEditor",
"No modified sheets to save");
445 return absl::OkStatus();
452 std::vector<std::string> refused;
453 for (
const auto& [sheet_id, reason] : prepared.
refusals) {
454 refused.push_back(absl::StrFormat(
"0x%02X (%s)", sheet_id, reason));
456 return absl::FailedPreconditionError(absl::StrFormat(
457 "Graphics save refused for sheet(s) %s. Discard those edits to save "
459 absl::StrJoin(refused,
", ")));
462 LOG_INFO(
"GraphicsEditor",
"Saving %zu modified graphics sheets",
468 const std::vector<uint8_t> rom_snapshot =
rom_->
vector();
469 const bool rom_was_dirty =
rom_->
dirty();
470 auto restore = [&]() {
474 std::set<uint16_t> saved_sheets;
475 for (uint16_t sheet_id : prepared.
sheet_ids) {
476 const auto snes_data =
482 return absl::Status(result.status().code(),
483 absl::StrFormat(
"Graphics sheet 0x%02X not saved: %s",
484 sheet_id, result.status().message()));
486 LOG_INFO(
"GraphicsEditor",
"Saved sheet %02X (%zu bytes, %s) at 0x%06X",
487 sheet_id, result->new_stored_size,
492 saved_sheets.insert(sheet_id);
498 for (uint16_t sheet_id : prepared.
sheet_ids) {
500 *
rom_, sheet_id, sheets[sheet_id].vector(), prepared.
tables);
501 if (!verified.ok()) {
505 absl::StrFormat(
"Graphics save stopped; nothing was written: %s",
506 verified.message()));
512 return absl::OkStatus();
515absl::StatusOr<std::vector<GraphicsSavePlanEntry>>
518 std::vector<GraphicsSavePlanEntry> entries;
519 for (
const auto& [sheet_id, reason] : prepared.
refusals) {
523 entries.push_back(std::move(entry));
529 for (uint16_t sheet_id : prepared.
sheet_ids) {
532 const auto& pixels = sheets[sheet_id].vector();
537 entry.
refusal = std::string(result.status().message());
540 entry.
old_pc = result->old_pc;
541 entry.
new_pc = result->new_pc;
544 const absl::Status verified =
546 if (!verified.ok()) {
547 entry.
refusal = std::string(verified.message());
550 entries.push_back(std::move(entry));
552 std::sort(entries.begin(), entries.end(), [](
const auto& a,
const auto& b) {
553 return a.sheet_id < b.sheet_id;
570 return absl::OkStatus();
577 return absl::FailedPreconditionError(
"Finish the pixel stroke before Undo");
585 return absl::FailedPreconditionError(
"Finish the pixel stroke before Redo");
591 ImGui::TextWrapped(tr(
592 "No ROM loaded — CGX/SCR/COL/BIN and clipboard tools work without one. "
593 "Load a ROM when you want vanilla palette presets or to save graphics "
594 "back into a cartridge image."));
598 ImGui::TextColored(ImVec4(1.0f, 0.35f, 0.35f, 1.0f),
"%s",
600 if (ImGui::SmallButton(tr(
"Dismiss##prototype_import_feedback"))) {
612 constexpr ImGuiTableFlags kGfxEditFlags = ImGuiTableFlags_Reorderable |
613 ImGuiTableFlags_Resizable |
614 ImGuiTableFlags_SizingStretchSame;
619 ImGui::TableSetupColumn(
"Tilemaps and Objects (SCR, PNL, OBJ)",
620 ImGuiTableColumnFlags_WidthFixed);
645 ImGui::Image((ImTextureID)(intptr_t)
gfx_sheets_[i].texture(),
647 if ((i + 1) % 4 != 0) {
677 if (ImGui::Button(tr(
"Open CGX"))) {
685 if (ImGui::Button(tr(
"Copy CGX Path"))) {
689 if (ImGui::Button(tr(
"Load CGX Data"))) {
694 return absl::OkStatus();
706 return absl::OkStatus();
712 if (ImGui::Button(tr(
"Open SCR"))) {
722 if (ImGui::Button(tr(
"Load Scr Data"))) {
726 return absl::OkStatus();
734 absl::StrCat(
"[SCR draw] ",
status_.message());
735 return absl::OkStatus();
747 return absl::OkStatus();
755 if (ImGui::Button(tr(
"Open COL"))) {
764 auto col_file_palette_group_status =
766 if (col_file_palette_group_status.ok()) {
778 if (ImGui::Button(tr(
"Copy Col Path"))) {
782 if (
rom()->is_loaded()) {
786 IM_ARRAYSIZE(kPaletteGroupAddressesKeys));
794 return absl::OkStatus();
803 if (ImGui::Button(tr(
"Open OBJ"))) {
812 return absl::OkStatus();
821 if (ImGui::Button(tr(
"Open Tilemap"))) {
836 return absl::OkStatus();
845 if (ImGui::Button(tr(
"Open BIN"))) {
853 if (Button(tr(
"Copy File Path"))) {
860 if (Button(tr(
"Decompress BIN"))) {
862 return absl::InvalidArgumentError(
863 "Please select a file before decompressing.");
868 return absl::OkStatus();
873 if (Button(tr(
"Paste From Clipboard"))) {
874 const char* text = ImGui::GetClipboardText();
876 const auto clipboard_data =
877 std::vector<uint8_t>(text, text + strlen(text));
878 ImGui::MemFree((
void*)text);
888 if (Button(tr(
"Decompress Clipboard Data"))) {
892 status_ = absl::InvalidArgumentError(
893 "Please paste data into the clipboard before "
898 return absl::OkStatus();
903 if (Button(tr(
"Decompress Super Donkey Full"))) {
905 return absl::InvalidArgumentError(
906 "Please select `super_donkey_1.bin` before "
911 ImGui::SetItemTooltip(
912 tr(
"Requires `super_donkey_1.bin` to be imported under the "
913 "BIN import section."));
914 return absl::OkStatus();
918 std::string title =
"Memory Editor";
924 return absl::OkStatus();
950 return absl::OkStatus();
957 std::stoi(offset,
nullptr, 16);
970 return absl::FailedPreconditionError(
"GameData not available");
985 std::stoi(offset,
nullptr, 16);
1012 return absl::OkStatus();
1045 return absl::FailedPreconditionError(
"Pixel editor is not open");
1048 return absl::OkStatus();
1053 return absl::FailedPreconditionError(
"Pixel editor is not open");
1056 return absl::OkStatus();
1061 return absl::FailedPreconditionError(
"Pixel editor is not open");
1064 return absl::OkStatus();
1082 return absl::FailedPreconditionError(
1083 "Graphics context belongs to a different ROM session");
1085 if (
context.targets.empty()) {
1086 return absl::InvalidArgumentError(
"Graphics context has no editable tiles");
1088 for (
const auto& target :
context.targets) {
1090 target.palette.size() != 8 ||
1094 return absl::InvalidArgumentError(
1095 "Graphics context needs loaded 3bpp sheets and eight indexed colors");
1099 return absl::FailedPreconditionError(
1100 "Commit or cancel the floating paste before opening another source");
1114 return absl::OkStatus();
1118 const std::vector<size_t>& targets,
1119 const std::array<uint8_t, 8>& permutation) {
1121 return absl::FailedPreconditionError(
"Finish the pixel stroke first");
1125 return absl::OkStatus();
1129 const std::string& label,
1130 double duration_secs) {
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
absl::Status LoadFromFile(const std::string &filename, const LoadOptions &options=LoadOptions::Defaults())
void set_dirty(bool dirty)
const auto & vector() const
absl::Status LoadFromData(const std::vector< uint8_t > &data, const LoadOptions &options=LoadOptions::Defaults())
const std::string & hack_name() const
static RomSettings & Get()
uint32_t GetAddressOr(const std::string &key, uint32_t default_value) const
The EditorManager controls the main editor window and manages the various editor classes.
EditorContext context() const
UndoManager undo_manager_
project::YazeProject * project() const
zelda3::GameData * game_data() const
EditorDependencies dependencies_
std::map< uint16_t, GraphicsPaletteSelection > preview_palettes
std::optional< GraphicsEditContext > source_context
std::optional< uint16_t > requested_sheet_tab
std::set< uint16_t > selected_sheets
void HighlightTile(uint16_t sheet_id, uint16_t tile_index, const std::string &label="", double duration_secs=3.0)
Highlight a tile in the current sheet for quick visual focus.
std::set< uint16_t > modified_sheets
uint16_t current_sheet_id
std::function< void()> on_before_sheet_selected
uint64_t source_context_revision
bool HasUnsavedChanges() const
Check if any sheets have unsaved changes.
zelda3::GameData * game_data
void SelectSheet(uint16_t sheet_id)
Select a sheet for editing.
void SelectSourceTarget(size_t index)
void ClearSourceContext()
const GraphicsEditTarget * SourceTarget() const
void SetTool(PixelTool tool)
Set the current editing tool.
void ClearModifiedSheets()
Clear modification tracking (after save)
void FinishPixelGesture()
usage_preview::LiveRoomProvider room_usage_provider_
std::vector< uint8_t > scr_data_
GraphicsEditorState state_
absl::Status Save() override
void HighlightTile(uint16_t sheet_id, uint16_t tile_index, const std::string &label="", double duration_secs=3.0)
uint64_t clipboard_offset_
absl::Status ReorderSourcePalette(const std::vector< size_t > &targets, const std::array< uint8_t, 8 > &permutation)
GfxGroupEditor::RoomsetUsageCallback roomset_graphics_usage_
void SetPixelTool(PixelTool tool)
gfx::PaletteGroup col_file_palette_group_
void SelectSheet(uint16_t sheet_id)
std::string col_file_path_
std::string cgx_file_path_
absl::Status DrawMemoryEditor()
absl::Status Load() override
std::unique_ptr< GfxGroupEditor > gfx_group_panel_
zelda3::GameData * game_data_
std::unique_ptr< PaletteControlsPanel > palette_controls_panel_
void Initialize() override
std::vector< uint8_t > decoded_cgx_
EditorContextSnapshot BuildContextSnapshot() const override
std::string scr_file_path_
absl::Status Copy() override
absl::Status DecompressSuperDonkey()
GfxGroupEditor::MainUsageCallback main_graphics_usage_
absl::Status EditProject(const std::function< void(project::YazeProject &)> &edit)
gfx::SnesPalette col_file_palette_
std::string col_file_name_
absl::Status DrawCgxImport()
std::vector< uint8_t > cgx_data_
std::unique_ptr< PixelEditorPanel > pixel_editor_panel_
absl::Status Redo() override
std::unique_ptr< PolyhedralEditorPanel > polyhedral_panel_
std::unique_ptr< PalettesetEditorPanel > paletteset_panel_
std::function< absl::Status(const Rom &, uint16_t, const std::vector< uint8_t > &, const zelda3::GfxSheetPointerTables &) verify_written_sheet_)
absl::Status Cut() override
std::string obj_file_path_
void DrawPrototypeViewer()
absl::Status DrawFileImport()
std::vector< uint8_t > import_data_
uint64_t current_palette_index_
absl::Status Paste() override
absl::StatusOr< std::vector< GraphicsSavePlanEntry > > PlanGraphicsSave()
Graphics save preflight: every dirty sheet written to a scratch copy of the ROM (placement,...
std::vector< SDL_Color > decoded_col_
absl::Status Undo() override
absl::Status DrawTilemapImport()
absl::StatusOr< PreparedGraphicsSave > PrepareGraphicsSave()
gfx::SnesPalette z3_rom_palette_
absl::Status Update() override
absl::Status OpenInContext(GraphicsEditContext context)
absl::Status DrawScrImport()
std::string tilemap_file_path_
std::vector< uint8_t > extra_cgx_data_
std::array< gfx::Bitmap, zelda3::kNumGfxSheets > gfx_sheets_
gui::Canvas import_canvas_
std::vector< uint8_t > decoded_scr_data_
std::string cgx_file_name_
std::unique_ptr< LinkSpritePanel > link_sprite_panel_
absl::Status DrawObjImport()
std::string prototype_import_feedback_
absl::Status DrawClipboardImport()
uint64_t num_sheets_to_load_
absl::Status DecompressImportData(int size)
void ContributeStatus(StatusBar *status_bar) override
std::unique_ptr< UsagePreviewView > usage_preview_view_
absl::Status DrawExperimentalFeatures()
absl::Status DrawPaletteControls()
std::unique_ptr< SheetBrowserPanel > sheet_browser_panel_
std::string scr_file_name_
A session-aware status bar displayed at the bottom of the application.
void SetSelection(int count, int width=0, int height=0)
Set selection information.
void SetCustomSegment(const std::string &key, const std::string &value)
Set a custom segment with key-value pair.
absl::Status Redo()
Redo the top action. Returns error if stack is empty.
absl::Status Undo()
Undo the top action. Returns error if stack is empty.
bool IsWindowOpen(size_t session_id, const std::string &base_window_id) const
bool OpenWindow(size_t session_id, const std::string &base_window_id)
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
GraphicsSheets & gfx_sheets()
Represents a bitmap image optimized for SNES ROM hacking.
void Create(int width, int height, int depth, std::span< uint8_t > data)
Create a bitmap with the given dimensions and data.
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).
CanvasConfig & GetConfig()
static std::string ShowOpenFileDialog()
ShowOpenFileDialog opens a file dialog and returns the selected filepath. Uses global feature flag to...
bool HasSheet(uint16_t sheet) const
True when the buffer holds all 4096 bytes of sheet.
uint64_t Revision(uint16_t sheet) const
Locates graphics sheets in the ROM and writes edited sheets back.
#define LOG_INFO(category, format,...)
#define BEGIN_TABLE(l, n, f)
#define ASSIGN_OR_RETURN(type_variable_name, expression)
#define CLEAR_AND_RETURN_STATUS(status)
#define HOVER_HINT(string)
constexpr char kOverworldGfxPtr3[]
constexpr char kOverworldGfxPtr1[]
constexpr char kOverworldGfxPtr2[]
absl::StatusOr< zelda3::GfxSheetWritePolicy > BuildGfxSheetWritePolicy(const HackManifest &manifest)
void SetGraphicsSheetLabel(project::YazeProject &project, uint16_t sheet, const std::string &label)
int ImportSpritesetSheetLabels(project::YazeProject &project, const std::string &csv_text, bool overwrite)
int SyncArenaFromStore(GraphicsEditorState &state)
std::array< uint8_t, 8 > PalettePermutation
const std::string kSuperDonkeySprites[]
void AppendResolvedGraphicsContext(EditorContextSnapshot &snapshot, const zelda3::ResolvedGraphicsContext &context)
void AttachSheetStore(GraphicsEditorState &state, zelda3::GameData *game_data)
const std::string kSuperDonkeyTiles[]
void RefreshGraphicsPreviewPalettes(GraphicsEditorState &state)
PixelTool
Pixel editing tool types for the graphics editor.
absl::Status ApplyPalettePixelPermutation(GraphicsEditorState &state, UndoManager &history, const std::vector< size_t > &targets, const PalettePermutation &permutation)
absl::StatusOr< std::vector< uint8_t > > DecompressV2(const uint8_t *data, int offset, int size, int mode, size_t rom_size)
Decompresses a buffer of data using the LC_LZ2 algorithm.
constexpr int kNintendoMode1
absl::Status LoadScr(std::string_view filename, uint8_t input_value, std::vector< uint8_t > &map_data)
Load Scr file (screen data)
std::vector< SDL_Color > DecodeColFile(const std::string_view filename)
Decode color file.
constexpr int kTilesheetHeight
constexpr int kTilesheetWidth
absl::Status LoadCgx(uint8_t bpp, std::string_view filename, std::vector< uint8_t > &cgx_data, std::vector< uint8_t > &cgx_loaded, std::vector< uint8_t > &cgx_header)
Load Cgx file (graphical content)
constexpr const char * kPaletteGroupAddressesKeys[]
absl::Status DrawScrWithCgx(uint8_t bpp, std::vector< uint8_t > &map_bitmap_data, std::vector< uint8_t > &map_data, std::vector< uint8_t > &cgx_loaded)
Draw screen tilemap with graphical data.
constexpr int kTilesheetDepth
std::vector< uint8_t > SnesTo8bppSheet(std::span< const uint8_t > sheet, int bpp, int num_sheets)
std::vector< uint8_t > IndexedToSnesSheet(std::span< const uint8_t > sheet, int bpp, int num_sheets)
absl::StatusOr< PaletteGroup > CreatePaletteGroupFromColFile(std::vector< SnesColor > &palette_rows)
std::vector< SnesColor > GetColFileData(uint8_t *data)
bool InputHex(const char *label, uint64_t *data)
void SelectablePalettePipeline(uint64_t &palette_id, bool &refresh_graphics, gfx::SnesPalette &palette)
void BitmapCanvasPipeline(Canvas &canvas, gfx::Bitmap &bitmap, int width, int height, int tile_size, bool is_loaded, bool scrollbar, int canvas_id)
void TextWithSeparators(const absl::string_view &text)
absl::StatusOr< GfxSheetWriteResult > WriteGfxSheet(Rom &rom, uint16_t sheet_id, const std::vector< uint8_t > &snes_3bpp, const GfxSheetWritePolicy &policy, const GfxSheetPointerTables &tables)
Writes one edited 3bpp sheet to the ROM buffer.
constexpr uint32_t kNumGfxSheets
GfxSheetStorageKind GetGfxSheetStorageKind(uint16_t sheet_id)
#define RETURN_IF_ERROR(expr)
std::string semantic_owner
std::vector< EditorContextValue > metadata
WorkspaceWindowManager * window_manager
GfxGroupWorkspaceState * gfx_group_workspace
std::map< uint16_t, std::string > refusals
zelda3::GfxSheetPointerTables tables
std::vector< uint16_t > sheet_ids
zelda3::GfxSheetWritePolicy policy
zelda3::GfxSheetPlacement placement
Optional behavior for an interactive status bar segment.
PaletteGroup overworld_animated
PaletteGroup * get_group(const std::string &group_name)
Modern project structure with comprehensive settings consolidation.
core::HackManifest hack_manifest
gfx::PaletteGroupMap palette_groups
std::set< uint16_t > reserved_sheets
GraphicsSheetStore sheet_store