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 <algorithm>
4#include <chrono>
5
6#include "absl/strings/str_format.h"
9#include "rom/rom.h"
10#include "util/macro.h"
11#include "zelda3/game_data.h"
12
13namespace yaze {
14namespace gfx {
15
17 if (!game_data_) {
18 return &unbound_state_;
19 }
20 return &session_states_.try_emplace(game_data_).first->second;
21}
22
24 if (!game_data_) {
25 return &unbound_state_;
26 }
27 auto it = session_states_.find(game_data_);
28 return it == session_states_.end() ? &unbound_state_ : &it->second;
29}
30
32 const zelda3::GameData* game_data) const {
33 if (!game_data) {
34 return nullptr;
35 }
36 auto it = session_states_.find(game_data);
37 return it == session_states_.end() ? nullptr : &it->second;
38}
39
41 if (!game_data_) {
42 return rom_ != nullptr;
43 }
44 const auto* state = FindState(game_data_);
45 return state != nullptr && state->initialized && rom_ == game_data_->rom();
46}
47
49 game_data_ = game_data;
50 rom_ = game_data ? game_data->rom() : nullptr;
51 if (game_data) {
52 session_states_.try_emplace(game_data);
53 }
54}
55
57 if (!game_data) {
58 return;
59 }
60
61 ActivateSession(game_data);
62 auto* state = CurrentState();
63 if (state->initialized) {
64 return;
65 }
66
67 *state = SessionState{};
68
69 // Load original palette snapshots for all groups
70 auto* palette_groups = &game_data_->palette_groups;
71
72 // Snapshot all palette groups
73 const char* group_names[] = {"ow_main", "ow_aux", "ow_animated",
74 "hud", "global_sprites", "armors",
75 "swords", "shields", "sprites_aux1",
76 "sprites_aux2", "sprites_aux3", "dungeon_main",
77 "grass", "3d_object", "ow_mini_map"};
78
79 for (const auto& group_name : group_names) {
80 try {
81 auto* group = palette_groups->get_group(group_name);
82 if (group) {
83 std::vector<SnesPalette> originals;
84 for (size_t i = 0; i < group->size(); i++) {
85 originals.push_back(group->palette(i));
86 }
87 state->original_palettes[group_name] = originals;
88 }
89 } catch (const std::exception& e) {
90 // Group doesn't exist, skip
91 continue;
92 }
93 }
94
95 state->initialized = true;
96}
97
99 return game_data != nullptr && game_data_ == game_data &&
100 rom_ == game_data->rom();
101}
102
103bool PaletteManager::IsManaging(const zelda3::GameData* game_data) const {
104 return IsSessionActive(game_data) && IsInitialized();
105}
106
108 if (!game_data) {
109 return;
110 }
111 if (game_data_ == game_data) {
112 game_data_ = nullptr;
113 rom_ = nullptr;
114 }
115 session_states_.erase(game_data);
116}
117
119 // Legacy initialization - not supported in new architecture
120 // Keep ROM pointer for backwards compatibility but log warning
121 if (!rom) {
122 return;
123 }
124 game_data_ = nullptr;
125 rom_ = rom;
127}
128
130 game_data_ = nullptr;
131 rom_ = nullptr;
132 session_states_.clear();
134 change_listeners_.clear();
136}
137
138// ========== Color Operations ==========
139
140SnesColor PaletteManager::GetColor(const std::string& group_name,
141 int palette_index, int color_index) const {
142 const auto* group = GetGroup(group_name);
143 if (!group || palette_index < 0 || palette_index >= group->size()) {
144 return SnesColor();
145 }
146
147 const auto& palette = group->palette_ref(palette_index);
148 if (color_index < 0 || color_index >= palette.size()) {
149 return SnesColor();
150 }
151
152 return palette[color_index];
153}
154
155absl::Status PaletteManager::SetColor(const std::string& group_name,
156 int palette_index, int color_index,
157 const SnesColor& new_color) {
158 if (!IsInitialized()) {
159 return absl::FailedPreconditionError("PaletteManager not initialized");
160 }
161
162 auto* group = GetMutableGroup(group_name);
163 if (!group) {
164 return absl::NotFoundError(
165 absl::StrFormat("Palette group '%s' not found", group_name));
166 }
167
168 if (palette_index < 0 || palette_index >= group->size()) {
169 return absl::InvalidArgumentError(absl::StrFormat(
170 "Palette index %d out of range [0, %d)", palette_index, group->size()));
171 }
172
173 auto* palette = group->mutable_palette(palette_index);
174 if (color_index < 0 || color_index >= palette->size()) {
175 return absl::InvalidArgumentError(absl::StrFormat(
176 "Color index %d out of range [0, %d)", color_index, palette->size()));
177 }
178
179 // Get original color
180 SnesColor original_color = (*palette)[color_index];
181
182 // Update in-memory palette
183 (*palette)[color_index] = new_color;
184
185 // Track modification
186 MarkModified(group_name, palette_index, color_index);
187
188 // Record for undo (unless in batch mode - batch changes recorded separately)
189 if (!InBatch()) {
190 auto now = std::chrono::system_clock::now();
191 auto timestamp_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
192 now.time_since_epoch())
193 .count();
194
195 PaletteColorChange change{group_name, palette_index,
196 color_index, original_color,
197 new_color, static_cast<uint64_t>(timestamp_ms)};
198 RecordChange(change);
199
200 // Notify listeners
202 group_name, palette_index, color_index};
203 NotifyListeners(event);
204 } else {
205 // Store in batch buffer
206 auto now = std::chrono::system_clock::now();
207 auto timestamp_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
208 now.time_since_epoch())
209 .count();
210 CurrentState()->batch_changes.push_back(
211 {group_name, palette_index, color_index, original_color, new_color,
212 static_cast<uint64_t>(timestamp_ms)});
213 }
214
215 return absl::OkStatus();
216}
217
218absl::Status PaletteManager::ResetColor(const std::string& group_name,
219 int palette_index, int color_index) {
220 SnesColor original = GetOriginalColor(group_name, palette_index, color_index);
221 return SetColor(group_name, palette_index, color_index, original);
222}
223
224absl::Status PaletteManager::ResetPalette(const std::string& group_name,
225 int palette_index) {
226 if (!IsInitialized()) {
227 return absl::FailedPreconditionError("PaletteManager not initialized");
228 }
229
230 // Check if original snapshot exists
231 auto* state = CurrentState();
232 auto it = state->original_palettes.find(group_name);
233 if (it == state->original_palettes.end() || palette_index < 0 ||
234 palette_index >= it->second.size()) {
235 return absl::NotFoundError("Original palette not found");
236 }
237
238 auto* group = GetMutableGroup(group_name);
239 if (!group || palette_index >= group->size()) {
240 return absl::NotFoundError("Palette group or index not found");
241 }
242
243 // Restore from original
244 *group->mutable_palette(palette_index) = it->second[palette_index];
245
246 // Clear modified flags for this palette
247 if (auto modified_it = state->modified_palettes.find(group_name);
248 modified_it != state->modified_palettes.end()) {
249 modified_it->second.erase(palette_index);
250 if (modified_it->second.empty()) {
251 state->modified_palettes.erase(modified_it);
252 }
253 }
254 if (auto colors_it = state->modified_colors.find(group_name);
255 colors_it != state->modified_colors.end()) {
256 colors_it->second.erase(palette_index);
257 if (colors_it->second.empty()) {
258 state->modified_colors.erase(colors_it);
259 }
260 }
261
262 // Notify listeners
264 palette_index, -1};
265 NotifyListeners(event);
266
267 return absl::OkStatus();
268}
269
270// ========== Dirty Tracking ==========
271
273 return !CurrentState()->modified_palettes.empty();
274}
275
277 const zelda3::GameData* game_data) const {
278 const auto* state = FindState(game_data);
279 return state != nullptr && !state->modified_palettes.empty();
280}
281
282std::vector<std::string> PaletteManager::GetModifiedGroups() const {
283 std::vector<std::string> groups;
284 for (const auto& [group_name, _] : CurrentState()->modified_palettes) {
285 groups.push_back(group_name);
286 }
287 return groups;
288}
289
290bool PaletteManager::IsGroupModified(const std::string& group_name) const {
291 auto it = CurrentState()->modified_palettes.find(group_name);
292 return it != CurrentState()->modified_palettes.end() && !it->second.empty();
293}
294
295bool PaletteManager::IsPaletteModified(const std::string& group_name,
296 int palette_index) const {
297 auto it = CurrentState()->modified_palettes.find(group_name);
298 if (it == CurrentState()->modified_palettes.end()) {
299 return false;
300 }
301 return it->second.contains(palette_index);
302}
303
304bool PaletteManager::IsColorModified(const std::string& group_name,
305 int palette_index, int color_index) const {
306 auto group_it = CurrentState()->modified_colors.find(group_name);
307 if (group_it == CurrentState()->modified_colors.end()) {
308 return false;
309 }
310
311 auto pal_it = group_it->second.find(palette_index);
312 if (pal_it == group_it->second.end()) {
313 return false;
314 }
315
316 return pal_it->second.contains(color_index);
317}
318
322
324 const zelda3::GameData* game_data) const {
325 const auto* state = FindState(game_data);
326 if (state == nullptr) {
327 return 0;
328 }
329
330 size_t count = 0;
331 for (const auto& [_, palette_map] : state->modified_colors) {
332 for (const auto& [__, color_set] : palette_map) {
333 count += color_set.size();
334 }
335 }
336 return count;
337}
338
339std::vector<std::pair<uint32_t, uint32_t>>
343
344std::vector<std::pair<uint32_t, uint32_t>>
346 const zelda3::GameData* game_data) const {
347 std::vector<std::pair<uint32_t, uint32_t>> ranges;
348 const auto* state = FindState(game_data);
349 if (state == nullptr) {
350 return ranges;
351 }
352 ranges.reserve(GetModifiedColorCount(game_data));
353
354 for (const auto& [group_name, palette_map] : state->modified_colors) {
355 for (const auto& [palette_index, color_indices] : palette_map) {
356 for (int color_index : color_indices) {
357 const uint32_t begin =
358 GetPaletteAddress(group_name, palette_index, color_index);
359 ranges.emplace_back(begin, begin + 2u);
360 }
361 }
362 }
363
364 std::sort(ranges.begin(), ranges.end());
365 std::vector<std::pair<uint32_t, uint32_t>> coalesced;
366 coalesced.reserve(ranges.size());
367 for (const auto& range : ranges) {
368 if (coalesced.empty() || coalesced.back().second < range.first) {
369 coalesced.push_back(range);
370 continue;
371 }
372 coalesced.back().second = std::max(coalesced.back().second, range.second);
373 }
374 return coalesced;
375}
376
377// ========== Persistence ==========
378
379absl::Status PaletteManager::SaveGroup(const std::string& group_name) {
380 if (!IsInitialized()) {
381 return absl::FailedPreconditionError("PaletteManager not initialized");
382 }
383
384 Rom* rom = rom_;
385 if (!rom && game_data_) {
386 rom = game_data_->rom();
387 }
388 if (!rom) {
389 return absl::FailedPreconditionError("No ROM available for palette save");
390 }
391
392 auto* group = GetMutableGroup(group_name);
393 if (!group) {
394 return absl::NotFoundError(
395 absl::StrFormat("Palette group '%s' not found", group_name));
396 }
397
398 // Get modified palettes for this group
399 auto pal_it = CurrentState()->modified_palettes.find(group_name);
400 if (pal_it == CurrentState()->modified_palettes.end() ||
401 pal_it->second.empty()) {
402 // No changes to save
403 return absl::OkStatus();
404 }
405
406 // Write each modified palette
407 for (int palette_idx : pal_it->second) {
408 auto* palette = group->mutable_palette(palette_idx);
409
410 // Get modified colors for this palette
411 auto color_it =
412 CurrentState()->modified_colors[group_name].find(palette_idx);
413 if (color_it != CurrentState()->modified_colors[group_name].end()) {
414 for (int color_idx : color_it->second) {
415 // Calculate ROM address using the helper function
416 uint32_t address =
417 GetPaletteAddress(group_name, palette_idx, color_idx);
418
419 // Write color to ROM - write the 16-bit SNES color value
420 RETURN_IF_ERROR(rom->WriteShort(address, (*palette)[color_idx].snes()));
421 }
422 }
423 }
424
425 // Update original snapshots
426 auto& originals = CurrentState()->original_palettes[group_name];
427 for (size_t i = 0; i < group->size() && i < originals.size(); i++) {
428 originals[i] = group->palette(i);
429 }
430
431 // Clear modified flags for this group
432 ClearModifiedFlags(group_name);
433
434 // Mark ROM as dirty
435 rom->set_dirty(true);
436
437 // Notify listeners
439 -1, -1};
440 NotifyListeners(event);
441
442 // Notify Arena for bitmap propagation to other editors
443 Arena::Get().NotifyPaletteModified(group_name, -1);
444
445 return absl::OkStatus();
446}
447
449 if (!IsInitialized()) {
450 return absl::FailedPreconditionError("PaletteManager not initialized");
451 }
452
453 // Save all modified groups
454 for (const auto& group_name : GetModifiedGroups()) {
455 RETURN_IF_ERROR(SaveGroup(group_name));
456 }
457
458 // Notify listeners
460 NotifyListeners(event);
461
462 return absl::OkStatus();
463}
464
466 if (!IsInitialized()) {
467 return absl::FailedPreconditionError("PaletteManager not initialized");
468 }
469
470 auto* state = CurrentState();
471 if (state->save_transaction_snapshot.has_value()) {
472 return absl::FailedPreconditionError(
473 "Palette save transaction is already active");
474 }
475 state->save_transaction_snapshot =
477 state->modified_palettes, state->modified_colors};
478 return absl::OkStatus();
479}
480
482 auto* state = CurrentState();
483 if (!state->save_transaction_snapshot.has_value()) {
484 return;
485 }
486 state->original_palettes =
487 std::move(state->save_transaction_snapshot->original_palettes);
488 state->modified_palettes =
489 std::move(state->save_transaction_snapshot->modified_palettes);
490 state->modified_colors =
491 std::move(state->save_transaction_snapshot->modified_colors);
492 state->save_transaction_snapshot.reset();
493}
494
498
500 if (!IsInitialized()) {
501 return absl::FailedPreconditionError("PaletteManager not initialized");
502 }
503
504 // Get all modified groups and notify Arena for each
505 // This triggers bitmap refresh in other editors WITHOUT saving to ROM
506 auto modified_groups = GetModifiedGroups();
507
508 if (modified_groups.empty()) {
509 return absl::OkStatus(); // Nothing to preview
510 }
511
512 for (const auto& group_name : modified_groups) {
513 Arena::Get().NotifyPaletteModified(group_name, -1);
514 }
515
516 // Notify listeners that preview was applied
518 NotifyListeners(event);
519
520 return absl::OkStatus();
521}
522
523void PaletteManager::DiscardGroup(const std::string& group_name) {
524 if (!IsInitialized()) {
525 return;
526 }
527
528 auto* group = GetMutableGroup(group_name);
529 if (!group) {
530 return;
531 }
532
533 // Get modified palettes
534 auto pal_it = CurrentState()->modified_palettes.find(group_name);
535 if (pal_it == CurrentState()->modified_palettes.end()) {
536 return;
537 }
538
539 // Restore from original snapshots
540 auto orig_it = CurrentState()->original_palettes.find(group_name);
541 if (orig_it != CurrentState()->original_palettes.end()) {
542 for (int palette_idx : pal_it->second) {
543 if (palette_idx < orig_it->second.size()) {
544 *group->mutable_palette(palette_idx) = orig_it->second[palette_idx];
545 }
546 }
547 }
548
549 // Clear modified flags
550 ClearModifiedFlags(group_name);
551
552 // Notify listeners
554 group_name, -1, -1};
555 NotifyListeners(event);
556}
557
559 if (!IsInitialized()) {
560 return;
561 }
562
563 // Discard all modified groups
564 for (const auto& group_name : GetModifiedGroups()) {
565 DiscardGroup(group_name);
566 }
567
568 // Clear undo/redo
569 ClearHistory();
570
571 // Notify listeners
573 NotifyListeners(event);
574}
575
576// ========== Undo/Redo ==========
577
579 if (!CanUndo()) {
580 return;
581 }
582
583 auto change = CurrentState()->undo_stack.back();
584 CurrentState()->undo_stack.pop_back();
585
586 // Restore original color
587 auto* group = GetMutableGroup(change.group_name);
588 if (group && change.palette_index < group->size()) {
589 auto* palette = group->mutable_palette(change.palette_index);
590 if (change.color_index < palette->size()) {
591 (*palette)[change.color_index] = change.original_color;
592 MarkModified(change.group_name, change.palette_index, change.color_index);
593 }
594 }
595
596 // Move to redo stack
597 CurrentState()->redo_stack.push_back(change);
598
599 // Notify listeners
601 change.group_name, change.palette_index,
602 change.color_index};
603 NotifyListeners(event);
604}
605
607 if (!CanRedo()) {
608 return;
609 }
610
611 auto change = CurrentState()->redo_stack.back();
612 CurrentState()->redo_stack.pop_back();
613
614 // Reapply new color
615 auto* group = GetMutableGroup(change.group_name);
616 if (group && change.palette_index < group->size()) {
617 auto* palette = group->mutable_palette(change.palette_index);
618 if (change.color_index < palette->size()) {
619 (*palette)[change.color_index] = change.new_color;
620 MarkModified(change.group_name, change.palette_index, change.color_index);
621 }
622 }
623
624 // Move back to undo stack
625 CurrentState()->undo_stack.push_back(change);
626
627 // Notify listeners
629 change.group_name, change.palette_index,
630 change.color_index};
631 NotifyListeners(event);
632}
633
635 CurrentState()->undo_stack.clear();
636 CurrentState()->redo_stack.clear();
637}
638
640 return !CurrentState()->undo_stack.empty();
641}
642
644 return !CurrentState()->redo_stack.empty();
645}
646
648 return CurrentState()->undo_stack.size();
649}
650
652 return CurrentState()->redo_stack.size();
653}
654
655// ========== Change Notifications ==========
656
658 int id = next_callback_id_++;
659 change_listeners_[id] = callback;
660 return id;
661}
662
664 change_listeners_.erase(callback_id);
665}
666
667// ========== Batch Operations ==========
668
671 if (CurrentState()->batch_depth == 1) {
672 CurrentState()->batch_changes.clear();
673 }
674}
675
677 if (CurrentState()->batch_depth == 0) {
678 return;
679 }
680
682
683 if (CurrentState()->batch_depth == 0 &&
684 !CurrentState()->batch_changes.empty()) {
685 // Commit all batch changes as a single undo step
686 for (const auto& change : CurrentState()->batch_changes) {
687 RecordChange(change);
688
689 // Notify listeners for each change
691 change.group_name, change.palette_index,
692 change.color_index};
693 NotifyListeners(event);
694 }
695
696 CurrentState()->batch_changes.clear();
697 }
698}
699
701 return CurrentState()->batch_depth > 0;
702}
703
704// ========== Private Helpers ==========
705
706PaletteGroup* PaletteManager::GetMutableGroup(const std::string& group_name) {
707 if (!IsInitialized()) {
708 return nullptr;
709 }
710 try {
711 if (game_data_) {
712 return game_data_->palette_groups.get_group(group_name);
713 }
714 return nullptr; // Legacy ROM-only mode not supported
715 } catch (const std::exception&) {
716 return nullptr;
717 }
718}
719
721 const std::string& group_name) const {
722 if (!IsInitialized()) {
723 return nullptr;
724 }
725 try {
726 if (game_data_) {
727 return const_cast<PaletteGroupMap*>(&game_data_->palette_groups)
728 ->get_group(group_name);
729 }
730 return nullptr; // Legacy ROM-only mode not supported
731 } catch (const std::exception&) {
732 return nullptr;
733 }
734}
735
736SnesColor PaletteManager::GetOriginalColor(const std::string& group_name,
737 int palette_index,
738 int color_index) const {
739 const auto* state = CurrentState();
740 auto it = state->original_palettes.find(group_name);
741 if (it == state->original_palettes.end() || palette_index < 0 ||
742 palette_index >= it->second.size()) {
743 return SnesColor();
744 }
745
746 const auto& palette = it->second[palette_index];
747 if (color_index < 0 || color_index >= palette.size()) {
748 return SnesColor();
749 }
750
751 return palette[color_index];
752}
753
755 CurrentState()->undo_stack.push_back(change);
756
757 // Limit history size
758 if (CurrentState()->undo_stack.size() > kMaxUndoHistory) {
759 CurrentState()->undo_stack.pop_front();
760 }
761
762 // Clear redo stack (can't redo after a new change)
763 CurrentState()->redo_stack.clear();
764}
765
767 for (const auto& [_, callback] : change_listeners_) {
768 callback(event);
769 }
770}
771
772void PaletteManager::MarkModified(const std::string& group_name,
773 int palette_index, int color_index) {
774 auto* state = CurrentState();
775 const auto* group = GetGroup(group_name);
776 const auto original_it = state->original_palettes.find(group_name);
777 const bool matches_original =
778 group != nullptr && palette_index >= 0 && palette_index < group->size() &&
779 color_index >= 0 &&
780 color_index < group->palette_ref(palette_index).size() &&
781 original_it != state->original_palettes.end() &&
782 palette_index < original_it->second.size() &&
783 color_index < original_it->second[palette_index].size() &&
784 group->palette_ref(palette_index)[color_index].snes() ==
785 original_it->second[palette_index][color_index].snes();
786
787 if (!matches_original) {
788 state->modified_palettes[group_name].insert(palette_index);
789 state->modified_colors[group_name][palette_index].insert(color_index);
790 return;
791 }
792
793 if (auto group_it = state->modified_colors.find(group_name);
794 group_it != state->modified_colors.end()) {
795 if (auto palette_it = group_it->second.find(palette_index);
796 palette_it != group_it->second.end()) {
797 palette_it->second.erase(color_index);
798 if (palette_it->second.empty()) {
799 group_it->second.erase(palette_it);
800 }
801 }
802 if (group_it->second.empty()) {
803 state->modified_colors.erase(group_it);
804 }
805 }
806
807 const auto colors_group_it = state->modified_colors.find(group_name);
808 const bool palette_still_modified =
809 colors_group_it != state->modified_colors.end() &&
810 colors_group_it->second.contains(palette_index);
811 if (!palette_still_modified) {
812 if (auto group_it = state->modified_palettes.find(group_name);
813 group_it != state->modified_palettes.end()) {
814 group_it->second.erase(palette_index);
815 if (group_it->second.empty()) {
816 state->modified_palettes.erase(group_it);
817 }
818 }
819 }
820}
821
822void PaletteManager::ClearModifiedFlags(const std::string& group_name) {
823 CurrentState()->modified_palettes.erase(group_name);
824 CurrentState()->modified_colors.erase(group_name);
825}
826
827} // namespace gfx
828} // 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:28
void set_dirty(bool dirty)
Definition rom.h:146
absl::Status WriteShort(int addr, uint16_t value)
Definition rom.cc:628
static Arena & Get()
Definition arena.cc:21
void NotifyPaletteModified(const std::string &group_name, int palette_index=-1)
Notify all listeners that a palette has been modified.
Definition arena.cc:423
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.
std::vector< std::pair< uint32_t, uint32_t > > GetModifiedColorWriteRanges() const
Get exact, coalesced half-open ROM ranges for modified colors.
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 ActivateSession(zelda3::GameData *game_data)
Select the palette state for a ROM session without resnapshotting it.
size_t GetUndoStackSize() const
Get size of undo stack.
void Undo()
Undo the most recent change.
size_t GetRedoStackSize() const
Get size of redo stack.
void ClearHistory()
Clear undo/redo history.
std::unordered_map< int, ChangeCallback > change_listeners_
Change listeners.
void UnregisterChangeListener(int callback_id)
Unregister a change listener.
Rom * rom_
ROM instance (not owned) - legacy, used when game_data_ is null.
bool IsManaging(const zelda3::GameData *game_data) const
Check whether the manager is bound to this exact GameData and ROM.
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.
const SessionState * FindState(const zelda3::GameData *game_data) const
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::unordered_map< const zelda3::GameData *, SessionState > session_states_
Per-GameData state. RomSession owns the keys and releases them on teardown.
SessionState unbound_state_
State used only before a GameData session is selected (legacy/tests).
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.
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
SessionState * CurrentState()
absl::Status SaveAllToRom()
Save ALL modified palettes to ROM.
absl::Status BeginSaveTransaction()
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.
bool IsSessionActive(const zelda3::GameData *game_data) const
Check whether this exact GameData is the active palette session.
void ReleaseSession(const zelda3::GameData *game_data)
Forget all palette tracking for a closing or reloaded ROM session.
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.
std::unordered_map< std::string, std::vector< SnesPalette > > original_palettes
std::vector< PaletteColorChange > batch_changes
std::unordered_map< std::string, std::unordered_map< int, std::unordered_set< int > > > modified_colors
std::unordered_map< std::string, std::vector< SnesPalette > > original_palettes
std::optional< SaveTransactionSnapshot > save_transaction_snapshot
std::deque< PaletteColorChange > redo_stack
std::unordered_map< std::string, std::unordered_set< int > > modified_palettes
std::deque< PaletteColorChange > undo_stack
gfx::PaletteGroupMap palette_groups
Definition game_data.h:92
Rom * rom() const
Definition game_data.h:76