yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
tile16_editor.h
Go to the documentation of this file.
1#ifndef YAZE_APP_EDITOR_TILE16EDITOR_H
2#define YAZE_APP_EDITOR_TILE16EDITOR_H
3
4#include <algorithm>
5#include <array>
6#include <chrono>
7#include <functional>
8#include <map>
9#include <optional>
10#include <string>
11#include <vector>
12
13#include "absl/status/status.h"
17#include "app/gfx/core/bitmap.h"
21#include "app/gui/core/input.h"
23#include "rom/rom.h"
24#include "imgui/imgui.h"
25#include "util/log.h"
26#include "util/notify.h"
27
28namespace yaze {
29namespace zelda3 {
30struct GameData;
31} // namespace zelda3
32
33namespace editor {
34
35// ============================================================================
36// Tile16 Editor Constants
37// ============================================================================
38
39constexpr int kTile16Size = 16; // 16x16 pixel tile
40constexpr int kTile8Size = 8; // 8x8 pixel sub-tile
41constexpr int kTilesheetEditorWidth = 0x100; // 256 pixels wide
42constexpr int kTilesheetEditorHeight = 0x4000; // 16384 pixels tall
43constexpr int kTile16CanvasSize = 0x20; // 32 pixels
44constexpr int kTile8CanvasHeight = 0x175; // 373 pixels
45constexpr int kNumScratchSlots = 4; // 4 scratch space slots
46constexpr int kNumPalettes = 8; // 8 palette buttons (0-7)
47constexpr int kTile8PixelCount = 64; // 8x8 = 64 pixels
48constexpr int kTile16PixelCount = 256; // 16x16 = 256 pixels
49
55
61
62// ============================================================================
63// Tile16 Editor
64// ============================================================================
65//
66// ARCHITECTURE OVERVIEW:
67// ----------------------
68// The Tile16Editor provides a popup window for editing individual 16x16 tiles
69// used in the overworld tileset. Each Tile16 is composed of four 8x8 sub-tiles
70// (Tile8) arranged in a 2x2 grid.
71//
72// EDITING WORKFLOW:
73// -----------------
74// 1. Select a Tile16 from the blockset canvas (left panel)
75// 2. Edit by clicking on the tile8 source canvas to select sub-tiles
76// 3. Place selected tile8s into the four quadrants of the Tile16
77// 4. Changes are held as "pending" until explicitly committed or discarded
78// 5. Commit saves to ROM; Discard reverts to original
79//
80// PENDING CHANGES SYSTEM:
81// -----------------------
82// To prevent accidental ROM modifications, all edits are staged:
83// - pending_tile16_changes_: Maps tile ID -> modified Tile16 data
84// - pending_tile16_bitmaps_: Maps tile ID -> preview bitmap
85// - has_pending_changes(): Returns true if any tiles are modified
86// - CommitAllChanges(): Writes all pending changes to ROM
87// - DiscardAllChanges(): Reverts all pending changes
88//
89// PALETTE COORDINATION:
90// ---------------------
91// The overworld uses a 256-color palette organized as 16 rows of 16 colors.
92// Different graphics sheets map to different palette regions:
93//
94// Sheet Index | Palette Region | Purpose
95// ------------|----------------|------------------------
96// 0, 3, 4 | AUX1 (row 10+) | Main blockset graphics
97// 1, 2 | MAIN (row 2+) | Main area graphics
98// 5, 6 | AUX2 (row 10+) | Secondary blockset
99// 7 | ANIMATED | Animated tiles
100//
101// Key palette methods:
102// - GetPaletteSlotForSheet(): Get base palette slot for a sheet
103// - GetActualPaletteSlot(): Combine palette button + sheet to get final slot
104// - GetActualPaletteSlotForCurrentTile16(): Get slot for current editing tile
105// - ApplyPaletteToCurrentTile16Bitmap(): Apply correct colors to preview
106//
107// INTEGRATION WITH OVERWORLD:
108// ---------------------------
109// The Tile16Editor communicates with OverworldEditor via:
110// - set_palette(): Called when overworld area changes (updates colors)
111// - on_changes_committed_: Callback invoked after CommitAllChanges()
112// - The callback triggers RefreshTile16Blockset() and RefreshOverworldMap()
113//
114// See README.md in this directory for complete documentation.
115// ============================================================================
116
126 public:
127 Tile16Editor(Rom* rom, gfx::Tilemap* tile16_blockset)
128 : rom_(rom), tile16_blockset_(tile16_blockset) {}
129 absl::Status Initialize(const gfx::Bitmap& tile16_blockset_bmp,
130 const gfx::Bitmap& current_gfx_bmp,
131 std::array<uint8_t, 0x200>& all_tiles_types);
132
133 absl::Status Update();
134
141 absl::Status UpdateAsPanel();
142
149 void DrawContextMenu();
150
151 void DrawTile16Editor();
152 absl::Status UpdateBlockset();
153
154 // Scratch space for tile16 layouts
155 void DrawScratchSpace();
156 absl::Status SaveLayoutToScratch(int slot);
157 absl::Status LoadLayoutFromScratch(int slot);
158
159 absl::Status DrawToCurrentTile16(ImVec2 pos,
160 const gfx::Bitmap* source_tile = nullptr);
161
162 absl::Status UpdateTile16Edit();
163
164 absl::Status LoadTile8();
165
166 absl::Status SetCurrentTile(int id);
167
168 // Request a tile switch - shows confirmation dialog if current tile has pending changes
169 void RequestTileSwitch(int target_tile_id);
170
171 // New methods for clipboard and scratch space
172 absl::Status CopyTile16ToClipboard(int tile_id);
173 absl::Status PasteTile16FromClipboard();
174 absl::Status SaveTile16ToScratchSpace(int slot);
175 absl::Status LoadTile16FromScratchSpace(int slot);
176 absl::Status ClearScratchSpace(int slot);
177
178 // Advanced editing features
179 absl::Status FlipTile16Horizontal();
180 absl::Status FlipTile16Vertical();
181 absl::Status RotateTile16();
182 absl::Status FillTile16WithTile8(int tile8_id);
183 absl::Status AutoTileTile16();
184 absl::Status ClearTile16();
185
186 // Palette management
187 absl::Status CyclePalette(bool forward = true);
188 absl::Status ApplyPaletteToAll(uint8_t palette_id);
189 absl::Status PreviewPaletteChange(uint8_t palette_id);
190
191 // Batch operations
192 absl::Status ApplyToSelection(const std::function<void(int)>& operation);
193 absl::Status BatchEdit(const std::vector<int>& tile_ids,
194 const std::function<void(int)>& operation);
195
196 // History and undo system
197 absl::Status Undo();
198 absl::Status Redo();
199 void SaveUndoState();
200
201 // Live preview system
202 void EnableLivePreview(bool enable) { live_preview_enabled_ = enable; }
203 absl::Status UpdateLivePreview();
204
205 // Validation and integrity checks
206 absl::Status ValidateTile16Data();
207 bool IsTile16Valid(int tile_id) const;
208
209 // ===========================================================================
210 // Integration with Overworld System
211 // ===========================================================================
212 // These methods handle the connection between tile editing and ROM data.
213 // The workflow is: Edit -> Pending -> Commit -> ROM
214
216 absl::Status SaveTile16ToROM();
217
219 absl::Status UpdateOverworldTilemap();
220
222 absl::Status CommitChangesToBlockset();
223
225 absl::Status CommitChangesToOverworld();
226
228 absl::Status DiscardChanges();
229
230 // ===========================================================================
231 // Pending Changes System
232 // ===========================================================================
233 // All tile edits are staged in memory before being written to ROM.
234 // This prevents accidental modifications and allows preview before commit.
235 //
236 // Usage:
237 // 1. Edit tiles normally (changes go to pending_tile16_changes_)
238 // 2. Check has_pending_changes() to show save/discard UI
239 // 3. User clicks Save -> CommitAllChanges()
240 // 4. User clicks Discard -> DiscardAllChanges()
241 //
242 // The on_changes_committed_ callback notifies OverworldEditor to refresh.
243
245 bool has_pending_changes() const { return !pending_tile16_changes_.empty(); }
246
249 return static_cast<int>(pending_tile16_changes_.size());
250 }
251
253 bool is_tile_modified(int tile_id) const {
254 return pending_tile16_changes_.find(tile_id) != pending_tile16_changes_.end();
255 }
256
258 const gfx::Bitmap* GetPendingTileBitmap(int tile_id) const {
259 auto it = pending_tile16_bitmaps_.find(tile_id);
260 return it != pending_tile16_bitmaps_.end() ? &it->second : nullptr;
261 }
262
264 absl::Status CommitAllChanges();
265
267 void DiscardAllChanges();
268
271
274
275 // ===========================================================================
276 // Palette Coordination System
277 // ===========================================================================
278 // The overworld uses a 256-color palette organized as 16 rows of 16 colors.
279 // Different graphics sheets map to different palette regions based on how
280 // the SNES PPU organizes tile graphics.
281 //
282 // Palette Structure (256 colors = 16 rows × 16 colors):
283 // Row 0: Transparent/system colors
284 // Row 1: HUD colors (0x10-0x1F)
285 // Rows 2-6: MAIN/BG palettes for main graphics (sheets 1-2)
286 // Rows 7: ANIMATED palette (sheet 7)
287 // Rows 10+: AUX palettes for blockset graphics (sheets 0, 3-6)
288 //
289 // The palette button (0-7) selects which of the 8 available sub-palettes
290 // to use, and the sheet index determines the base offset.
291
293 absl::Status UpdateTile8Palette(int tile8_id);
294
296 absl::Status RefreshAllPalettes();
297
299 void DrawPaletteSettings();
300
304 int GetPaletteSlotForSheet(int sheet_index) const;
305
315 int GetActualPaletteSlot(int palette_button, int sheet_index) const;
316
320 int GetPaletteBaseForSheet(int sheet_index) const;
321
325 int GetSheetIndexForTile8(int tile8_id) const;
326
330
336 const gfx::SnesPalette& source, int target_row) const;
337
341 int GetEncodedPaletteRow(uint8_t pixel_value) const;
342
343 // ROM data access and modification
344 absl::Status UpdateROMTile16Data();
345 absl::Status RefreshTile16Blockset();
347 absl::Status RegenerateTile16BitmapFromROM();
348 absl::Status UpdateBlocksetBitmap();
349 absl::Status PickTile8FromTile16(const ImVec2& position);
350
351 // Manual tile8 input controls
353
354 void SetRom(Rom* rom) { rom_ = rom; }
355 Rom* rom() const { return rom_; }
358
359 // Set the palette from overworld to ensure color consistency
360 void set_palette(const gfx::SnesPalette& palette) {
361 palette_ = palette;
362
363 // Store the complete 256-color overworld palette
364 if (palette.size() >= 256) {
365 overworld_palette_ = palette;
367 "Tile16 editor received complete overworld palette with %zu colors",
368 palette.size());
369 } else {
370 util::logf("Warning: Received incomplete palette with %zu colors",
371 palette.size());
372 overworld_palette_ = palette;
373 }
374
375 // CRITICAL FIX: Load tile8 graphics now that we have the proper palette
377 auto status = LoadTile8();
378 if (!status.ok()) {
379 util::logf("Failed to load tile8 graphics with new palette: %s",
380 status.message().data());
381 } else {
383 "Successfully loaded tile8 graphics with complete overworld "
384 "palette");
385 }
386 }
387
388 util::logf("Tile16 editor palette coordination complete");
389 }
390
391 // Callback for when changes are committed to notify parent editor
392 void set_on_changes_committed(std::function<absl::Status()> callback) {
393 on_changes_committed_ = callback;
394 }
395
396 // Accessors for testing and external use
397 int current_palette() const { return current_palette_; }
398 void set_current_palette(int palette) {
399 current_palette_ = static_cast<uint8_t>(std::clamp(palette, 0, 7));
400 }
401 int current_tile16() const { return current_tile16_; }
402 int current_tile8() const { return current_tile8_; }
403
404 // Diagnostic function to analyze tile8 source data format
405 void AnalyzeTile8SourceData() const;
406
407 private:
408 Rom* rom_ = nullptr;
411 bool x_flip = false;
412 bool y_flip = false;
413 bool priority_tile = false;
414
418 uint8_t current_palette_ = 0;
419
420 // Clipboard for Tile16 graphics and metadata
422
423 // Scratch space for Tile16 graphics and metadata (4 slots)
424 std::array<Tile16ScratchData, 4> scratch_space_;
425
426 // Layout scratch space for tile16 arrangements (4 slots of 8x8 grids)
428 std::array<std::array<int, 8>, 8> tile_layout; // 8x8 grid of tile16 IDs
429 bool in_use = false;
430 std::string name = "Empty";
431 };
432 std::array<LayoutScratch, 4> layout_scratch_;
433
434 // Undo/Redo system (unified UndoManager framework)
436 std::optional<Tile16Snapshot> pending_undo_before_;
437
440 void FinalizePendingUndo();
441
443 void RestoreFromSnapshot(const Tile16Snapshot& snapshot);
444
445 // Live preview system
448 bool preview_dirty_ = false;
449 gfx::Bitmap tile8_preview_bmp_; // Persistent preview to keep arena commands valid
450
451 // Selection system
452 std::vector<int> selected_tiles_;
454 bool multi_select_mode_ = false;
455
456 // Advanced editing state
457 bool auto_tile_mode_ = false;
459 bool show_tile_info_ = true;
461
462 // Palette management settings
464 int current_palette_group_ = 0; // 0=overworld_main, 1=aux1, 2=aux2, etc.
466 0xFF; // Default 8-bit mask (preserve full palette index)
468 false; // Disabled by default to preserve palette offsets
469
470 // Performance tracking
471 std::chrono::steady_clock::time_point last_edit_time_;
472 bool batch_mode_ = false;
473
474 // Pending changes system for batch preview/commit workflow
475 std::map<int, gfx::Tile16> pending_tile16_changes_;
476 std::map<int, gfx::Bitmap> pending_tile16_bitmaps_;
478 int pending_tile_switch_target_ = -1; // Target tile for pending switch
479
480 // Navigation controls for expanded tile support
481 int jump_to_tile_id_ = 0; // Input field for jump to tile ID
482 bool scroll_to_current_ = false; // Flag to scroll to current tile
483 int current_page_ = 0; // Current page (64 tiles per page)
484 static constexpr int kTilesPerPage = 64; // 8x8 tiles per page
485 static constexpr int kTilesPerRow = 8; // Tiles per row in grid
486
489
490 std::array<uint8_t, 0x200> all_tiles_types_;
491
492 // Tile16 blockset for selecting the tile to edit
500
501 // Canvas for editing the selected tile - optimized for 2x2 grid of 8x8 tiles
502 // (16x16 total)
504 "Tile16EditCanvas",
505 ImVec2(64, 64), // Fixed 64x64 display size (16x16 pixels at 4x scale)
506 gui::CanvasGridSize::k8x8, 8.0F}; // 8x8 grid with 4x scale for clarity
508
509 // Tile8 canvas to get the tile to drawing in the tile16_edit_canvas_
515
516 gui::Table tile_edit_table_{"##TileEditTable", 3, ImGuiTableFlags_Borders,
517 ImVec2(0, 0)};
518
520 std::vector<gfx::Bitmap> current_gfx_individual_;
521
524 gfx::SnesPalette overworld_palette_; // Complete 256-color overworld palette
525
526 absl::Status status_;
527
528 // Callback to notify parent editor when changes are committed
529 std::function<absl::Status()> on_changes_committed_;
530
531 // Instance variable to store current tile16 data for proper persistence
533
534 // Apply the active palette (overworld area if available) to the current
535 // tile16 bitmap using sheet-aware offsets.
537
538 // Handle keyboard shortcuts (shared between Update and UpdateAsPanel)
540
541 // Copy current tile16 bitmap pixels into the blockset atlas at the given
542 // tile position. Consolidates the repeated 16x16 copy loops.
543 void CopyTile16ToAtlas(int tile_id);
544};
545
546} // namespace editor
547} // namespace yaze
548
549#endif // YAZE_APP_EDITOR_TILE16EDITOR_H
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:28
Allows the user to view and edit in game palettes.
Popup window to edit Tile16 data.
absl::Status SaveTile16ToScratchSpace(int slot)
Tile16Editor(Rom *rom, gfx::Tilemap *tile16_blockset)
std::map< int, gfx::Tile16 > pending_tile16_changes_
absl::Status LoadLayoutFromScratch(int slot)
zelda3::GameData * game_data() const
void FinalizePendingUndo()
Finalize any pending undo snapshot by capturing current state as "after" and pushing a Tile16EditActi...
std::chrono::steady_clock::time_point last_edit_time_
util::NotifyValue< uint32_t > notify_tile16
void DrawContextMenu()
Draw context menu with editor actions.
absl::Status CyclePalette(bool forward=true)
absl::Status BatchEdit(const std::vector< int > &tile_ids, const std::function< void(int)> &operation)
absl::Status FillTile16WithTile8(int tile8_id)
gfx::Tilemap * tile16_blockset_
absl::Status CommitAllChanges()
Write all pending changes to ROM and notify parent.
std::map< int, gfx::Bitmap > pending_tile16_bitmaps_
std::array< Tile16ScratchData, 4 > scratch_space_
absl::Status SaveTile16ToROM()
Write current tile16 data directly to ROM (bypasses pending system)
void RestoreFromSnapshot(const Tile16Snapshot &snapshot)
Restore editor state from a Tile16Snapshot (used by undo actions).
absl::Status ApplyPaletteToAll(uint8_t palette_id)
void DiscardAllChanges()
Discard all pending changes (revert to ROM state)
int GetActualPaletteSlotForCurrentTile16() const
Get the palette slot for the current tile being edited.
void set_current_palette(int palette)
absl::Status AutoTileTile16()
std::array< uint8_t, 0x200 > all_tiles_types_
absl::Status RegenerateTile16BitmapFromROM()
absl::Status DiscardChanges()
Discard current tile's changes (single tile)
absl::Status PasteTile16FromClipboard()
gui::TileSelectorWidget blockset_selector_
int pending_changes_count() const
Get count of tiles with pending changes.
absl::Status SaveLayoutToScratch(int slot)
absl::Status LoadTile16FromScratchSpace(int slot)
int GetPaletteSlotForSheet(int sheet_index) const
Get base palette slot for a graphics sheet.
gfx::SnesPalette CreateRemappedPaletteForViewing(const gfx::SnesPalette &source, int target_row) const
Create a remapped palette for viewing with user-selected palette.
int GetActualPaletteSlot(int palette_button, int sheet_index) const
Calculate actual palette slot from button + sheet.
absl::Status FlipTile16Horizontal()
void SetGameData(zelda3::GameData *game_data)
gfx::SnesPalette overworld_palette_
absl::Status Initialize(const gfx::Bitmap &tile16_blockset_bmp, const gfx::Bitmap &current_gfx_bmp, std::array< uint8_t, 0x200 > &all_tiles_types)
static constexpr int kTilesPerPage
int GetEncodedPaletteRow(uint8_t pixel_value) const
Get the encoded palette row for a pixel value.
static constexpr int kTilesPerRow
void EnableLivePreview(bool enable)
absl::Status SetCurrentTile(int id)
absl::Status UpdateTile8Palette(int tile8_id)
Update palette for a specific tile8.
absl::Status UpdateAsPanel()
Update the editor content without MenuBar (for EditorPanel usage)
absl::Status RefreshTile16Blockset()
Tile16ClipboardData clipboard_tile16_
const gfx::Bitmap * GetPendingTileBitmap(int tile_id) const
Get preview bitmap for a pending tile (nullptr if not modified)
zelda3::GameData * game_data_
absl::Status RefreshAllPalettes()
Refresh all tile8 palettes after a palette change.
void DrawPaletteSettings()
Draw palette settings UI.
absl::Status UpdateOverworldTilemap()
Update the overworld tilemap to reflect tile changes.
absl::Status CommitChangesToBlockset()
Commit pending changes to the blockset atlas.
std::array< LayoutScratch, 4 > layout_scratch_
absl::Status UpdateROMTile16Data()
std::optional< Tile16Snapshot > pending_undo_before_
bool IsTile16Valid(int tile_id) const
gfx::Tile16 * GetCurrentTile16Data()
absl::Status ApplyToSelection(const std::function< void(int)> &operation)
void MarkCurrentTileModified()
Mark the current tile as having pending changes.
std::vector< gfx::Bitmap > current_gfx_individual_
bool has_pending_changes() const
Check if any tiles have uncommitted changes.
absl::Status CommitChangesToOverworld()
Full commit workflow: ROM + blockset + notify parent.
absl::Status UpdateBlocksetBitmap()
void DiscardCurrentTileChanges()
Discard only the current tile's pending changes.
void RequestTileSwitch(int target_tile_id)
absl::Status DrawToCurrentTile16(ImVec2 pos, const gfx::Bitmap *source_tile=nullptr)
absl::Status PreviewPaletteChange(uint8_t palette_id)
absl::Status CopyTile16ToClipboard(int tile_id)
absl::Status ClearScratchSpace(int slot)
bool is_tile_modified(int tile_id) const
Check if a specific tile has pending changes.
std::function< absl::Status()> on_changes_committed_
int GetPaletteBaseForSheet(int sheet_index) const
Get palette base row for a graphics sheet.
int GetSheetIndexForTile8(int tile8_id) const
Determine which graphics sheet contains a tile8.
absl::Status PickTile8FromTile16(const ImVec2 &position)
std::vector< int > selected_tiles_
void CopyTile16ToAtlas(int tile_id)
void set_palette(const gfx::SnesPalette &palette)
util::NotifyValue< uint8_t > notify_palette
void set_on_changes_committed(std::function< absl::Status()> callback)
Manages undo/redo stacks for a single editor.
Represents a bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:67
bool is_active() const
Definition bitmap.h:384
Shared graphical context across editors.
Represents a palette of colors for the Super Nintendo Entertainment System (SNES).
Tile composition of four 8x8 tiles.
Definition snes_tile.h:142
Modern, robust canvas for drawing and manipulating graphics.
Definition canvas.h:150
Reusable tile selector built on top of Canvas.
A class to manage a value that can be modified and notify when it changes.
Definition notify.h:13
constexpr int kTile16PixelCount
constexpr int kTile8PixelCount
constexpr int kTile16CanvasSize
constexpr int kTile8Size
constexpr int kTilesheetEditorHeight
constexpr int kTile16Size
constexpr int kNumScratchSlots
constexpr int kTile8CanvasHeight
constexpr int kNumPalettes
constexpr int kTilesheetEditorWidth
constexpr int kTilesheetHeight
Definition snes_tile.h:17
constexpr int kTilesheetWidth
Definition snes_tile.h:16
void logf(const absl::FormatSpec< Args... > &format, Args &&... args)
Definition log.h:115
std::array< std::array< int, 8 >, 8 > tile_layout
Snapshot of a Tile16's editable state for undo/redo.
Tilemap structure for SNES tile-based graphics management.
Definition tilemap.h:118