10#include "absl/strings/str_format.h"
24 std::vector<std::pair<uint32_t, uint32_t>> ranges) {
25 std::sort(ranges.begin(), ranges.end());
26 std::vector<std::pair<uint32_t, uint32_t>> coalesced;
27 coalesced.reserve(ranges.size());
28 for (
const auto& range : ranges) {
29 if (coalesced.empty() || coalesced.back().second < range.first) {
30 coalesced.push_back(range);
33 coalesced.back().second = std::max(coalesced.back().second, range.second);
66 return rom_ !=
nullptr;
75 return state ? state->color_revision : 0;
80 rom_ = game_data ? game_data->
rom() :
nullptr;
93 if (state->initialized) {
103 const char* group_names[] = {
"ow_main",
"ow_aux",
"ow_animated",
104 "hud",
"global_sprites",
"armors",
105 "swords",
"shields",
"sprites_aux1",
106 "sprites_aux2",
"sprites_aux3",
"dungeon_main",
107 "grass",
"3d_object",
"ow_mini_map"};
109 for (
const auto& group_name : group_names) {
111 auto* group = palette_groups->
get_group(group_name);
113 std::vector<SnesPalette> originals;
114 for (
size_t i = 0; i < group->size(); i++) {
115 originals.push_back(group->palette(i));
117 state->original_palettes[group_name] = originals;
119 }
catch (
const std::exception& e) {
125 state->initialized =
true;
129 return game_data !=
nullptr &&
game_data_ == game_data &&
171 int palette_index,
int color_index)
const {
172 const auto* group =
GetGroup(group_name);
173 if (!group || palette_index < 0 || palette_index >= group->size()) {
177 const auto& palette = group->palette_ref(palette_index);
178 if (color_index < 0 || color_index >= palette.size()) {
182 return palette[color_index];
186 int palette_index,
int color_index,
189 return absl::FailedPreconditionError(
"PaletteManager not initialized");
194 return absl::NotFoundError(
195 absl::StrFormat(
"Palette group '%s' not found", group_name));
198 if (palette_index < 0 || palette_index >= group->size()) {
199 return absl::InvalidArgumentError(absl::StrFormat(
200 "Palette index %d out of range [0, %d)", palette_index, group->size()));
203 auto* palette = group->mutable_palette(palette_index);
204 if (color_index < 0 || color_index >= palette->size()) {
205 return absl::InvalidArgumentError(absl::StrFormat(
206 "Color index %d out of range [0, %d)", color_index, palette->size()));
210 if (state->color_edit && (state->color_edit->group_name != group_name ||
211 state->color_edit->palette_index != palette_index ||
212 state->color_edit->color_index != color_index)) {
215 const SnesColor original_color = (*palette)[color_index];
216 if (original_color.
snes() == new_color.
snes()) {
217 return absl::OkStatus();
219 (*palette)[color_index] = new_color;
222 const auto timestamp_ms =
223 std::chrono::duration_cast<std::chrono::milliseconds>(
224 std::chrono::system_clock::now().time_since_epoch())
227 color_index, original_color,
228 new_color,
static_cast<uint64_t
>(timestamp_ms)};
229 if (state->color_edit) {
230 state->color_edit->new_color = new_color;
231 state->color_edit->timestamp_ms = change.timestamp_ms;
233 state->batch_changes.push_back(change);
234 return absl::OkStatus();
239 palette_index, color_index});
241 return absl::OkStatus();
246 const std::vector<PaletteColorChange>& changes,
247 const std::function<absl::Status()>& participant) {
249 return absl::FailedPreconditionError(
250 "Palette command session is unavailable");
253 if (state->color_edit || state->batch_depth ||
254 state->save_transaction_snapshot) {
255 return absl::FailedPreconditionError(
256 "Finish the active palette operation first");
258 std::set<std::tuple<std::string, int, int>> addresses;
259 std::vector<SnesColor*> slots;
260 for (
const auto& change : changes) {
262 if (!group || change.palette_index < 0 ||
263 change.palette_index >= group->size()) {
264 return absl::InvalidArgumentError(
"Invalid command palette");
266 auto* palette = group->mutable_palette(change.palette_index);
267 if (change.color_index < 0 || change.color_index >= palette->size() ||
269 .emplace(change.group_name, change.palette_index,
272 return absl::InvalidArgumentError(
"Invalid or duplicate command color");
274 auto* slot = &(*palette)[change.color_index];
275 if (slot->snes() != change.original_color.snes()) {
276 return absl::FailedPreconditionError(
277 "Palette changed since command planning");
279 slots.push_back(slot);
281 const auto before = *state;
282 for (
size_t i = 0; i < changes.size(); ++i) {
283 *slots[i] = changes[i].new_color;
284 MarkModified(changes[i].group_name, changes[i].palette_index,
285 changes[i].color_index);
287 const auto status = participant();
289 for (
size_t i = 0; i < changes.size(); ++i)
290 *slots[i] = changes[i].original_color;
294 for (
const auto& change : changes) {
298 change.palette_index, change.color_index});
300 return absl::OkStatus();
304 int palette_index,
int color_index) {
306 return SetColor(group_name, palette_index, color_index, original);
313 return absl::FailedPreconditionError(
"PaletteManager not initialized");
318 auto it = state->original_palettes.find(group_name);
319 if (it == state->original_palettes.end() || palette_index < 0 ||
320 palette_index >= it->second.size()) {
321 return absl::NotFoundError(
"Original palette not found");
325 if (!group || palette_index >= group->size()) {
326 return absl::NotFoundError(
"Palette group or index not found");
330 *group->mutable_palette(palette_index) = it->second[palette_index];
333 if (
auto modified_it = state->modified_palettes.find(group_name);
334 modified_it != state->modified_palettes.end()) {
335 modified_it->second.erase(palette_index);
336 if (modified_it->second.empty()) {
337 state->modified_palettes.erase(modified_it);
340 if (
auto colors_it = state->modified_colors.find(group_name);
341 colors_it != state->modified_colors.end()) {
342 colors_it->second.erase(palette_index);
343 if (colors_it->second.empty()) {
344 state->modified_colors.erase(colors_it);
353 return absl::OkStatus();
357 int palette_index,
int color_index,
358 uint64_t caller_id) {
360 return absl::FailedPreconditionError(
361 "Color interaction requires an initialized session outside a batch");
363 const auto* group =
GetGroup(group_name);
364 if (!group || palette_index < 0 || palette_index >= group->size() ||
366 color_index >= group->palette_ref(palette_index).size()) {
367 return absl::InvalidArgumentError(
"Invalid color interaction target");
370 const auto color = group->palette_ref(palette_index)[color_index];
373 group_name, palette_index, color_index, color, color, 0};
374 return absl::OkStatus();
382 auto change = std::move(*state->color_edit);
383 state->color_edit.reset();
384 if (change.original_color.snes() != change.new_color.snes()) {
394 const auto change = std::move(*state->color_edit);
395 state->color_edit.reset();
397 if (group && change.palette_index < group->size()) {
398 auto* palette = group->mutable_palette(change.palette_index);
399 if (change.color_index < palette->size() &&
400 (*palette)[change.color_index].snes() != change.original_color.snes()) {
401 (*palette)[change.color_index] = change.original_color;
402 MarkModified(change.group_name, change.palette_index, change.color_index);
404 change.group_name, change.palette_index,
405 change.color_index});
412 return state->color_edit.has_value() &&
413 (caller_id == 0 || state->color_edit_caller == caller_id);
424 const auto* state =
FindState(game_data);
425 return state !=
nullptr && !state->modified_palettes.empty();
429 std::vector<std::string> groups;
431 groups.push_back(group_name);
442 int palette_index)
const {
447 return it->second.contains(palette_index);
451 int palette_index,
int color_index)
const {
453 if (group_it ==
CurrentState()->modified_colors.end()) {
457 auto pal_it = group_it->second.find(palette_index);
458 if (pal_it == group_it->second.end()) {
462 return pal_it->second.contains(color_index);
471 const auto* state =
FindState(game_data);
472 if (state ==
nullptr) {
477 for (
const auto& [_, palette_map] : state->modified_colors) {
478 for (
const auto& [__, color_set] : palette_map) {
479 count += color_set.size();
487 int palette_index,
int color_index)
const {
488 if (!game_data || !game_data->
rom()) {
489 return absl::FailedPreconditionError(
"No ROM session for palette address");
494 }
catch (
const std::out_of_range& e) {
495 return absl::InvalidArgumentError(e.what());
497 if (!group || palette_index < 0 || palette_index >= group->
size()) {
498 return absl::OutOfRangeError(
"Modified palette no longer exists");
500 if (color_index < 0 ||
502 return absl::OutOfRangeError(
"Modified palette color no longer exists");
505 const size_t rom_size = game_data->
rom()->
vector().size();
506 if (group_name ==
"ow_main" || group_name ==
"ow_aux") {
508 const auto kind = group_name ==
"ow_main"
509 ? OverworldPaletteKind::kMain
510 : OverworldPaletteKind::kAuxiliary;
514 kind, palette_index, rom_size));
515 return record.ColorAddress(color_index);
522 }
catch (
const std::out_of_range& e) {
523 return absl::InvalidArgumentError(e.what());
525 if (address > rom_size || rom_size - address < 2) {
526 return absl::OutOfRangeError(
"Modified palette color exceeds ROM bounds");
531absl::StatusOr<std::vector<std::pair<uint32_t, uint32_t>>>
536absl::StatusOr<std::vector<std::pair<uint32_t, uint32_t>>>
539 std::vector<std::pair<uint32_t, uint32_t>> ranges;
540 const auto* state =
FindState(game_data);
541 if (state ==
nullptr) {
546 for (
const auto& [group_name, palette_map] : state->modified_colors) {
547 for (
const auto& [palette_index, color_indices] : palette_map) {
548 for (
int color_index : color_indices) {
551 palette_index, color_index));
552 ranges.emplace_back(begin, begin + 2u);
557 return CoalescePaletteWriteRanges(std::move(ranges));
560absl::StatusOr<std::vector<std::pair<uint32_t, uint32_t>>>
562 const std::string& group_name)
const {
563 std::vector<std::pair<uint32_t, uint32_t>> ranges;
565 const auto group_it = state->modified_colors.find(group_name);
566 if (group_it == state->modified_colors.end()) {
570 for (
const auto& [palette_index, color_indices] : group_it->second) {
571 for (
int color_index : color_indices) {
574 palette_index, color_index));
575 ranges.emplace_back(begin, begin + 2u);
578 return CoalescePaletteWriteRanges(std::move(ranges));
585 return absl::FailedPreconditionError(
"PaletteManager not initialized");
593 return absl::FailedPreconditionError(
"No ROM available for palette save");
598 return absl::NotFoundError(
599 absl::StrFormat(
"Palette group '%s' not found", group_name));
604 if (pal_it ==
CurrentState()->modified_palettes.end() ||
605 pal_it->second.empty()) {
608 return absl::OkStatus();
612 std::vector<std::pair<uint32_t, uint16_t>> writes;
613 for (
int palette_idx : pal_it->second) {
615 if (group_it ==
CurrentState()->modified_colors.end()) {
618 const auto color_it = group_it->second.find(palette_idx);
619 if (color_it != group_it->second.end()) {
620 for (
int color_idx : color_it->second) {
623 palette_idx, color_idx));
624 writes.emplace_back(address,
625 group->palette_ref(palette_idx)[color_idx].snes());
630 for (
const auto& [address, color] : writes) {
636 for (
size_t i = 0; i < group->size() && i < originals.size(); i++) {
637 originals[i] = group->palette(i);
654 return absl::OkStatus();
659 return absl::FailedPreconditionError(
"PaletteManager not initialized");
675 return absl::OkStatus();
681 return absl::FailedPreconditionError(
"PaletteManager not initialized");
685 if (state->save_transaction_snapshot.has_value()) {
686 return absl::FailedPreconditionError(
687 "Palette save transaction is already active");
689 state->save_transaction_snapshot =
691 state->modified_palettes, state->modified_colors};
692 return absl::OkStatus();
697 if (!state->save_transaction_snapshot.has_value()) {
700 state->original_palettes =
701 std::move(state->save_transaction_snapshot->original_palettes);
702 state->modified_palettes =
703 std::move(state->save_transaction_snapshot->modified_palettes);
704 state->modified_colors =
705 std::move(state->save_transaction_snapshot->modified_colors);
706 state->save_transaction_snapshot.reset();
715 return absl::FailedPreconditionError(
"PaletteManager not initialized");
722 if (modified_groups.empty()) {
723 return absl::OkStatus();
727 for (
const auto& group_name : modified_groups) {
735 return absl::OkStatus();
753 if (pal_it ==
CurrentState()->modified_palettes.end()) {
759 if (orig_it !=
CurrentState()->original_palettes.end()) {
760 for (
int palette_idx : pal_it->second) {
761 if (palette_idx < orig_it->second.size()) {
762 *group->mutable_palette(palette_idx) = orig_it->second[palette_idx];
803 auto changes = std::move(state->undo_stack.back());
804 state->undo_stack.pop_back();
808 for (
auto it = changes.rbegin(); it != changes.rend(); ++it) {
809 const auto& change = *it;
811 if (group && change.palette_index >= 0 &&
812 change.palette_index < group->size()) {
813 auto* palette = group->mutable_palette(change.palette_index);
814 if (change.color_index >= 0 && change.color_index < palette->size()) {
815 (*palette)[change.color_index] = change.original_color;
822 state->redo_stack.push_back(changes);
824 const auto* owner_rom =
rom_;
825 for (
const auto& change : changes) {
830 change.palette_index, change.color_index});
841 auto changes = std::move(state->redo_stack.back());
842 state->redo_stack.pop_back();
844 for (
const auto& change : changes) {
846 if (group && change.palette_index >= 0 &&
847 change.palette_index < group->size()) {
848 auto* palette = group->mutable_palette(change.palette_index);
849 if (change.color_index >= 0 && change.color_index < palette->size()) {
850 (*palette)[change.color_index] = change.new_color;
857 state->undo_stack.push_back(changes);
859 const auto* owner_rom =
rom_;
860 for (
const auto& change : changes) {
865 change.palette_index, change.color_index});
877 if (state->batch_depth || state->save_transaction_snapshot)
879 if (state->color_edit && state->color_edit->original_color.snes() !=
880 state->color_edit->new_color.snes()) {
883 return !state->undo_stack.empty() &&
889 return !state->redo_stack.empty() && !state->batch_depth &&
890 !state->save_transaction_snapshot &&
891 (!state->color_edit || state->color_edit->original_color.snes() ==
892 state->color_edit->new_color.snes()) &&
897 const std::vector<PaletteColorChange>& changes,
bool redo)
const {
900 std::map<std::tuple<std::string, int, int>, uint16_t> staged;
901 for (
size_t step = 0; step < changes.size(); ++step) {
902 const auto& change = changes[redo ? step : changes.size() - 1 - step];
903 const auto* group =
GetGroup(change.group_name);
904 if (!group || change.palette_index < 0 ||
905 change.palette_index >= group->size())
907 const auto& palette = group->palette_ref(change.palette_index);
908 if (change.color_index < 0 || change.color_index >= palette.size())
910 const auto [at, inserted] = staged.try_emplace(
911 std::make_tuple(change.group_name, change.palette_index,
913 palette[change.color_index].snes());
914 const auto expected = redo ? change.original_color : change.new_color;
915 if (at->second != expected.snes())
917 at->second = (redo ? change.new_color : change.original_color).snes();
960 if (state->batch_depth == 0 && !state->batch_changes.empty()) {
963 std::vector<PaletteColorChange> changes;
964 for (
const auto& change : state->batch_changes) {
965 auto existing = std::find_if(
967 return entry.group_name == change.group_name &&
968 entry.palette_index == change.palette_index &&
969 entry.color_index == change.color_index;
971 if (existing == changes.end()) {
972 changes.push_back(change);
974 existing->new_color = change.new_color;
975 existing->timestamp_ms = change.timestamp_ms;
978 changes.erase(std::remove_if(changes.begin(), changes.end(),
980 return change.original_color.snes() ==
981 change.new_color.snes();
984 state->batch_changes.clear();
985 if (!changes.empty()) {
988 const auto* owner_rom =
rom_;
989 for (
const auto& change : changes) {
1016 }
catch (
const std::exception&) {
1022 const std::string& group_name)
const {
1029 ->get_group(group_name);
1032 }
catch (
const std::exception&) {
1039 int color_index)
const {
1041 auto it = state->original_palettes.find(group_name);
1042 if (it == state->original_palettes.end() || palette_index < 0 ||
1043 palette_index >= it->second.size()) {
1047 const auto& palette = it->second[palette_index];
1048 if (color_index < 0 || color_index >= palette.size()) {
1052 return palette[color_index];
1060 if (changes.empty()) {
1084 const auto* owner_rom =
rom_;
1094 int palette_index,
int color_index) {
1096 const auto* group =
GetGroup(group_name);
1097 const auto original_it = state->original_palettes.find(group_name);
1098 const bool matches_original =
1099 group !=
nullptr && palette_index >= 0 && palette_index < group->size() &&
1101 color_index < group->palette_ref(palette_index).size() &&
1102 original_it != state->original_palettes.end() &&
1103 palette_index < original_it->second.size() &&
1104 color_index < original_it->second[palette_index].size() &&
1105 group->palette_ref(palette_index)[color_index].snes() ==
1106 original_it->second[palette_index][color_index].snes();
1108 if (!matches_original) {
1109 state->modified_palettes[group_name].insert(palette_index);
1110 state->modified_colors[group_name][palette_index].insert(color_index);
1114 if (
auto group_it = state->modified_colors.find(group_name);
1115 group_it != state->modified_colors.end()) {
1116 if (
auto palette_it = group_it->second.find(palette_index);
1117 palette_it != group_it->second.end()) {
1118 palette_it->second.erase(color_index);
1119 if (palette_it->second.empty()) {
1120 group_it->second.erase(palette_it);
1123 if (group_it->second.empty()) {
1124 state->modified_colors.erase(group_it);
1128 const auto colors_group_it = state->modified_colors.find(group_name);
1129 const bool palette_still_modified =
1130 colors_group_it != state->modified_colors.end() &&
1131 colors_group_it->second.contains(palette_index);
1132 if (!palette_still_modified) {
1133 if (
auto group_it = state->modified_palettes.find(group_name);
1134 group_it != state->modified_palettes.end()) {
1135 group_it->second.erase(palette_index);
1136 if (group_it->second.empty()) {
1137 state->modified_palettes.erase(group_it);
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
void set_dirty(bool dirty)
const auto & vector() const
absl::Status WriteShort(int addr, uint16_t value)
void NotifyPaletteModified(const std::string &group_name, int palette_index=-1)
Notify all listeners that a palette has been modified.
void RecordChange(const PaletteColorChange &change)
Helper: Record a single-color undo step.
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)
bool CanReplayHistory(const std::vector< PaletteColorChange > &changes, bool redo) const
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.
uint64_t ColorRevision(const zelda3::GameData *game_data) const
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)
void RollbackSaveTransaction()
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.
absl::Status BeginColorEdit(const std::string &group_name, int palette_index, int color_index, uint64_t caller_id=0)
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.
void CancelColorEdit(uint64_t caller_id=0)
bool CanRedo() const
Check if the complete redo entry matches current colors. Conflicting history is retained,...
bool InBatch() const
Check if currently in a batch operation.
bool CanUndo() const
Check if the complete undo entry matches current colors. Conflicting history is retained,...
absl::StatusOr< std::vector< std::pair< uint32_t, uint32_t > > > GetModifiedColorWriteRanges() const
Get exact, coalesced half-open ROM ranges for modified colors. Invalid or unbound addresses return an...
void CommitSaveTransaction()
void Initialize(zelda3::GameData *game_data)
Initialize the palette manager with GameData.
void ResetForTesting()
Reset all state for test isolation.
absl::StatusOr< 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. Resolution errors leave the pending...
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.
absl::Status ApplyExternalChanges(const zelda3::GameData *owner, const std::vector< PaletteColorChange > &changes, const std::function< absl::Status()> &participant)
void EndBatch()
End a batch operation.
int RegisterChangeListener(ChangeCallback callback)
Register a callback for palette change events.
void RecordChanges(std::vector< PaletteColorChange > changes)
Helper: Record one undo step (a gesture or an entire batch).
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
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.
absl::StatusOr< uint32_t > ResolveColorAddress(const zelda3::GameData *game_data, const std::string &group_name, int palette_index, int color_index) const
bool IsInitialized() const
Check if manager is initialized.
void CommitColorEdit(uint64_t caller_id=0)
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.
bool HasColorEdit(uint64_t caller_id=0) const
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.
constexpr uint16_t snes() const
Get SNES 15-bit color.
static OverworldPaletteLayout Legacy()
#define ASSIGN_OR_RETURN(type_variable_name, expression)
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)
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.
int color_index
Index of color within palette.
std::string group_name
Palette group name (e.g., "ow_main")
int palette_index
Index of palette within group.
Represents a mapping of palette groups.
PaletteGroup * get_group(const std::string &group_name)
Represents a group of palettes.
const SnesPalette & palette_ref(int i) const
std::unordered_map< std::string, std::vector< SnesPalette > > original_palettes
std::vector< PaletteColorChange > batch_changes
std::deque< std::vector< PaletteColorChange > > redo_stack
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::optional< PaletteColorChange > color_edit
std::deque< std::vector< PaletteColorChange > > undo_stack
std::unordered_map< std::string, std::unordered_set< int > > modified_palettes
uint64_t color_edit_caller
gfx::PaletteGroupMap palette_groups