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"
7#include "util/macro.h"
8
9namespace yaze {
10namespace gfx {
11
13 if (!rom) {
14 return;
15 }
16
17 rom_ = rom;
18
19 // Load original palette snapshots for all groups
20 auto* palette_groups = rom_->mutable_palette_group();
21
22 // Snapshot all palette groups
23 const char* group_names[] = {
24 "ow_main", "ow_aux", "ow_animated", "hud", "global_sprites",
25 "armors", "swords", "shields", "sprites_aux1", "sprites_aux2",
26 "sprites_aux3", "dungeon_main", "grass", "3d_object", "ow_mini_map"
27 };
28
29 for (const auto& group_name : group_names) {
30 try {
31 auto* group = palette_groups->get_group(group_name);
32 if (group) {
33 std::vector<SnesPalette> originals;
34 for (size_t i = 0; i < group->size(); i++) {
35 originals.push_back(group->palette(i));
36 }
37 original_palettes_[group_name] = originals;
38 }
39 } catch (const std::exception& e) {
40 // Group doesn't exist, skip
41 continue;
42 }
43 }
44
45 // Clear any existing state
46 modified_palettes_.clear();
47 modified_colors_.clear();
49}
50
51// ========== Color Operations ==========
52
53SnesColor PaletteManager::GetColor(const std::string& group_name,
54 int palette_index,
55 int color_index) const {
56 const auto* group = GetGroup(group_name);
57 if (!group || palette_index < 0 || palette_index >= group->size()) {
58 return SnesColor();
59 }
60
61 const auto& palette = group->palette_ref(palette_index);
62 if (color_index < 0 || color_index >= palette.size()) {
63 return SnesColor();
64 }
65
66 return palette[color_index];
67}
68
69absl::Status PaletteManager::SetColor(const std::string& group_name,
70 int palette_index, int color_index,
71 const SnesColor& new_color) {
72 if (!IsInitialized()) {
73 return absl::FailedPreconditionError("PaletteManager not initialized");
74 }
75
76 auto* group = GetMutableGroup(group_name);
77 if (!group) {
78 return absl::NotFoundError(
79 absl::StrFormat("Palette group '%s' not found", group_name));
80 }
81
82 if (palette_index < 0 || palette_index >= group->size()) {
83 return absl::InvalidArgumentError(
84 absl::StrFormat("Palette index %d out of range [0, %d)", palette_index,
85 group->size()));
86 }
87
88 auto* palette = group->mutable_palette(palette_index);
89 if (color_index < 0 || color_index >= palette->size()) {
90 return absl::InvalidArgumentError(
91 absl::StrFormat("Color index %d out of range [0, %d)", color_index,
92 palette->size()));
93 }
94
95 // Get original color
96 SnesColor original_color = (*palette)[color_index];
97
98 // Update in-memory palette
99 (*palette)[color_index] = new_color;
100
101 // Track modification
102 MarkModified(group_name, palette_index, color_index);
103
104 // Record for undo (unless in batch mode - batch changes recorded separately)
105 if (!InBatch()) {
106 auto now = std::chrono::system_clock::now();
107 auto timestamp_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
108 now.time_since_epoch())
109 .count();
110
111 PaletteColorChange change{group_name, palette_index, color_index,
112 original_color, new_color,
113 static_cast<uint64_t>(timestamp_ms)};
114 RecordChange(change);
115
116 // Notify listeners
118 group_name, palette_index, color_index};
119 NotifyListeners(event);
120 } else {
121 // Store in batch buffer
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 batch_changes_.push_back(
127 {group_name, palette_index, color_index, original_color, new_color,
128 static_cast<uint64_t>(timestamp_ms)});
129 }
130
131 return absl::OkStatus();
132}
133
134absl::Status PaletteManager::ResetColor(const std::string& group_name,
135 int palette_index, int color_index) {
136 SnesColor original = GetOriginalColor(group_name, palette_index, color_index);
137 return SetColor(group_name, palette_index, color_index, original);
138}
139
140absl::Status PaletteManager::ResetPalette(const std::string& group_name,
141 int palette_index) {
142 if (!IsInitialized()) {
143 return absl::FailedPreconditionError("PaletteManager not initialized");
144 }
145
146 // Check if original snapshot exists
147 auto it = original_palettes_.find(group_name);
148 if (it == original_palettes_.end() ||
149 palette_index >= it->second.size()) {
150 return absl::NotFoundError("Original palette not found");
151 }
152
153 auto* group = GetMutableGroup(group_name);
154 if (!group || palette_index >= group->size()) {
155 return absl::NotFoundError("Palette group or index not found");
156 }
157
158 // Restore from original
159 *group->mutable_palette(palette_index) = it->second[palette_index];
160
161 // Clear modified flags for this palette
162 modified_palettes_[group_name].erase(palette_index);
163 modified_colors_[group_name].erase(palette_index);
164
165 // Notify listeners
167 group_name, palette_index, -1};
168 NotifyListeners(event);
169
170 return absl::OkStatus();
171}
172
173// ========== Dirty Tracking ==========
174
176 return !modified_palettes_.empty();
177}
178
179std::vector<std::string> PaletteManager::GetModifiedGroups() const {
180 std::vector<std::string> groups;
181 for (const auto& [group_name, _] : modified_palettes_) {
182 groups.push_back(group_name);
183 }
184 return groups;
185}
186
187bool PaletteManager::IsGroupModified(const std::string& group_name) const {
188 auto it = modified_palettes_.find(group_name);
189 return it != modified_palettes_.end() && !it->second.empty();
190}
191
192bool PaletteManager::IsPaletteModified(const std::string& group_name,
193 int palette_index) const {
194 auto it = modified_palettes_.find(group_name);
195 if (it == modified_palettes_.end()) {
196 return false;
197 }
198 return it->second.contains(palette_index);
199}
200
201bool PaletteManager::IsColorModified(const std::string& group_name,
202 int palette_index,
203 int color_index) const {
204 auto group_it = modified_colors_.find(group_name);
205 if (group_it == modified_colors_.end()) {
206 return false;
207 }
208
209 auto pal_it = group_it->second.find(palette_index);
210 if (pal_it == group_it->second.end()) {
211 return false;
212 }
213
214 return pal_it->second.contains(color_index);
215}
216
218 size_t count = 0;
219 for (const auto& [_, palette_map] : modified_colors_) {
220 for (const auto& [__, color_set] : palette_map) {
221 count += color_set.size();
222 }
223 }
224 return count;
225}
226
227// ========== Persistence ==========
228
229absl::Status PaletteManager::SaveGroup(const std::string& group_name) {
230 if (!IsInitialized()) {
231 return absl::FailedPreconditionError("PaletteManager not initialized");
232 }
233
234 auto* group = GetMutableGroup(group_name);
235 if (!group) {
236 return absl::NotFoundError(
237 absl::StrFormat("Palette group '%s' not found", group_name));
238 }
239
240 // Get modified palettes for this group
241 auto pal_it = modified_palettes_.find(group_name);
242 if (pal_it == modified_palettes_.end() || pal_it->second.empty()) {
243 // No changes to save
244 return absl::OkStatus();
245 }
246
247 // Write each modified palette
248 for (int palette_idx : pal_it->second) {
249 auto* palette = group->mutable_palette(palette_idx);
250
251 // Get modified colors for this palette
252 auto color_it = modified_colors_[group_name].find(palette_idx);
253 if (color_it != modified_colors_[group_name].end()) {
254 for (int color_idx : color_it->second) {
255 // Calculate ROM address using the helper function
256 uint32_t address = GetPaletteAddress(group_name, palette_idx, color_idx);
257
258 // Write color to ROM - write the 16-bit SNES color value
259 rom_->WriteShort(address, (*palette)[color_idx].snes());
260 }
261 }
262 }
263
264 // Update original snapshots
265 auto& originals = original_palettes_[group_name];
266 for (size_t i = 0; i < group->size() && i < originals.size(); i++) {
267 originals[i] = group->palette(i);
268 }
269
270 // Clear modified flags for this group
271 ClearModifiedFlags(group_name);
272
273 // Mark ROM as dirty
274 rom_->set_dirty(true);
275
276 // Notify listeners
278 -1, -1};
279 NotifyListeners(event);
280
281 return absl::OkStatus();
282}
283
285 if (!IsInitialized()) {
286 return absl::FailedPreconditionError("PaletteManager not initialized");
287 }
288
289 // Save all modified groups
290 for (const auto& group_name : GetModifiedGroups()) {
291 RETURN_IF_ERROR(SaveGroup(group_name));
292 }
293
294 // Notify listeners
296 NotifyListeners(event);
297
298 return absl::OkStatus();
299}
300
301void PaletteManager::DiscardGroup(const std::string& group_name) {
302 if (!IsInitialized()) {
303 return;
304 }
305
306 auto* group = GetMutableGroup(group_name);
307 if (!group) {
308 return;
309 }
310
311 // Get modified palettes
312 auto pal_it = modified_palettes_.find(group_name);
313 if (pal_it == modified_palettes_.end()) {
314 return;
315 }
316
317 // Restore from original snapshots
318 auto orig_it = original_palettes_.find(group_name);
319 if (orig_it != original_palettes_.end()) {
320 for (int palette_idx : pal_it->second) {
321 if (palette_idx < orig_it->second.size()) {
322 *group->mutable_palette(palette_idx) = orig_it->second[palette_idx];
323 }
324 }
325 }
326
327 // Clear modified flags
328 ClearModifiedFlags(group_name);
329
330 // Notify listeners
332 group_name, -1, -1};
333 NotifyListeners(event);
334}
335
337 if (!IsInitialized()) {
338 return;
339 }
340
341 // Discard all modified groups
342 for (const auto& group_name : GetModifiedGroups()) {
343 DiscardGroup(group_name);
344 }
345
346 // Clear undo/redo
347 ClearHistory();
348
349 // Notify listeners
351 -1};
352 NotifyListeners(event);
353}
354
355// ========== Undo/Redo ==========
356
358 if (!CanUndo()) {
359 return;
360 }
361
362 auto change = undo_stack_.back();
363 undo_stack_.pop_back();
364
365 // Restore original color
366 auto* group = GetMutableGroup(change.group_name);
367 if (group && change.palette_index < group->size()) {
368 auto* palette = group->mutable_palette(change.palette_index);
369 if (change.color_index < palette->size()) {
370 (*palette)[change.color_index] = change.original_color;
371 }
372 }
373
374 // Move to redo stack
375 redo_stack_.push_back(change);
376
377 // Notify listeners
379 change.group_name, change.palette_index,
380 change.color_index};
381 NotifyListeners(event);
382}
383
385 if (!CanRedo()) {
386 return;
387 }
388
389 auto change = redo_stack_.back();
390 redo_stack_.pop_back();
391
392 // Reapply new color
393 auto* group = GetMutableGroup(change.group_name);
394 if (group && change.palette_index < group->size()) {
395 auto* palette = group->mutable_palette(change.palette_index);
396 if (change.color_index < palette->size()) {
397 (*palette)[change.color_index] = change.new_color;
398 }
399 }
400
401 // Move back to undo stack
402 undo_stack_.push_back(change);
403
404 // Notify listeners
406 change.group_name, change.palette_index,
407 change.color_index};
408 NotifyListeners(event);
409}
410
412 undo_stack_.clear();
413 redo_stack_.clear();
414}
415
416// ========== Change Notifications ==========
417
419 int id = next_callback_id_++;
420 change_listeners_[id] = callback;
421 return id;
422}
423
425 change_listeners_.erase(callback_id);
426}
427
428// ========== Batch Operations ==========
429
431 batch_depth_++;
432 if (batch_depth_ == 1) {
433 batch_changes_.clear();
434 }
435}
436
438 if (batch_depth_ == 0) {
439 return;
440 }
441
442 batch_depth_--;
443
444 if (batch_depth_ == 0 && !batch_changes_.empty()) {
445 // Commit all batch changes as a single undo step
446 for (const auto& change : batch_changes_) {
447 RecordChange(change);
448
449 // Notify listeners for each change
451 change.group_name, change.palette_index,
452 change.color_index};
453 NotifyListeners(event);
454 }
455
456 batch_changes_.clear();
457 }
458}
459
460// ========== Private Helpers ==========
461
462PaletteGroup* PaletteManager::GetMutableGroup(const std::string& group_name) {
463 if (!IsInitialized()) {
464 return nullptr;
465 }
466 try {
467 return rom_->mutable_palette_group()->get_group(group_name);
468 } catch (const std::exception&) {
469 return nullptr;
470 }
471}
472
474 const std::string& group_name) const {
475 if (!IsInitialized()) {
476 return nullptr;
477 }
478 try {
479 // Need to const_cast because get_group() is not const
480 return const_cast<Rom*>(rom_)->mutable_palette_group()->get_group(
481 group_name);
482 } catch (const std::exception&) {
483 return nullptr;
484 }
485}
486
487SnesColor PaletteManager::GetOriginalColor(const std::string& group_name,
488 int palette_index,
489 int color_index) const {
490 auto it = original_palettes_.find(group_name);
491 if (it == original_palettes_.end() || palette_index >= it->second.size()) {
492 return SnesColor();
493 }
494
495 const auto& palette = it->second[palette_index];
496 if (color_index >= palette.size()) {
497 return SnesColor();
498 }
499
500 return palette[color_index];
501}
502
504 undo_stack_.push_back(change);
505
506 // Limit history size
507 if (undo_stack_.size() > kMaxUndoHistory) {
508 undo_stack_.pop_front();
509 }
510
511 // Clear redo stack (can't redo after a new change)
512 redo_stack_.clear();
513}
514
516 for (const auto& [_, callback] : change_listeners_) {
517 callback(event);
518 }
519}
520
521void PaletteManager::MarkModified(const std::string& group_name,
522 int palette_index, int color_index) {
523 modified_palettes_[group_name].insert(palette_index);
524 modified_colors_[group_name][palette_index].insert(color_index);
525}
526
527void PaletteManager::ClearModifiedFlags(const std::string& group_name) {
528 modified_palettes_.erase(group_name);
529 modified_colors_.erase(group_name);
530}
531
532} // namespace gfx
533} // namespace yaze
The Rom class is used to load, save, and modify Rom data.
Definition rom.h:74
auto mutable_palette_group()
Definition rom.h:217
void set_dirty(bool dirty)
Definition rom.h:202
absl::Status WriteShort(int addr, uint16_t value)
Definition rom.cc:753
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)
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.
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.
void Initialize(Rom *rom)
Initialize the palette manager with ROM data.
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.
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.
void Redo()
Redo the most recently undone change.
SNES Color container.
Definition snes_color.h:109
#define RETURN_IF_ERROR(expression)
Definition macro.h:53
uint32_t GetPaletteAddress(const std::string &group_name, size_t palette_index, size_t color_index)
Main namespace for the application.
Definition controller.cc:20
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 group of palettes.