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