yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
palette_manager.cc
Go to the documentation of this file.
1#include "palette_manager.h"
2
3#include <chrono>
4
5#include "absl/strings/str_format.h"
8#include "rom/rom.h"
9#include "util/macro.h"
10#include "zelda3/game_data.h"
11
12namespace yaze {
13namespace gfx {
14
16 if (!game_data) {
17 return;
18 }
19
20 game_data_ = game_data;
21 rom_ = game_data->rom();
22
23 // Load original palette snapshots for all groups
24 auto* palette_groups = &game_data_->palette_groups;
25
26 // Snapshot all palette groups
27 const char* group_names[] = {"ow_main", "ow_aux", "ow_animated",
28 "hud", "global_sprites", "armors",
29 "swords", "shields", "sprites_aux1",
30 "sprites_aux2", "sprites_aux3", "dungeon_main",
31 "grass", "3d_object", "ow_mini_map"};
32
33 for (const auto& group_name : group_names) {
34 try {
35 auto* group = palette_groups->get_group(group_name);
36 if (group) {
37 std::vector<SnesPalette> originals;
38 for (size_t i = 0; i < group->size(); i++) {
39 originals.push_back(group->palette(i));
40 }
41 original_palettes_[group_name] = originals;
42 }
43 } catch (const std::exception& e) {
44 // Group doesn't exist, skip
45 continue;
46 }
47 }
48
49 // Clear any existing state
51 modified_colors_.clear();
53}
54
56 // Legacy initialization - not supported in new architecture
57 // Keep ROM pointer for backwards compatibility but log warning
58 if (!rom) {
59 return;
60 }
61 rom_ = rom;
62 game_data_ = nullptr;
63
64 // Clear any existing state
65 modified_palettes_.clear();
66 modified_colors_.clear();
68}
69
71 game_data_ = nullptr;
72 rom_ = nullptr;
73 original_palettes_.clear();
74 modified_palettes_.clear();
75 modified_colors_.clear();
76 change_listeners_.clear();
78 batch_depth_ = 0;
79 batch_changes_.clear();
81}
82
83// ========== Color Operations ==========
84
85SnesColor PaletteManager::GetColor(const std::string& group_name,
86 int palette_index, int color_index) const {
87 const auto* group = GetGroup(group_name);
88 if (!group || palette_index < 0 || palette_index >= group->size()) {
89 return SnesColor();
90 }
91
92 const auto& palette = group->palette_ref(palette_index);
93 if (color_index < 0 || color_index >= palette.size()) {
94 return SnesColor();
95 }
96
97 return palette[color_index];
98}
99
100absl::Status PaletteManager::SetColor(const std::string& group_name,
101 int palette_index, int color_index,
102 const SnesColor& new_color) {
103 if (!IsInitialized()) {
104 return absl::FailedPreconditionError("PaletteManager not initialized");
105 }
106
107 auto* group = GetMutableGroup(group_name);
108 if (!group) {
109 return absl::NotFoundError(
110 absl::StrFormat("Palette group '%s' not found", group_name));
111 }
112
113 if (palette_index < 0 || palette_index >= group->size()) {
114 return absl::InvalidArgumentError(absl::StrFormat(
115 "Palette index %d out of range [0, %d)", palette_index, group->size()));
116 }
117
118 auto* palette = group->mutable_palette(palette_index);
119 if (color_index < 0 || color_index >= palette->size()) {
120 return absl::InvalidArgumentError(absl::StrFormat(
121 "Color index %d out of range [0, %d)", color_index, palette->size()));
122 }
123
124 // Get original color
125 SnesColor original_color = (*palette)[color_index];
126
127 // Update in-memory palette
128 (*palette)[color_index] = new_color;
129
130 // Track modification
131 MarkModified(group_name, palette_index, color_index);
132
133 // Record for undo (unless in batch mode - batch changes recorded separately)
134 if (!InBatch()) {
135 auto now = std::chrono::system_clock::now();
136 auto timestamp_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
137 now.time_since_epoch())
138 .count();
139
140 PaletteColorChange change{group_name, palette_index,
141 color_index, original_color,
142 new_color, static_cast<uint64_t>(timestamp_ms)};
143 RecordChange(change);
144
145 // Notify listeners
147 group_name, palette_index, color_index};
148 NotifyListeners(event);
149 } else {
150 // Store in batch buffer
151 auto now = std::chrono::system_clock::now();
152 auto timestamp_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
153 now.time_since_epoch())
154 .count();
155 batch_changes_.push_back({group_name, palette_index, color_index,
156 original_color, new_color,
157 static_cast<uint64_t>(timestamp_ms)});
158 }
159
160 return absl::OkStatus();
161}
162
163absl::Status PaletteManager::ResetColor(const std::string& group_name,
164 int palette_index, int color_index) {
165 SnesColor original = GetOriginalColor(group_name, palette_index, color_index);
166 return SetColor(group_name, palette_index, color_index, original);
167}
168
169absl::Status PaletteManager::ResetPalette(const std::string& group_name,
170 int palette_index) {
171 if (!IsInitialized()) {
172 return absl::FailedPreconditionError("PaletteManager not initialized");
173 }
174
175 // Check if original snapshot exists
176 auto it = original_palettes_.find(group_name);
177 if (it == original_palettes_.end() || palette_index >= it->second.size()) {
178 return absl::NotFoundError("Original palette not found");
179 }
180
181 auto* group = GetMutableGroup(group_name);
182 if (!group || palette_index >= group->size()) {
183 return absl::NotFoundError("Palette group or index not found");
184 }
185
186 // Restore from original
187 *group->mutable_palette(palette_index) = it->second[palette_index];
188
189 // Clear modified flags for this palette
190 modified_palettes_[group_name].erase(palette_index);
191 modified_colors_[group_name].erase(palette_index);
192
193 // Notify listeners
195 palette_index, -1};
196 NotifyListeners(event);
197
198 return absl::OkStatus();
199}
200
201// ========== Dirty Tracking ==========
202
204 return !modified_palettes_.empty();
205}
206
207std::vector<std::string> PaletteManager::GetModifiedGroups() const {
208 std::vector<std::string> groups;
209 for (const auto& [group_name, _] : modified_palettes_) {
210 groups.push_back(group_name);
211 }
212 return groups;
213}
214
215bool PaletteManager::IsGroupModified(const std::string& group_name) const {
216 auto it = modified_palettes_.find(group_name);
217 return it != modified_palettes_.end() && !it->second.empty();
218}
219
220bool PaletteManager::IsPaletteModified(const std::string& group_name,
221 int palette_index) const {
222 auto it = modified_palettes_.find(group_name);
223 if (it == modified_palettes_.end()) {
224 return false;
225 }
226 return it->second.contains(palette_index);
227}
228
229bool PaletteManager::IsColorModified(const std::string& group_name,
230 int palette_index, int color_index) const {
231 auto group_it = modified_colors_.find(group_name);
232 if (group_it == modified_colors_.end()) {
233 return false;
234 }
235
236 auto pal_it = group_it->second.find(palette_index);
237 if (pal_it == group_it->second.end()) {
238 return false;
239 }
240
241 return pal_it->second.contains(color_index);
242}
243
245 size_t count = 0;
246 for (const auto& [_, palette_map] : modified_colors_) {
247 for (const auto& [__, color_set] : palette_map) {
248 count += color_set.size();
249 }
250 }
251 return count;
252}
253
254// ========== Persistence ==========
255
256absl::Status PaletteManager::SaveGroup(const std::string& group_name) {
257 if (!IsInitialized()) {
258 return absl::FailedPreconditionError("PaletteManager not initialized");
259 }
260
261 Rom* rom = rom_;
262 if (!rom && game_data_) {
263 rom = game_data_->rom();
264 }
265 if (!rom) {
266 return absl::FailedPreconditionError("No ROM available for palette save");
267 }
268
269 auto* group = GetMutableGroup(group_name);
270 if (!group) {
271 return absl::NotFoundError(
272 absl::StrFormat("Palette group '%s' not found", group_name));
273 }
274
275 // Get modified palettes for this group
276 auto pal_it = modified_palettes_.find(group_name);
277 if (pal_it == modified_palettes_.end() || pal_it->second.empty()) {
278 // No changes to save
279 return absl::OkStatus();
280 }
281
282 // Write each modified palette
283 for (int palette_idx : pal_it->second) {
284 auto* palette = group->mutable_palette(palette_idx);
285
286 // Get modified colors for this palette
287 auto color_it = modified_colors_[group_name].find(palette_idx);
288 if (color_it != modified_colors_[group_name].end()) {
289 for (int color_idx : color_it->second) {
290 // Calculate ROM address using the helper function
291 uint32_t address =
292 GetPaletteAddress(group_name, palette_idx, color_idx);
293
294 // Write color to ROM - write the 16-bit SNES color value
295 RETURN_IF_ERROR(rom->WriteShort(address, (*palette)[color_idx].snes()));
296 }
297 }
298 }
299
300 // Update original snapshots
301 auto& originals = original_palettes_[group_name];
302 for (size_t i = 0; i < group->size() && i < originals.size(); i++) {
303 originals[i] = group->palette(i);
304 }
305
306 // Clear modified flags for this group
307 ClearModifiedFlags(group_name);
308
309 // Mark ROM as dirty
310 rom->set_dirty(true);
311
312 // Notify listeners
314 -1, -1};
315 NotifyListeners(event);
316
317 // Notify Arena for bitmap propagation to other editors
318 Arena::Get().NotifyPaletteModified(group_name, -1);
319
320 return absl::OkStatus();
321}
322
324 if (!IsInitialized()) {
325 return absl::FailedPreconditionError("PaletteManager not initialized");
326 }
327
328 // Save all modified groups
329 for (const auto& group_name : GetModifiedGroups()) {
330 RETURN_IF_ERROR(SaveGroup(group_name));
331 }
332
333 // Notify listeners
335 NotifyListeners(event);
336
337 return absl::OkStatus();
338}
339
341 if (!IsInitialized()) {
342 return absl::FailedPreconditionError("PaletteManager not initialized");
343 }
344
345 // Get all modified groups and notify Arena for each
346 // This triggers bitmap refresh in other editors WITHOUT saving to ROM
347 auto modified_groups = GetModifiedGroups();
348
349 if (modified_groups.empty()) {
350 return absl::OkStatus(); // Nothing to preview
351 }
352
353 for (const auto& group_name : modified_groups) {
354 Arena::Get().NotifyPaletteModified(group_name, -1);
355 }
356
357 // Notify listeners that preview was applied
359 NotifyListeners(event);
360
361 return absl::OkStatus();
362}
363
364void PaletteManager::DiscardGroup(const std::string& group_name) {
365 if (!IsInitialized()) {
366 return;
367 }
368
369 auto* group = GetMutableGroup(group_name);
370 if (!group) {
371 return;
372 }
373
374 // Get modified palettes
375 auto pal_it = modified_palettes_.find(group_name);
376 if (pal_it == modified_palettes_.end()) {
377 return;
378 }
379
380 // Restore from original snapshots
381 auto orig_it = original_palettes_.find(group_name);
382 if (orig_it != original_palettes_.end()) {
383 for (int palette_idx : pal_it->second) {
384 if (palette_idx < orig_it->second.size()) {
385 *group->mutable_palette(palette_idx) = orig_it->second[palette_idx];
386 }
387 }
388 }
389
390 // Clear modified flags
391 ClearModifiedFlags(group_name);
392
393 // Notify listeners
395 group_name, -1, -1};
396 NotifyListeners(event);
397}
398
400 if (!IsInitialized()) {
401 return;
402 }
403
404 // Discard all modified groups
405 for (const auto& group_name : GetModifiedGroups()) {
406 DiscardGroup(group_name);
407 }
408
409 // Clear undo/redo
410 ClearHistory();
411
412 // Notify listeners
414 NotifyListeners(event);
415}
416
417// ========== Undo/Redo ==========
418
420 if (!CanUndo()) {
421 return;
422 }
423
424 auto change = undo_stack_.back();
425 undo_stack_.pop_back();
426
427 // Restore original color
428 auto* group = GetMutableGroup(change.group_name);
429 if (group && change.palette_index < group->size()) {
430 auto* palette = group->mutable_palette(change.palette_index);
431 if (change.color_index < palette->size()) {
432 (*palette)[change.color_index] = change.original_color;
433 }
434 }
435
436 // Move to redo stack
437 redo_stack_.push_back(change);
438
439 // Notify listeners
441 change.group_name, change.palette_index,
442 change.color_index};
443 NotifyListeners(event);
444}
445
447 if (!CanRedo()) {
448 return;
449 }
450
451 auto change = redo_stack_.back();
452 redo_stack_.pop_back();
453
454 // Reapply new color
455 auto* group = GetMutableGroup(change.group_name);
456 if (group && change.palette_index < group->size()) {
457 auto* palette = group->mutable_palette(change.palette_index);
458 if (change.color_index < palette->size()) {
459 (*palette)[change.color_index] = change.new_color;
460 }
461 }
462
463 // Move back to undo stack
464 undo_stack_.push_back(change);
465
466 // Notify listeners
468 change.group_name, change.palette_index,
469 change.color_index};
470 NotifyListeners(event);
471}
472
474 undo_stack_.clear();
475 redo_stack_.clear();
476}
477
478// ========== Change Notifications ==========
479
481 int id = next_callback_id_++;
482 change_listeners_[id] = callback;
483 return id;
484}
485
487 change_listeners_.erase(callback_id);
488}
489
490// ========== Batch Operations ==========
491
493 batch_depth_++;
494 if (batch_depth_ == 1) {
495 batch_changes_.clear();
496 }
497}
498
500 if (batch_depth_ == 0) {
501 return;
502 }
503
504 batch_depth_--;
505
506 if (batch_depth_ == 0 && !batch_changes_.empty()) {
507 // Commit all batch changes as a single undo step
508 for (const auto& change : batch_changes_) {
509 RecordChange(change);
510
511 // Notify listeners for each change
513 change.group_name, change.palette_index,
514 change.color_index};
515 NotifyListeners(event);
516 }
517
518 batch_changes_.clear();
519 }
520}
521
522// ========== Private Helpers ==========
523
524PaletteGroup* PaletteManager::GetMutableGroup(const std::string& group_name) {
525 if (!IsInitialized()) {
526 return nullptr;
527 }
528 try {
529 if (game_data_) {
530 return game_data_->palette_groups.get_group(group_name);
531 }
532 return nullptr; // Legacy ROM-only mode not supported
533 } catch (const std::exception&) {
534 return nullptr;
535 }
536}
537
539 const std::string& group_name) const {
540 if (!IsInitialized()) {
541 return nullptr;
542 }
543 try {
544 if (game_data_) {
545 return const_cast<PaletteGroupMap*>(&game_data_->palette_groups)
546 ->get_group(group_name);
547 }
548 return nullptr; // Legacy ROM-only mode not supported
549 } catch (const std::exception&) {
550 return nullptr;
551 }
552}
553
554SnesColor PaletteManager::GetOriginalColor(const std::string& group_name,
555 int palette_index,
556 int color_index) const {
557 auto it = original_palettes_.find(group_name);
558 if (it == original_palettes_.end() || palette_index >= it->second.size()) {
559 return SnesColor();
560 }
561
562 const auto& palette = it->second[palette_index];
563 if (color_index >= palette.size()) {
564 return SnesColor();
565 }
566
567 return palette[color_index];
568}
569
571 undo_stack_.push_back(change);
572
573 // Limit history size
574 if (undo_stack_.size() > kMaxUndoHistory) {
575 undo_stack_.pop_front();
576 }
577
578 // Clear redo stack (can't redo after a new change)
579 redo_stack_.clear();
580}
581
583 for (const auto& [_, callback] : change_listeners_) {
584 callback(event);
585 }
586}
587
588void PaletteManager::MarkModified(const std::string& group_name,
589 int palette_index, int color_index) {
590 modified_palettes_[group_name].insert(palette_index);
591 modified_colors_[group_name][palette_index].insert(color_index);
592}
593
594void PaletteManager::ClearModifiedFlags(const std::string& group_name) {
595 modified_palettes_.erase(group_name);
596 modified_colors_.erase(group_name);
597}
598
599} // namespace gfx
600} // namespace yaze
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
void set_dirty(bool dirty)
Definition rom.h:130
absl::Status WriteShort(int addr, uint16_t value)
Definition rom.cc:320
static Arena & Get()
Definition arena.cc:20
void NotifyPaletteModified(const std::string &group_name, int palette_index=-1)
Notify all listeners that a palette has been modified.
Definition arena.cc:353
void RecordChange(const PaletteColorChange &change)
Helper: Record a change for undo.
absl::Status SetColor(const std::string &group_name, int palette_index, int color_index, const SnesColor &new_color)
Set a color in a palette (records change for undo)
std::vector< std::string > GetModifiedGroups() const
Get list of modified palette group names.
static constexpr size_t kMaxUndoHistory
bool HasUnsavedChanges() const
Check if there are ANY unsaved changes.
bool IsGroupModified(const std::string &group_name) const
Check if a specific palette group has modifications.
absl::Status SaveGroup(const std::string &group_name)
Save a specific palette group to ROM.
void BeginBatch()
Begin a batch operation (groups multiple changes into one undo step)
PaletteGroup * GetMutableGroup(const std::string &group_name)
Helper: Get mutable palette group.
void Undo()
Undo the most recent change.
void ClearHistory()
Clear undo/redo history.
std::unordered_map< int, ChangeCallback > change_listeners_
Change listeners.
int batch_depth_
Batch operation support.
std::deque< PaletteColorChange > redo_stack_
std::unordered_map< std::string, std::unordered_set< int > > modified_palettes_
void UnregisterChangeListener(int callback_id)
Unregister a change listener.
Rom * rom_
ROM instance (not owned) - legacy, used when game_data_ is null.
std::unordered_map< std::string, std::unordered_map< int, std::unordered_set< int > > > modified_colors_
bool CanRedo() const
Check if redo is available.
bool InBatch() const
Check if currently in a batch operation.
bool CanUndo() const
Check if undo is available.
void Initialize(zelda3::GameData *game_data)
Initialize the palette manager with GameData.
void ResetForTesting()
Reset all state for test isolation.
absl::Status ResetColor(const std::string &group_name, int palette_index, int color_index)
Reset a single color to its original ROM value.
void NotifyListeners(const PaletteChangeEvent &event)
Helper: Notify all listeners of an event.
void ClearModifiedFlags(const std::string &group_name)
Helper: Clear modified flags for a group.
void EndBatch()
End a batch operation.
int RegisterChangeListener(ChangeCallback callback)
Register a callback for palette change events.
std::deque< PaletteColorChange > undo_stack_
Undo/redo stacks.
absl::Status ResetPalette(const std::string &group_name, int palette_index)
Reset an entire palette to original ROM values.
SnesColor GetOriginalColor(const std::string &group_name, int palette_index, int color_index) const
Helper: Get original color from snapshot.
bool IsColorModified(const std::string &group_name, int palette_index, int color_index) const
Check if a specific color is modified.
void MarkModified(const std::string &group_name, int palette_index, int color_index)
Helper: Mark a color as modified.
void DiscardGroup(const std::string &group_name)
Discard changes for a specific group.
bool IsInitialized() const
Check if manager is initialized.
zelda3::GameData * game_data_
GameData instance (not owned) - preferred.
void DiscardAllChanges()
Discard ALL unsaved changes.
size_t GetModifiedColorCount() const
Get count of modified colors across all groups.
std::unordered_map< std::string, std::vector< SnesPalette > > original_palettes_
std::vector< PaletteColorChange > batch_changes_
const PaletteGroup * GetGroup(const std::string &group_name) const
Helper: Get const palette group.
SnesColor GetColor(const std::string &group_name, int palette_index, int color_index) const
Get a color from a palette.
std::function< void(const PaletteChangeEvent &)> ChangeCallback
absl::Status SaveAllToRom()
Save ALL modified palettes to ROM.
bool IsPaletteModified(const std::string &group_name, int palette_index) const
Check if a specific palette is modified.
absl::Status ApplyPreviewChanges()
Apply preview changes to other editors without saving to ROM.
void Redo()
Redo the most recently undone change.
SNES Color container.
Definition snes_color.h:110
uint32_t GetPaletteAddress(const std::string &group_name, size_t palette_index, size_t color_index)
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
Event notification for palette changes.
@ kColorChanged
Single color was modified.
@ kPaletteReset
Entire palette was reset.
@ kAllDiscarded
All changes discarded.
@ kGroupDiscarded
Palette group changes were discarded.
@ kAllSaved
All changes saved to ROM.
@ kGroupSaved
Palette group was saved to ROM.
Represents a single color change operation.
Represents a mapping of palette groups.
PaletteGroup * get_group(const std::string &group_name)
Represents a group of palettes.
gfx::PaletteGroupMap palette_groups
Definition game_data.h:89
Rom * rom() const
Definition game_data.h:74