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_ = nullptr; // Clear legacy ROM pointer
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
70// ========== Color Operations ==========
71
72SnesColor PaletteManager::GetColor(const std::string& group_name,
73 int palette_index, int color_index) const {
74 const auto* group = GetGroup(group_name);
75 if (!group || palette_index < 0 || palette_index >= group->size()) {
76 return SnesColor();
77 }
78
79 const auto& palette = group->palette_ref(palette_index);
80 if (color_index < 0 || color_index >= palette.size()) {
81 return SnesColor();
82 }
83
84 return palette[color_index];
85}
86
87absl::Status PaletteManager::SetColor(const std::string& group_name,
88 int palette_index, int color_index,
89 const SnesColor& new_color) {
90 if (!IsInitialized()) {
91 return absl::FailedPreconditionError("PaletteManager not initialized");
92 }
93
94 auto* group = GetMutableGroup(group_name);
95 if (!group) {
96 return absl::NotFoundError(
97 absl::StrFormat("Palette group '%s' not found", group_name));
98 }
99
100 if (palette_index < 0 || palette_index >= group->size()) {
101 return absl::InvalidArgumentError(absl::StrFormat(
102 "Palette index %d out of range [0, %d)", palette_index, group->size()));
103 }
104
105 auto* palette = group->mutable_palette(palette_index);
106 if (color_index < 0 || color_index >= palette->size()) {
107 return absl::InvalidArgumentError(absl::StrFormat(
108 "Color index %d out of range [0, %d)", color_index, palette->size()));
109 }
110
111 // Get original color
112 SnesColor original_color = (*palette)[color_index];
113
114 // Update in-memory palette
115 (*palette)[color_index] = new_color;
116
117 // Track modification
118 MarkModified(group_name, palette_index, color_index);
119
120 // Record for undo (unless in batch mode - batch changes recorded separately)
121 if (!InBatch()) {
122 auto now = std::chrono::system_clock::now();
123 auto timestamp_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
124 now.time_since_epoch())
125 .count();
126
127 PaletteColorChange change{group_name, palette_index,
128 color_index, original_color,
129 new_color, static_cast<uint64_t>(timestamp_ms)};
130 RecordChange(change);
131
132 // Notify listeners
134 group_name, palette_index, color_index};
135 NotifyListeners(event);
136 } else {
137 // Store in batch buffer
138 auto now = std::chrono::system_clock::now();
139 auto timestamp_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
140 now.time_since_epoch())
141 .count();
142 batch_changes_.push_back({group_name, palette_index, color_index,
143 original_color, new_color,
144 static_cast<uint64_t>(timestamp_ms)});
145 }
146
147 return absl::OkStatus();
148}
149
150absl::Status PaletteManager::ResetColor(const std::string& group_name,
151 int palette_index, int color_index) {
152 SnesColor original = GetOriginalColor(group_name, palette_index, color_index);
153 return SetColor(group_name, palette_index, color_index, original);
154}
155
156absl::Status PaletteManager::ResetPalette(const std::string& group_name,
157 int palette_index) {
158 if (!IsInitialized()) {
159 return absl::FailedPreconditionError("PaletteManager not initialized");
160 }
161
162 // Check if original snapshot exists
163 auto it = original_palettes_.find(group_name);
164 if (it == original_palettes_.end() || palette_index >= it->second.size()) {
165 return absl::NotFoundError("Original palette not found");
166 }
167
168 auto* group = GetMutableGroup(group_name);
169 if (!group || palette_index >= group->size()) {
170 return absl::NotFoundError("Palette group or index not found");
171 }
172
173 // Restore from original
174 *group->mutable_palette(palette_index) = it->second[palette_index];
175
176 // Clear modified flags for this palette
177 modified_palettes_[group_name].erase(palette_index);
178 modified_colors_[group_name].erase(palette_index);
179
180 // Notify listeners
182 palette_index, -1};
183 NotifyListeners(event);
184
185 return absl::OkStatus();
186}
187
188// ========== Dirty Tracking ==========
189
191 return !modified_palettes_.empty();
192}
193
194std::vector<std::string> PaletteManager::GetModifiedGroups() const {
195 std::vector<std::string> groups;
196 for (const auto& [group_name, _] : modified_palettes_) {
197 groups.push_back(group_name);
198 }
199 return groups;
200}
201
202bool PaletteManager::IsGroupModified(const std::string& group_name) const {
203 auto it = modified_palettes_.find(group_name);
204 return it != modified_palettes_.end() && !it->second.empty();
205}
206
207bool PaletteManager::IsPaletteModified(const std::string& group_name,
208 int palette_index) const {
209 auto it = modified_palettes_.find(group_name);
210 if (it == modified_palettes_.end()) {
211 return false;
212 }
213 return it->second.contains(palette_index);
214}
215
216bool PaletteManager::IsColorModified(const std::string& group_name,
217 int palette_index, int color_index) const {
218 auto group_it = modified_colors_.find(group_name);
219 if (group_it == modified_colors_.end()) {
220 return false;
221 }
222
223 auto pal_it = group_it->second.find(palette_index);
224 if (pal_it == group_it->second.end()) {
225 return false;
226 }
227
228 return pal_it->second.contains(color_index);
229}
230
232 size_t count = 0;
233 for (const auto& [_, palette_map] : modified_colors_) {
234 for (const auto& [__, color_set] : palette_map) {
235 count += color_set.size();
236 }
237 }
238 return count;
239}
240
241// ========== Persistence ==========
242
243absl::Status PaletteManager::SaveGroup(const std::string& group_name) {
244 if (!IsInitialized()) {
245 return absl::FailedPreconditionError("PaletteManager not initialized");
246 }
247
248 auto* group = GetMutableGroup(group_name);
249 if (!group) {
250 return absl::NotFoundError(
251 absl::StrFormat("Palette group '%s' not found", group_name));
252 }
253
254 // Get modified palettes for this group
255 auto pal_it = modified_palettes_.find(group_name);
256 if (pal_it == modified_palettes_.end() || pal_it->second.empty()) {
257 // No changes to save
258 return absl::OkStatus();
259 }
260
261 // Write each modified palette
262 for (int palette_idx : pal_it->second) {
263 auto* palette = group->mutable_palette(palette_idx);
264
265 // Get modified colors for this palette
266 auto color_it = modified_colors_[group_name].find(palette_idx);
267 if (color_it != modified_colors_[group_name].end()) {
268 for (int color_idx : color_it->second) {
269 // Calculate ROM address using the helper function
270 uint32_t address =
271 GetPaletteAddress(group_name, palette_idx, color_idx);
272
273 // Write color to ROM - write the 16-bit SNES color value
274 rom_->WriteShort(address, (*palette)[color_idx].snes());
275 }
276 }
277 }
278
279 // Update original snapshots
280 auto& originals = original_palettes_[group_name];
281 for (size_t i = 0; i < group->size() && i < originals.size(); i++) {
282 originals[i] = group->palette(i);
283 }
284
285 // Clear modified flags for this group
286 ClearModifiedFlags(group_name);
287
288 // Mark ROM as dirty
289 rom_->set_dirty(true);
290
291 // Notify listeners
293 -1, -1};
294 NotifyListeners(event);
295
296 // Notify Arena for bitmap propagation to other editors
297 Arena::Get().NotifyPaletteModified(group_name, -1);
298
299 return absl::OkStatus();
300}
301
303 if (!IsInitialized()) {
304 return absl::FailedPreconditionError("PaletteManager not initialized");
305 }
306
307 // Save all modified groups
308 for (const auto& group_name : GetModifiedGroups()) {
309 RETURN_IF_ERROR(SaveGroup(group_name));
310 }
311
312 // Notify listeners
314 NotifyListeners(event);
315
316 return absl::OkStatus();
317}
318
320 if (!IsInitialized()) {
321 return absl::FailedPreconditionError("PaletteManager not initialized");
322 }
323
324 // Get all modified groups and notify Arena for each
325 // This triggers bitmap refresh in other editors WITHOUT saving to ROM
326 auto modified_groups = GetModifiedGroups();
327
328 if (modified_groups.empty()) {
329 return absl::OkStatus(); // Nothing to preview
330 }
331
332 for (const auto& group_name : modified_groups) {
333 Arena::Get().NotifyPaletteModified(group_name, -1);
334 }
335
336 // Notify listeners that preview was applied
338 NotifyListeners(event);
339
340 return absl::OkStatus();
341}
342
343void PaletteManager::DiscardGroup(const std::string& group_name) {
344 if (!IsInitialized()) {
345 return;
346 }
347
348 auto* group = GetMutableGroup(group_name);
349 if (!group) {
350 return;
351 }
352
353 // Get modified palettes
354 auto pal_it = modified_palettes_.find(group_name);
355 if (pal_it == modified_palettes_.end()) {
356 return;
357 }
358
359 // Restore from original snapshots
360 auto orig_it = original_palettes_.find(group_name);
361 if (orig_it != original_palettes_.end()) {
362 for (int palette_idx : pal_it->second) {
363 if (palette_idx < orig_it->second.size()) {
364 *group->mutable_palette(palette_idx) = orig_it->second[palette_idx];
365 }
366 }
367 }
368
369 // Clear modified flags
370 ClearModifiedFlags(group_name);
371
372 // Notify listeners
374 group_name, -1, -1};
375 NotifyListeners(event);
376}
377
379 if (!IsInitialized()) {
380 return;
381 }
382
383 // Discard all modified groups
384 for (const auto& group_name : GetModifiedGroups()) {
385 DiscardGroup(group_name);
386 }
387
388 // Clear undo/redo
389 ClearHistory();
390
391 // Notify listeners
393 NotifyListeners(event);
394}
395
396// ========== Undo/Redo ==========
397
399 if (!CanUndo()) {
400 return;
401 }
402
403 auto change = undo_stack_.back();
404 undo_stack_.pop_back();
405
406 // Restore original color
407 auto* group = GetMutableGroup(change.group_name);
408 if (group && change.palette_index < group->size()) {
409 auto* palette = group->mutable_palette(change.palette_index);
410 if (change.color_index < palette->size()) {
411 (*palette)[change.color_index] = change.original_color;
412 }
413 }
414
415 // Move to redo stack
416 redo_stack_.push_back(change);
417
418 // Notify listeners
420 change.group_name, change.palette_index,
421 change.color_index};
422 NotifyListeners(event);
423}
424
426 if (!CanRedo()) {
427 return;
428 }
429
430 auto change = redo_stack_.back();
431 redo_stack_.pop_back();
432
433 // Reapply new color
434 auto* group = GetMutableGroup(change.group_name);
435 if (group && change.palette_index < group->size()) {
436 auto* palette = group->mutable_palette(change.palette_index);
437 if (change.color_index < palette->size()) {
438 (*palette)[change.color_index] = change.new_color;
439 }
440 }
441
442 // Move back to undo stack
443 undo_stack_.push_back(change);
444
445 // Notify listeners
447 change.group_name, change.palette_index,
448 change.color_index};
449 NotifyListeners(event);
450}
451
453 undo_stack_.clear();
454 redo_stack_.clear();
455}
456
457// ========== Change Notifications ==========
458
460 int id = next_callback_id_++;
461 change_listeners_[id] = callback;
462 return id;
463}
464
466 change_listeners_.erase(callback_id);
467}
468
469// ========== Batch Operations ==========
470
472 batch_depth_++;
473 if (batch_depth_ == 1) {
474 batch_changes_.clear();
475 }
476}
477
479 if (batch_depth_ == 0) {
480 return;
481 }
482
483 batch_depth_--;
484
485 if (batch_depth_ == 0 && !batch_changes_.empty()) {
486 // Commit all batch changes as a single undo step
487 for (const auto& change : batch_changes_) {
488 RecordChange(change);
489
490 // Notify listeners for each change
492 change.group_name, change.palette_index,
493 change.color_index};
494 NotifyListeners(event);
495 }
496
497 batch_changes_.clear();
498 }
499}
500
501// ========== Private Helpers ==========
502
503PaletteGroup* PaletteManager::GetMutableGroup(const std::string& group_name) {
504 if (!IsInitialized()) {
505 return nullptr;
506 }
507 try {
508 if (game_data_) {
509 return game_data_->palette_groups.get_group(group_name);
510 }
511 return nullptr; // Legacy ROM-only mode not supported
512 } catch (const std::exception&) {
513 return nullptr;
514 }
515}
516
518 const std::string& group_name) const {
519 if (!IsInitialized()) {
520 return nullptr;
521 }
522 try {
523 if (game_data_) {
524 return const_cast<PaletteGroupMap*>(&game_data_->palette_groups)
525 ->get_group(group_name);
526 }
527 return nullptr; // Legacy ROM-only mode not supported
528 } catch (const std::exception&) {
529 return nullptr;
530 }
531}
532
533SnesColor PaletteManager::GetOriginalColor(const std::string& group_name,
534 int palette_index,
535 int color_index) const {
536 auto it = original_palettes_.find(group_name);
537 if (it == original_palettes_.end() || palette_index >= it->second.size()) {
538 return SnesColor();
539 }
540
541 const auto& palette = it->second[palette_index];
542 if (color_index >= palette.size()) {
543 return SnesColor();
544 }
545
546 return palette[color_index];
547}
548
550 undo_stack_.push_back(change);
551
552 // Limit history size
553 if (undo_stack_.size() > kMaxUndoHistory) {
554 undo_stack_.pop_front();
555 }
556
557 // Clear redo stack (can't redo after a new change)
558 redo_stack_.clear();
559}
560
562 for (const auto& [_, callback] : change_listeners_) {
563 callback(event);
564 }
565}
566
567void PaletteManager::MarkModified(const std::string& group_name,
568 int palette_index, int color_index) {
569 modified_palettes_[group_name].insert(palette_index);
570 modified_colors_[group_name][palette_index].insert(color_index);
571}
572
573void PaletteManager::ClearModifiedFlags(const std::string& group_name) {
574 modified_palettes_.erase(group_name);
575 modified_colors_.erase(group_name);
576}
577
578} // namespace gfx
579} // 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:316
static Arena & Get()
Definition arena.cc:19
void NotifyPaletteModified(const std::string &group_name, int palette_index=-1)
Notify all listeners that a palette has been modified.
Definition arena.cc:343
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.
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