feat(palette): implement centralized PaletteManager for improved color management

- Introduced PaletteManager to handle all palette-related operations, including color modifications, undo/redo functionality, and batch processing.
- Updated PaletteEditor and PaletteGroupCard to utilize PaletteManager for managing palette states and modifications, streamlining the editing process.
- Enhanced user interface with confirmation popups for discard actions and error notifications for save failures.

Benefits:
- Centralizes palette management, improving consistency and reducing code duplication across editors.
- Enhances user experience by providing clear feedback on unsaved changes and simplifying color operations.
This commit is contained in:
scawful
2025-10-12 21:42:13 -04:00
parent 19cc46614a
commit 9c89ad5843
13 changed files with 1658 additions and 230 deletions

View File

@@ -16,6 +16,7 @@ set(
app/gfx/tilemap.cc
app/gfx/graphics_optimizer.cc
app/gfx/bpp_format_manager.cc
app/gfx/palette_manager.cc
app/gfx/backend/sdl2_renderer.cc
)

View File

@@ -0,0 +1,534 @@
#include "palette_manager.h"
#include <chrono>
#include "absl/strings/str_format.h"
#include "app/gfx/snes_palette.h"
#include "util/macro.h"
namespace yaze {
namespace gfx {
void PaletteManager::Initialize(Rom* rom) {
if (!rom) {
return;
}
rom_ = rom;
// Load original palette snapshots for all groups
auto* palette_groups = rom_->mutable_palette_group();
// Snapshot all palette groups
const char* group_names[] = {
"ow_main", "ow_aux", "ow_animated", "hud", "global_sprites",
"armors", "swords", "shields", "sprites_aux1", "sprites_aux2",
"sprites_aux3", "dungeon_main", "grass", "3d_object", "ow_mini_map"
};
for (const auto& group_name : group_names) {
try {
auto* group = palette_groups->get_group(group_name);
if (group) {
std::vector<SnesPalette> originals;
for (size_t i = 0; i < group->size(); i++) {
originals.push_back(group->palette(i));
}
original_palettes_[group_name] = originals;
}
} catch (const std::exception& e) {
// Group doesn't exist, skip
continue;
}
}
// Clear any existing state
modified_palettes_.clear();
modified_colors_.clear();
ClearHistory();
}
// ========== Color Operations ==========
SnesColor PaletteManager::GetColor(const std::string& group_name,
int palette_index,
int color_index) const {
const auto* group = GetGroup(group_name);
if (!group || palette_index < 0 || palette_index >= group->size()) {
return SnesColor();
}
const auto& palette = group->palette_ref(palette_index);
if (color_index < 0 || color_index >= palette.size()) {
return SnesColor();
}
return palette[color_index];
}
absl::Status PaletteManager::SetColor(const std::string& group_name,
int palette_index, int color_index,
const SnesColor& new_color) {
if (!IsInitialized()) {
return absl::FailedPreconditionError("PaletteManager not initialized");
}
auto* group = GetMutableGroup(group_name);
if (!group) {
return absl::NotFoundError(
absl::StrFormat("Palette group '%s' not found", group_name));
}
if (palette_index < 0 || palette_index >= group->size()) {
return absl::InvalidArgumentError(
absl::StrFormat("Palette index %d out of range [0, %d)", palette_index,
group->size()));
}
auto* palette = group->mutable_palette(palette_index);
if (color_index < 0 || color_index >= palette->size()) {
return absl::InvalidArgumentError(
absl::StrFormat("Color index %d out of range [0, %d)", color_index,
palette->size()));
}
// Get original color
SnesColor original_color = (*palette)[color_index];
// Update in-memory palette
(*palette)[color_index] = new_color;
// Track modification
MarkModified(group_name, palette_index, color_index);
// Record for undo (unless in batch mode - batch changes recorded separately)
if (!InBatch()) {
auto now = std::chrono::system_clock::now();
auto timestamp_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
now.time_since_epoch())
.count();
PaletteColorChange change{group_name, palette_index, color_index,
original_color, new_color,
static_cast<uint64_t>(timestamp_ms)};
RecordChange(change);
// Notify listeners
PaletteChangeEvent event{PaletteChangeEvent::Type::kColorChanged,
group_name, palette_index, color_index};
NotifyListeners(event);
} else {
// Store in batch buffer
auto now = std::chrono::system_clock::now();
auto timestamp_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
now.time_since_epoch())
.count();
batch_changes_.push_back(
{group_name, palette_index, color_index, original_color, new_color,
static_cast<uint64_t>(timestamp_ms)});
}
return absl::OkStatus();
}
absl::Status PaletteManager::ResetColor(const std::string& group_name,
int palette_index, int color_index) {
SnesColor original = GetOriginalColor(group_name, palette_index, color_index);
return SetColor(group_name, palette_index, color_index, original);
}
absl::Status PaletteManager::ResetPalette(const std::string& group_name,
int palette_index) {
if (!IsInitialized()) {
return absl::FailedPreconditionError("PaletteManager not initialized");
}
// Check if original snapshot exists
auto it = original_palettes_.find(group_name);
if (it == original_palettes_.end() ||
palette_index >= it->second.size()) {
return absl::NotFoundError("Original palette not found");
}
auto* group = GetMutableGroup(group_name);
if (!group || palette_index >= group->size()) {
return absl::NotFoundError("Palette group or index not found");
}
// Restore from original
*group->mutable_palette(palette_index) = it->second[palette_index];
// Clear modified flags for this palette
modified_palettes_[group_name].erase(palette_index);
modified_colors_[group_name].erase(palette_index);
// Notify listeners
PaletteChangeEvent event{PaletteChangeEvent::Type::kPaletteReset,
group_name, palette_index, -1};
NotifyListeners(event);
return absl::OkStatus();
}
// ========== Dirty Tracking ==========
bool PaletteManager::HasUnsavedChanges() const {
return !modified_palettes_.empty();
}
std::vector<std::string> PaletteManager::GetModifiedGroups() const {
std::vector<std::string> groups;
for (const auto& [group_name, _] : modified_palettes_) {
groups.push_back(group_name);
}
return groups;
}
bool PaletteManager::IsGroupModified(const std::string& group_name) const {
auto it = modified_palettes_.find(group_name);
return it != modified_palettes_.end() && !it->second.empty();
}
bool PaletteManager::IsPaletteModified(const std::string& group_name,
int palette_index) const {
auto it = modified_palettes_.find(group_name);
if (it == modified_palettes_.end()) {
return false;
}
return it->second.contains(palette_index);
}
bool PaletteManager::IsColorModified(const std::string& group_name,
int palette_index,
int color_index) const {
auto group_it = modified_colors_.find(group_name);
if (group_it == modified_colors_.end()) {
return false;
}
auto pal_it = group_it->second.find(palette_index);
if (pal_it == group_it->second.end()) {
return false;
}
return pal_it->second.contains(color_index);
}
size_t PaletteManager::GetModifiedColorCount() const {
size_t count = 0;
for (const auto& [_, palette_map] : modified_colors_) {
for (const auto& [__, color_set] : palette_map) {
count += color_set.size();
}
}
return count;
}
// ========== Persistence ==========
absl::Status PaletteManager::SaveGroup(const std::string& group_name) {
if (!IsInitialized()) {
return absl::FailedPreconditionError("PaletteManager not initialized");
}
auto* group = GetMutableGroup(group_name);
if (!group) {
return absl::NotFoundError(
absl::StrFormat("Palette group '%s' not found", group_name));
}
// Get modified palettes for this group
auto pal_it = modified_palettes_.find(group_name);
if (pal_it == modified_palettes_.end() || pal_it->second.empty()) {
// No changes to save
return absl::OkStatus();
}
// Write each modified palette
for (int palette_idx : pal_it->second) {
auto* palette = group->mutable_palette(palette_idx);
// Get modified colors for this palette
auto color_it = modified_colors_[group_name].find(palette_idx);
if (color_it != modified_colors_[group_name].end()) {
for (int color_idx : color_it->second) {
// Calculate ROM address
uint32_t address =
GetPaletteAddress(group_name, palette_idx, color_idx);
// Write color to ROM
RETURN_IF_ERROR(rom_->WriteColor(address, (*palette)[color_idx]));
}
}
}
// Update original snapshots
auto& originals = original_palettes_[group_name];
for (size_t i = 0; i < group->size() && i < originals.size(); i++) {
originals[i] = group->palette(i);
}
// Clear modified flags for this group
ClearModifiedFlags(group_name);
// Mark ROM as dirty
rom_->set_dirty(true);
// Notify listeners
PaletteChangeEvent event{PaletteChangeEvent::Type::kGroupSaved, group_name,
-1, -1};
NotifyListeners(event);
return absl::OkStatus();
}
absl::Status PaletteManager::SaveAllToRom() {
if (!IsInitialized()) {
return absl::FailedPreconditionError("PaletteManager not initialized");
}
// Save all modified groups
for (const auto& group_name : GetModifiedGroups()) {
RETURN_IF_ERROR(SaveGroup(group_name));
}
// Notify listeners
PaletteChangeEvent event{PaletteChangeEvent::Type::kAllSaved, "", -1, -1};
NotifyListeners(event);
return absl::OkStatus();
}
void PaletteManager::DiscardGroup(const std::string& group_name) {
if (!IsInitialized()) {
return;
}
auto* group = GetMutableGroup(group_name);
if (!group) {
return;
}
// Get modified palettes
auto pal_it = modified_palettes_.find(group_name);
if (pal_it == modified_palettes_.end()) {
return;
}
// Restore from original snapshots
auto orig_it = original_palettes_.find(group_name);
if (orig_it != original_palettes_.end()) {
for (int palette_idx : pal_it->second) {
if (palette_idx < orig_it->second.size()) {
*group->mutable_palette(palette_idx) = orig_it->second[palette_idx];
}
}
}
// Clear modified flags
ClearModifiedFlags(group_name);
// Notify listeners
PaletteChangeEvent event{PaletteChangeEvent::Type::kGroupDiscarded,
group_name, -1, -1};
NotifyListeners(event);
}
void PaletteManager::DiscardAllChanges() {
if (!IsInitialized()) {
return;
}
// Discard all modified groups
for (const auto& group_name : GetModifiedGroups()) {
DiscardGroup(group_name);
}
// Clear undo/redo
ClearHistory();
// Notify listeners
PaletteChangeEvent event{PaletteChangeEvent::Type::kAllDiscarded, "", -1,
-1};
NotifyListeners(event);
}
// ========== Undo/Redo ==========
void PaletteManager::Undo() {
if (!CanUndo()) {
return;
}
auto change = undo_stack_.back();
undo_stack_.pop_back();
// Restore original color
auto* group = GetMutableGroup(change.group_name);
if (group && change.palette_index < group->size()) {
auto* palette = group->mutable_palette(change.palette_index);
if (change.color_index < palette->size()) {
(*palette)[change.color_index] = change.original_color;
}
}
// Move to redo stack
redo_stack_.push_back(change);
// Notify listeners
PaletteChangeEvent event{PaletteChangeEvent::Type::kColorChanged,
change.group_name, change.palette_index,
change.color_index};
NotifyListeners(event);
}
void PaletteManager::Redo() {
if (!CanRedo()) {
return;
}
auto change = redo_stack_.back();
redo_stack_.pop_back();
// Reapply new color
auto* group = GetMutableGroup(change.group_name);
if (group && change.palette_index < group->size()) {
auto* palette = group->mutable_palette(change.palette_index);
if (change.color_index < palette->size()) {
(*palette)[change.color_index] = change.new_color;
}
}
// Move back to undo stack
undo_stack_.push_back(change);
// Notify listeners
PaletteChangeEvent event{PaletteChangeEvent::Type::kColorChanged,
change.group_name, change.palette_index,
change.color_index};
NotifyListeners(event);
}
void PaletteManager::ClearHistory() {
undo_stack_.clear();
redo_stack_.clear();
}
// ========== Change Notifications ==========
int PaletteManager::RegisterChangeListener(ChangeCallback callback) {
int id = next_callback_id_++;
change_listeners_[id] = callback;
return id;
}
void PaletteManager::UnregisterChangeListener(int callback_id) {
change_listeners_.erase(callback_id);
}
// ========== Batch Operations ==========
void PaletteManager::BeginBatch() {
batch_depth_++;
if (batch_depth_ == 1) {
batch_changes_.clear();
}
}
void PaletteManager::EndBatch() {
if (batch_depth_ == 0) {
return;
}
batch_depth_--;
if (batch_depth_ == 0 && !batch_changes_.empty()) {
// Commit all batch changes as a single undo step
for (const auto& change : batch_changes_) {
RecordChange(change);
// Notify listeners for each change
PaletteChangeEvent event{PaletteChangeEvent::Type::kColorChanged,
change.group_name, change.palette_index,
change.color_index};
NotifyListeners(event);
}
batch_changes_.clear();
}
}
// ========== Private Helpers ==========
PaletteGroup* PaletteManager::GetMutableGroup(const std::string& group_name) {
if (!IsInitialized()) {
return nullptr;
}
try {
return rom_->mutable_palette_group()->get_group(group_name);
} catch (const std::exception&) {
return nullptr;
}
}
const PaletteGroup* PaletteManager::GetGroup(
const std::string& group_name) const {
if (!IsInitialized()) {
return nullptr;
}
try {
// Need to const_cast because get_group() is not const
return const_cast<Rom*>(rom_)->mutable_palette_group()->get_group(
group_name);
} catch (const std::exception&) {
return nullptr;
}
}
SnesColor PaletteManager::GetOriginalColor(const std::string& group_name,
int palette_index,
int color_index) const {
auto it = original_palettes_.find(group_name);
if (it == original_palettes_.end() || palette_index >= it->second.size()) {
return SnesColor();
}
const auto& palette = it->second[palette_index];
if (color_index >= palette.size()) {
return SnesColor();
}
return palette[color_index];
}
void PaletteManager::RecordChange(const PaletteColorChange& change) {
undo_stack_.push_back(change);
// Limit history size
if (undo_stack_.size() > kMaxUndoHistory) {
undo_stack_.pop_front();
}
// Clear redo stack (can't redo after a new change)
redo_stack_.clear();
}
void PaletteManager::NotifyListeners(const PaletteChangeEvent& event) {
for (const auto& [_, callback] : change_listeners_) {
callback(event);
}
}
void PaletteManager::MarkModified(const std::string& group_name,
int palette_index, int color_index) {
modified_palettes_[group_name].insert(palette_index);
modified_colors_[group_name][palette_index].insert(color_index);
}
void PaletteManager::ClearModifiedFlags(const std::string& group_name) {
modified_palettes_.erase(group_name);
modified_colors_.erase(group_name);
}
} // namespace gfx
} // namespace yaze

View File

@@ -0,0 +1,319 @@
#ifndef YAZE_APP_GFX_PALETTE_MANAGER_H
#define YAZE_APP_GFX_PALETTE_MANAGER_H
#include <deque>
#include <functional>
#include <memory>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "app/gfx/snes_color.h"
#include "app/gfx/snes_palette.h"
#include "app/rom.h"
namespace yaze {
namespace gfx {
/**
* @brief Represents a single color change operation
*/
struct PaletteColorChange {
std::string group_name; ///< Palette group name (e.g., "ow_main")
int palette_index; ///< Index of palette within group
int color_index; ///< Index of color within palette
SnesColor original_color; ///< Original color before change
SnesColor new_color; ///< New color after change
uint64_t timestamp_ms; ///< Timestamp in milliseconds
};
/**
* @brief Event notification for palette changes
*/
struct PaletteChangeEvent {
enum class Type {
kColorChanged, ///< Single color was modified
kPaletteReset, ///< Entire palette was reset
kGroupSaved, ///< Palette group was saved to ROM
kGroupDiscarded, ///< Palette group changes were discarded
kAllSaved, ///< All changes saved to ROM
kAllDiscarded ///< All changes discarded
};
Type type;
std::string group_name;
int palette_index = -1;
int color_index = -1;
};
/**
* @brief Centralized palette management system
*
* Singleton coordinator for ALL palette editing operations.
* Provides:
* - Global dirty tracking across all palette groups
* - Transaction-based editing with automatic ROM synchronization
* - Unified undo/redo stack shared across all editors
* - Batch operations (save all, discard all)
* - Change notifications via observer pattern
* - Conflict resolution when multiple editors modify same palette
*
* Thread-safety: This class is NOT thread-safe. All operations must
* be called from the main UI thread.
*/
class PaletteManager {
public:
using ChangeCallback = std::function<void(const PaletteChangeEvent&)>;
/// Get the singleton instance
static PaletteManager& Get() {
static PaletteManager instance;
return instance;
}
// Delete copy/move constructors and assignment operators
PaletteManager(const PaletteManager&) = delete;
PaletteManager& operator=(const PaletteManager&) = delete;
PaletteManager(PaletteManager&&) = delete;
PaletteManager& operator=(PaletteManager&&) = delete;
// ========== Initialization ==========
/**
* @brief Initialize the palette manager with ROM data
* @param rom Pointer to ROM instance (must outlive PaletteManager)
*/
void Initialize(Rom* rom);
/**
* @brief Check if manager is initialized
*/
bool IsInitialized() const { return rom_ != nullptr; }
// ========== Color Operations ==========
/**
* @brief Get a color from a palette
* @param group_name Palette group name
* @param palette_index Palette index within group
* @param color_index Color index within palette
* @return The color, or default SnesColor if invalid indices
*/
SnesColor GetColor(const std::string& group_name, int palette_index,
int color_index) const;
/**
* @brief Set a color in a palette (records change for undo)
* @param group_name Palette group name
* @param palette_index Palette index within group
* @param color_index Color index within palette
* @param new_color The new color value
* @return Status of the operation
*/
absl::Status SetColor(const std::string& group_name, int palette_index,
int color_index, const SnesColor& new_color);
/**
* @brief Reset a single color to its original ROM value
*/
absl::Status ResetColor(const std::string& group_name, int palette_index,
int color_index);
/**
* @brief Reset an entire palette to original ROM values
*/
absl::Status ResetPalette(const std::string& group_name, int palette_index);
// ========== Dirty Tracking ==========
/**
* @brief Check if there are ANY unsaved changes
*/
bool HasUnsavedChanges() const;
/**
* @brief Get list of modified palette group names
*/
std::vector<std::string> GetModifiedGroups() const;
/**
* @brief Check if a specific palette group has modifications
*/
bool IsGroupModified(const std::string& group_name) const;
/**
* @brief Check if a specific palette is modified
*/
bool IsPaletteModified(const std::string& group_name,
int palette_index) const;
/**
* @brief Check if a specific color is modified
*/
bool IsColorModified(const std::string& group_name, int palette_index,
int color_index) const;
/**
* @brief Get count of modified colors across all groups
*/
size_t GetModifiedColorCount() const;
// ========== Persistence ==========
/**
* @brief Save a specific palette group to ROM
*/
absl::Status SaveGroup(const std::string& group_name);
/**
* @brief Save ALL modified palettes to ROM
*/
absl::Status SaveAllToRom();
/**
* @brief Discard changes for a specific group
*/
void DiscardGroup(const std::string& group_name);
/**
* @brief Discard ALL unsaved changes
*/
void DiscardAllChanges();
// ========== Undo/Redo ==========
/**
* @brief Undo the most recent change
*/
void Undo();
/**
* @brief Redo the most recently undone change
*/
void Redo();
/**
* @brief Check if undo is available
*/
bool CanUndo() const { return !undo_stack_.empty(); }
/**
* @brief Check if redo is available
*/
bool CanRedo() const { return !redo_stack_.empty(); }
/**
* @brief Get size of undo stack
*/
size_t GetUndoStackSize() const { return undo_stack_.size(); }
/**
* @brief Get size of redo stack
*/
size_t GetRedoStackSize() const { return redo_stack_.size(); }
/**
* @brief Clear undo/redo history
*/
void ClearHistory();
// ========== Change Notifications ==========
/**
* @brief Register a callback for palette change events
* @return Unique ID for this callback (use to unregister)
*/
int RegisterChangeListener(ChangeCallback callback);
/**
* @brief Unregister a change listener
*/
void UnregisterChangeListener(int callback_id);
// ========== Batch Operations ==========
/**
* @brief Begin a batch operation (groups multiple changes into one undo step)
* @note Must be paired with EndBatch()
*/
void BeginBatch();
/**
* @brief End a batch operation
*/
void EndBatch();
/**
* @brief Check if currently in a batch operation
*/
bool InBatch() const { return batch_depth_ > 0; }
private:
PaletteManager() = default;
~PaletteManager() = default;
/// Helper: Get mutable palette group
PaletteGroup* GetMutableGroup(const std::string& group_name);
/// Helper: Get const palette group
const PaletteGroup* GetGroup(const std::string& group_name) const;
/// Helper: Get original color from snapshot
SnesColor GetOriginalColor(const std::string& group_name, int palette_index,
int color_index) const;
/// Helper: Record a change for undo
void RecordChange(const PaletteColorChange& change);
/// Helper: Notify all listeners of an event
void NotifyListeners(const PaletteChangeEvent& event);
/// Helper: Mark a color as modified
void MarkModified(const std::string& group_name, int palette_index,
int color_index);
/// Helper: Clear modified flags for a group
void ClearModifiedFlags(const std::string& group_name);
// ========== Member Variables ==========
/// ROM instance (not owned)
Rom* rom_ = nullptr;
/// Original palette snapshots (loaded from ROM for reset/comparison)
/// Key: group_name, Value: vector of original palettes
std::unordered_map<std::string, std::vector<SnesPalette>>
original_palettes_;
/// Modified tracking
/// Key: group_name, Value: set of modified palette indices
std::unordered_map<std::string, std::unordered_set<int>>
modified_palettes_;
/// Detailed color modification tracking
/// Key: group_name, Value: map of palette_index -> set of color indices
std::unordered_map<std::string,
std::unordered_map<int, std::unordered_set<int>>>
modified_colors_;
/// Undo/redo stacks
std::deque<PaletteColorChange> undo_stack_;
std::deque<PaletteColorChange> redo_stack_;
static constexpr size_t kMaxUndoHistory = 500;
/// Change listeners
std::unordered_map<int, ChangeCallback> change_listeners_;
int next_callback_id_ = 1;
/// Batch operation support
int batch_depth_ = 0;
std::vector<PaletteColorChange> batch_changes_;
};
} // namespace gfx
} // namespace yaze
#endif // YAZE_APP_GFX_PALETTE_MANAGER_H

View File

@@ -99,13 +99,19 @@ std::vector<SnesColor> GetColFileData(uint8_t* data) {
}
void SnesColor::set_rgb(const ImVec4 val) {
rgb_.x = val.x / kColorByteMax;
rgb_.y = val.y / kColorByteMax;
rgb_.z = val.z / kColorByteMax;
// ImGui ColorPicker returns colors in 0-1 range, but internally we store 0-255
// Convert from 0-1 normalized to 0-255 range
rgb_.x = val.x * kColorByteMax;
rgb_.y = val.y * kColorByteMax;
rgb_.z = val.z * kColorByteMax;
rgb_.w = kColorByteMaxF; // Alpha always 255
// Create snes_color struct for ROM/SNES conversion (expects 0-255 range)
snes_color color;
color.red = val.x;
color.green = val.y;
color.blue = val.z;
color.red = static_cast<uint16_t>(rgb_.x);
color.green = static_cast<uint16_t>(rgb_.y);
color.blue = static_cast<uint16_t>(rgb_.z);
rom_color_ = color;
snes_ = ConvertRgbToSnes(color);
modified = true;
@@ -114,7 +120,9 @@ void SnesColor::set_rgb(const ImVec4 val) {
void SnesColor::set_snes(uint16_t val) {
snes_ = val;
snes_color col = ConvertSnesToRgb(val);
// ConvertSnesToRgb returns 0-255 range, store directly (not normalized 0-1)
rgb_ = ImVec4(col.red, col.green, col.blue, kColorByteMaxF);
rom_color_ = col;
modified = true;
}

View File

@@ -40,17 +40,26 @@ class SnesColor {
constexpr SnesColor()
: rgb_({0.f, 0.f, 0.f, 0.f}), snes_(0), rom_color_({0, 0, 0}) {}
explicit SnesColor(const ImVec4 val) : rgb_(val) {
explicit SnesColor(const ImVec4 val) {
// ImVec4 from ImGui is in 0-1 range, convert to 0-255 for internal storage
rgb_.x = val.x * kColorByteMax;
rgb_.y = val.y * kColorByteMax;
rgb_.z = val.z * kColorByteMax;
rgb_.w = kColorByteMaxF; // Alpha always 255
snes_color color;
color.red = static_cast<uint16_t>(val.x * kColorByteMax);
color.green = static_cast<uint16_t>(val.y * kColorByteMax);
color.blue = static_cast<uint16_t>(val.z * kColorByteMax);
color.red = static_cast<uint16_t>(rgb_.x);
color.green = static_cast<uint16_t>(rgb_.y);
color.blue = static_cast<uint16_t>(rgb_.z);
rom_color_ = color;
snes_ = ConvertRgbToSnes(color);
}
explicit SnesColor(const uint16_t val) : snes_(val) {
snes_color color = ConvertSnesToRgb(val);
rgb_ = ImVec4(color.red, color.green, color.blue, 0.f);
// ConvertSnesToRgb returns 0-255 range, store directly
rgb_ = ImVec4(color.red, color.green, color.blue, kColorByteMaxF);
rom_color_ = color;
}
explicit SnesColor(const snes_color val)