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 <vector>
10
11#include "absl/status/status.h"
13#include "app/gfx/core/bitmap.h"
17#include "app/gui/core/input.h"
19#include "rom/rom.h"
20#include "imgui/imgui.h"
21#include "util/log.h"
22#include "util/notify.h"
23
24namespace yaze {
25namespace zelda3 {
26struct GameData;
27} // namespace zelda3
28
29namespace editor {
30
31// ============================================================================
32// Tile16 Editor Constants
33// ============================================================================
34
35constexpr int kTile16Size = 16; // 16x16 pixel tile
36constexpr int kTile8Size = 8; // 8x8 pixel sub-tile
37constexpr int kTilesheetEditorWidth = 0x100; // 256 pixels wide
38constexpr int kTilesheetEditorHeight = 0x4000; // 16384 pixels tall
39constexpr int kTile16CanvasSize = 0x20; // 32 pixels
40constexpr int kTile8CanvasHeight = 0x175; // 373 pixels
41constexpr int kNumScratchSlots = 4; // 4 scratch space slots
42constexpr int kNumPalettes = 8; // 8 palette buttons (0-7)
43constexpr int kTile8PixelCount = 64; // 8x8 = 64 pixels
44constexpr int kTile16PixelCount = 256; // 16x16 = 256 pixels
45
46// ============================================================================
47// Tile16 Editor
48// ============================================================================
49//
50// ARCHITECTURE OVERVIEW:
51// ----------------------
52// The Tile16Editor provides a popup window for editing individual 16x16 tiles
53// used in the overworld tileset. Each Tile16 is composed of four 8x8 sub-tiles
54// (Tile8) arranged in a 2x2 grid.
55//
56// EDITING WORKFLOW:
57// -----------------
58// 1. Select a Tile16 from the blockset canvas (left panel)
59// 2. Edit by clicking on the tile8 source canvas to select sub-tiles
60// 3. Place selected tile8s into the four quadrants of the Tile16
61// 4. Changes are held as "pending" until explicitly committed or discarded
62// 5. Commit saves to ROM; Discard reverts to original
63//
64// PENDING CHANGES SYSTEM:
65// -----------------------
66// To prevent accidental ROM modifications, all edits are staged:
67// - pending_tile16_changes_: Maps tile ID -> modified Tile16 data
68// - pending_tile16_bitmaps_: Maps tile ID -> preview bitmap
69// - has_pending_changes(): Returns true if any tiles are modified
70// - CommitAllChanges(): Writes all pending changes to ROM
71// - DiscardAllChanges(): Reverts all pending changes
72//
73// PALETTE COORDINATION:
74// ---------------------
75// The overworld uses a 256-color palette organized as 16 rows of 16 colors.
76// Different graphics sheets map to different palette regions:
77//
78// Sheet Index | Palette Region | Purpose
79// ------------|----------------|------------------------
80// 0, 3, 4 | AUX1 (row 10+) | Main blockset graphics
81// 1, 2 | MAIN (row 2+) | Main area graphics
82// 5, 6 | AUX2 (row 10+) | Secondary blockset
83// 7 | ANIMATED | Animated tiles
84//
85// Key palette methods:
86// - GetPaletteSlotForSheet(): Get base palette slot for a sheet
87// - GetActualPaletteSlot(): Combine palette button + sheet to get final slot
88// - GetActualPaletteSlotForCurrentTile16(): Get slot for current editing tile
89// - ApplyPaletteToCurrentTile16Bitmap(): Apply correct colors to preview
90//
91// INTEGRATION WITH OVERWORLD:
92// ---------------------------
93// The Tile16Editor communicates with OverworldEditor via:
94// - set_palette(): Called when overworld area changes (updates colors)
95// - on_changes_committed_: Callback invoked after CommitAllChanges()
96// - The callback triggers RefreshTile16Blockset() and RefreshOverworldMap()
97//
98// See README.md in this directory for complete documentation.
99// ============================================================================
100
110 public:
111 Tile16Editor(Rom* rom, gfx::Tilemap* tile16_blockset)
112 : rom_(rom), tile16_blockset_(tile16_blockset) {}
113 absl::Status Initialize(const gfx::Bitmap& tile16_blockset_bmp,
114 const gfx::Bitmap& current_gfx_bmp,
115 std::array<uint8_t, 0x200>& all_tiles_types);
116
117 absl::Status Update();
118
125 absl::Status UpdateAsPanel();
126
133 void DrawContextMenu();
134
135 void DrawTile16Editor();
136 absl::Status UpdateBlockset();
137
138 // Scratch space for tile16 layouts
139 void DrawScratchSpace();
140 absl::Status SaveLayoutToScratch(int slot);
141 absl::Status LoadLayoutFromScratch(int slot);
142
143 absl::Status DrawToCurrentTile16(ImVec2 pos,
144 const gfx::Bitmap* source_tile = nullptr);
145
146 absl::Status UpdateTile16Edit();
147
148 absl::Status LoadTile8();
149
150 absl::Status SetCurrentTile(int id);
151
152 // Request a tile switch - shows confirmation dialog if current tile has pending changes
153 void RequestTileSwitch(int target_tile_id);
154
155 // New methods for clipboard and scratch space
156 absl::Status CopyTile16ToClipboard(int tile_id);
157 absl::Status PasteTile16FromClipboard();
158 absl::Status SaveTile16ToScratchSpace(int slot);
159 absl::Status LoadTile16FromScratchSpace(int slot);
160 absl::Status ClearScratchSpace(int slot);
161
162 // Advanced editing features
163 absl::Status FlipTile16Horizontal();
164 absl::Status FlipTile16Vertical();
165 absl::Status RotateTile16();
166 absl::Status FillTile16WithTile8(int tile8_id);
167 absl::Status AutoTileTile16();
168 absl::Status ClearTile16();
169
170 // Palette management
171 absl::Status CyclePalette(bool forward = true);
172 absl::Status ApplyPaletteToAll(uint8_t palette_id);
173 absl::Status PreviewPaletteChange(uint8_t palette_id);
174
175 // Batch operations
176 absl::Status ApplyToSelection(const std::function<void(int)>& operation);
177 absl::Status BatchEdit(const std::vector<int>& tile_ids,
178 const std::function<void(int)>& operation);
179
180 // History and undo system
181 absl::Status Undo();
182 absl::Status Redo();
183 void SaveUndoState();
184
185 // Live preview system
186 void EnableLivePreview(bool enable) { live_preview_enabled_ = enable; }
187 absl::Status UpdateLivePreview();
188
189 // Validation and integrity checks
190 absl::Status ValidateTile16Data();
191 bool IsTile16Valid(int tile_id) const;
192
193 // ===========================================================================
194 // Integration with Overworld System
195 // ===========================================================================
196 // These methods handle the connection between tile editing and ROM data.
197 // The workflow is: Edit -> Pending -> Commit -> ROM
198
200 absl::Status SaveTile16ToROM();
201
203 absl::Status UpdateOverworldTilemap();
204
206 absl::Status CommitChangesToBlockset();
207
209 absl::Status CommitChangesToOverworld();
210
212 absl::Status DiscardChanges();
213
214 // ===========================================================================
215 // Pending Changes System
216 // ===========================================================================
217 // All tile edits are staged in memory before being written to ROM.
218 // This prevents accidental modifications and allows preview before commit.
219 //
220 // Usage:
221 // 1. Edit tiles normally (changes go to pending_tile16_changes_)
222 // 2. Check has_pending_changes() to show save/discard UI
223 // 3. User clicks Save -> CommitAllChanges()
224 // 4. User clicks Discard -> DiscardAllChanges()
225 //
226 // The on_changes_committed_ callback notifies OverworldEditor to refresh.
227
229 bool has_pending_changes() const { return !pending_tile16_changes_.empty(); }
230
233 return static_cast<int>(pending_tile16_changes_.size());
234 }
235
237 bool is_tile_modified(int tile_id) const {
238 return pending_tile16_changes_.find(tile_id) != pending_tile16_changes_.end();
239 }
240
242 const gfx::Bitmap* GetPendingTileBitmap(int tile_id) const {
243 auto it = pending_tile16_bitmaps_.find(tile_id);
244 return it != pending_tile16_bitmaps_.end() ? &it->second : nullptr;
245 }
246
248 absl::Status CommitAllChanges();
249
251 void DiscardAllChanges();
252
255
258
259 // ===========================================================================
260 // Palette Coordination System
261 // ===========================================================================
262 // The overworld uses a 256-color palette organized as 16 rows of 16 colors.
263 // Different graphics sheets map to different palette regions based on how
264 // the SNES PPU organizes tile graphics.
265 //
266 // Palette Structure (256 colors = 16 rows × 16 colors):
267 // Row 0: Transparent/system colors
268 // Row 1: HUD colors (0x10-0x1F)
269 // Rows 2-6: MAIN/BG palettes for main graphics (sheets 1-2)
270 // Rows 7: ANIMATED palette (sheet 7)
271 // Rows 10+: AUX palettes for blockset graphics (sheets 0, 3-6)
272 //
273 // The palette button (0-7) selects which of the 8 available sub-palettes
274 // to use, and the sheet index determines the base offset.
275
277 absl::Status UpdateTile8Palette(int tile8_id);
278
280 absl::Status RefreshAllPalettes();
281
283 void DrawPaletteSettings();
284
288 int GetPaletteSlotForSheet(int sheet_index) const;
289
299 int GetActualPaletteSlot(int palette_button, int sheet_index) const;
300
304 int GetPaletteBaseForSheet(int sheet_index) const;
305
309 int GetSheetIndexForTile8(int tile8_id) const;
310
314
320 const gfx::SnesPalette& source, int target_row) const;
321
325 int GetEncodedPaletteRow(uint8_t pixel_value) const;
326
327 // ROM data access and modification
328 absl::Status UpdateROMTile16Data();
329 absl::Status RefreshTile16Blockset();
331 absl::Status RegenerateTile16BitmapFromROM();
332 absl::Status UpdateBlocksetBitmap();
333 absl::Status PickTile8FromTile16(const ImVec2& position);
334
335 // Manual tile8 input controls
337
338 void SetRom(Rom* rom) { rom_ = rom; }
339 Rom* rom() const { return rom_; }
342
343 // Set the palette from overworld to ensure color consistency
344 void set_palette(const gfx::SnesPalette& palette) {
345 palette_ = palette;
346
347 // Store the complete 256-color overworld palette
348 if (palette.size() >= 256) {
349 overworld_palette_ = palette;
351 "Tile16 editor received complete overworld palette with %zu colors",
352 palette.size());
353 } else {
354 util::logf("Warning: Received incomplete palette with %zu colors",
355 palette.size());
356 overworld_palette_ = palette;
357 }
358
359 // CRITICAL FIX: Load tile8 graphics now that we have the proper palette
361 auto status = LoadTile8();
362 if (!status.ok()) {
363 util::logf("Failed to load tile8 graphics with new palette: %s",
364 status.message().data());
365 } else {
367 "Successfully loaded tile8 graphics with complete overworld "
368 "palette");
369 }
370 }
371
372 util::logf("Tile16 editor palette coordination complete");
373 }
374
375 // Callback for when changes are committed to notify parent editor
376 void set_on_changes_committed(std::function<absl::Status()> callback) {
377 on_changes_committed_ = callback;
378 }
379
380 // Accessors for testing and external use
381 int current_palette() const { return current_palette_; }
382 void set_current_palette(int palette) {
383 current_palette_ = static_cast<uint8_t>(std::clamp(palette, 0, 7));
384 }
385 int current_tile16() const { return current_tile16_; }
386 int current_tile8() const { return current_tile8_; }
387
388 // Diagnostic function to analyze tile8 source data format
389 void AnalyzeTile8SourceData() const;
390
391 private:
392 Rom* rom_ = nullptr;
395 bool x_flip = false;
396 bool y_flip = false;
397 bool priority_tile = false;
398
402 uint8_t current_palette_ = 0;
403
404 // Clipboard for Tile16 graphics
407
408 // Scratch space for Tile16 graphics (4 slots)
409 std::array<gfx::Bitmap, 4> scratch_space_;
410 std::array<bool, 4> scratch_space_used_ = {false, false, false, false};
411
412 // Layout scratch space for tile16 arrangements (4 slots of 8x8 grids)
414 std::array<std::array<int, 8>, 8> tile_layout; // 8x8 grid of tile16 IDs
415 bool in_use = false;
416 std::string name = "Empty";
417 };
418 std::array<LayoutScratch, 4> layout_scratch_;
419
420 // Undo/Redo system
428 std::vector<UndoState> undo_stack_;
429 std::vector<UndoState> redo_stack_;
430 static constexpr size_t kMaxUndoStates_ = 50;
431
432 // Live preview system
435 bool preview_dirty_ = false;
436 gfx::Bitmap tile8_preview_bmp_; // Persistent preview to keep arena commands valid
437
438 // Selection system
439 std::vector<int> selected_tiles_;
441 bool multi_select_mode_ = false;
442
443 // Advanced editing state
444 bool auto_tile_mode_ = false;
446 bool show_tile_info_ = true;
448
449 // Palette management settings
451 int current_palette_group_ = 0; // 0=overworld_main, 1=aux1, 2=aux2, etc.
453 0xFF; // Default 8-bit mask (preserve full palette index)
455 false; // Disabled by default to preserve palette offsets
456
457 // Performance tracking
458 std::chrono::steady_clock::time_point last_edit_time_;
459 bool batch_mode_ = false;
460
461 // Pending changes system for batch preview/commit workflow
462 std::map<int, gfx::Tile16> pending_tile16_changes_;
463 std::map<int, gfx::Bitmap> pending_tile16_bitmaps_;
465 int pending_tile_switch_target_ = -1; // Target tile for pending switch
466
467 // Navigation controls for expanded tile support
468 int jump_to_tile_id_ = 0; // Input field for jump to tile ID
469 bool scroll_to_current_ = false; // Flag to scroll to current tile
470 int current_page_ = 0; // Current page (64 tiles per page)
471 static constexpr int kTilesPerPage = 64; // 8x8 tiles per page
472 static constexpr int kTilesPerRow = 8; // Tiles per row in grid
473
476
477 std::array<uint8_t, 0x200> all_tiles_types_;
478
479 // Tile16 blockset for selecting the tile to edit
485
486 // Canvas for editing the selected tile - optimized for 2x2 grid of 8x8 tiles
487 // (16x16 total)
489 "Tile16EditCanvas",
490 ImVec2(64, 64), // Fixed 64x64 display size (16x16 pixels at 4x scale)
491 gui::CanvasGridSize::k8x8, 8.0F}; // 8x8 grid with 4x scale for clarity
493
494 // Tile8 canvas to get the tile to drawing in the tile16_edit_canvas_
500
501 gui::Table tile_edit_table_{"##TileEditTable", 3, ImGuiTableFlags_Borders,
502 ImVec2(0, 0)};
503
505 std::vector<gfx::Bitmap> current_gfx_individual_;
506
509 gfx::SnesPalette overworld_palette_; // Complete 256-color overworld palette
510
511 absl::Status status_;
512
513 // Callback to notify parent editor when changes are committed
514 std::function<absl::Status()> on_changes_committed_;
515
516 // Instance variable to store current tile16 data for proper persistence
518
519 // Apply the active palette (overworld area if available) to the current
520 // tile16 bitmap using sheet-aware offsets.
522};
523
524} // namespace editor
525} // namespace yaze
526
527#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:24
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
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_
absl::Status SaveTile16ToROM()
Write current tile16 data directly to ROM (bypasses pending system)
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.
std::vector< UndoState > undo_stack_
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()
const gfx::Bitmap * GetPendingTileBitmap(int tile_id) const
Get preview bitmap for a pending tile (nullptr if not modified)
zelda3::GameData * game_data_
std::array< gfx::Bitmap, 4 > scratch_space_
absl::Status RefreshAllPalettes()
Refresh all tile8 palettes after a palette change.
void DrawPaletteSettings()
Draw palette settings UI.
std::vector< UndoState > redo_stack_
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()
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_
static constexpr size_t kMaxUndoStates_
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.
std::array< bool, 4 > scratch_space_used_
absl::Status PickTile8FromTile16(const ImVec2 &position)
std::vector< int > selected_tiles_
void set_palette(const gfx::SnesPalette &palette)
util::NotifyValue< uint8_t > notify_palette
void set_on_changes_committed(std::function< absl::Status()> callback)
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:140
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
Tilemap structure for SNES tile-based graphics management.
Definition tilemap.h:118