yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
color.cc
Go to the documentation of this file.
1#include "color.h"
2#include "util/i18n/tr.h"
3
4#include <algorithm>
5
6#include "absl/strings/str_format.h"
10#include "imgui/imgui.h"
11
12namespace yaze {
13namespace gui {
14
25 // SnesColor stores RGB as 0-255 in ImVec4, convert to standard 0-1 range
26 ImVec4 rgb_255 = color.rgb();
27 return ImVec4(rgb_255.x / 255.0f, rgb_255.y / 255.0f, rgb_255.z / 255.0f,
28 1.0f);
29}
30
38 // SnesColor constructor expects 0-1 range and handles conversion internally
39 return gfx::SnesColor(color);
40}
41
42std::string SnesColorToSnesString(const gfx::SnesColor& color) {
43 return absl::StrFormat("$%04X", color.snes());
44}
45
46std::string SnesColorToRgbString(const gfx::SnesColor& color) {
47 // rgb() is already in the 0-255 range.
48 const auto rgb = color.rgb();
49 return absl::StrFormat("(%d,%d,%d)", static_cast<int>(rgb.x),
50 static_cast<int>(rgb.y), static_cast<int>(rgb.z));
51}
52
53std::string SnesColorToHexString(const gfx::SnesColor& color) {
54 const auto rgb = color.rgb();
55 return absl::StrFormat("#%02X%02X%02X", static_cast<int>(rgb.x),
56 static_cast<int>(rgb.y), static_cast<int>(rgb.z));
57}
58
60 const std::string snes = SnesColorToSnesString(color);
61 const std::string rgb = SnesColorToRgbString(color);
62 const std::string hex = SnesColorToHexString(color);
63 const std::string snes_label =
64 std::string(ICON_MD_CONTENT_COPY " ") + tr("Copy as SNES");
65 if (ImGui::MenuItem(snes_label.c_str(), snes.c_str())) {
66 ImGui::SetClipboardText(snes.c_str());
67 }
68 const std::string rgb_label =
69 std::string(ICON_MD_CONTENT_COPY " ") + tr("Copy as RGB");
70 if (ImGui::MenuItem(rgb_label.c_str(), rgb.c_str())) {
71 ImGui::SetClipboardText(rgb.c_str());
72 }
73 const std::string hex_label =
74 std::string(ICON_MD_CONTENT_COPY " ") + tr("Copy as Hex");
75 if (ImGui::MenuItem(hex_label.c_str(), hex.c_str())) {
76 ImGui::SetClipboardText(hex.c_str());
77 }
78}
79
80IMGUI_API bool SnesColorButton(absl::string_view id, gfx::SnesColor& color,
81 ImGuiColorEditFlags flags,
82 const ImVec2& size_arg) {
83 // Convert the SNES color values to ImGui color values
84 ImVec4 displayColor = ConvertSnesColorToImVec4(color);
85
86 // Call the original ImGui::ColorButton with the converted color
87 bool pressed = ImGui::ColorButton(id.data(), displayColor, flags, size_arg);
88 // Add the SNES color representation to the tooltip
89 if (ImGui::IsItemHovered()) {
90 ImGui::BeginTooltip();
91 ImGui::Text(tr("SNES: $%04X"), color.snes());
92 ImGui::EndTooltip();
93 }
94 return pressed;
95}
96
97IMGUI_API bool SnesColorEdit4(absl::string_view label, gfx::SnesColor* color,
98 ImGuiColorEditFlags flags) {
99 // Convert from internal 0-255 storage to 0-1 for ImGui
100 ImVec4 displayColor = ConvertSnesColorToImVec4(*color);
101
102 // Call the original ImGui::ColorEdit4 with the converted color
103 bool changed =
104 ImGui::ColorEdit4(label.data(), (float*)&displayColor.x, flags);
105
106 // Only update if the user actually changed the color
107 if (changed) {
108 // set_rgb() handles conversion from 0-1 (ImGui) to 0-255 (internal)
109 // and automatically calculates snes_ value - no need to call set_snes
110 // separately
111 color->set_rgb(displayColor);
112 }
113
114 return changed;
115}
116
117// ============================================================================
118// New Standardized Palette Widgets
119// ============================================================================
120
121IMGUI_API bool InlinePaletteSelector(gfx::SnesPalette& palette, int num_colors,
122 int* selected_index) {
123 bool selection_made = false;
124 int colors_to_show = std::min(num_colors, static_cast<int>(palette.size()));
125
126 ImGui::BeginGroup();
127 for (int n = 0; n < colors_to_show; n++) {
128 ImGui::PushID(n);
129 if (n > 0 && (n % 8) != 0) {
130 ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.y);
131 }
132
133 bool is_selected = selected_index && (*selected_index == n);
134 if (is_selected) {
135 ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(1.0f, 1.0f, 0.0f, 1.0f));
136 ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 2.0f);
137 }
138
139 if (SnesColorButton(
140 "##palettesel", palette[n],
141 ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoPicker,
142 ImVec2(20, 20))) {
143 if (selected_index) {
144 *selected_index = n;
145 selection_made = true;
146 }
147 }
148
149 if (is_selected) {
150 ImGui::PopStyleVar();
151 ImGui::PopStyleColor();
152 }
153
154 ImGui::PopID();
155 }
156 ImGui::EndGroup();
157
158 return selection_made;
159}
160
161IMGUI_API absl::Status InlinePaletteEditor(gfx::SnesPalette& palette,
162 const std::string& title,
163 ImGuiColorEditFlags flags) {
164 if (!title.empty()) {
165 ImGui::Text("%s", title.c_str());
166 }
167
168 static int selected_color = 0;
169 static ImVec4 current_color = ImVec4(0, 0, 0, 1.0f);
170
171 // Color picker
172 ImGui::Separator();
173 if (ImGui::ColorPicker4("##colorpicker", (float*)&current_color,
174 ImGuiColorEditFlags_NoSidePreview |
175 ImGuiColorEditFlags_NoSmallPreview)) {
176 gfx::SnesColor snes_color(current_color);
177 palette.UpdateColor(selected_color, snes_color);
178 }
179
180 ImGui::Separator();
181
182 // Palette grid
183 ImGui::BeginGroup();
184 for (int n = 0; n < palette.size(); n++) {
185 ImGui::PushID(n);
186 if ((n % 8) != 0) {
187 ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.y);
188 }
189
190 if (flags == 0) {
191 flags = ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoPicker;
192 }
193
194 if (SnesColorButton("##palettedit", palette[n], flags, ImVec2(20, 20))) {
195 selected_color = n;
196 current_color = ConvertSnesColorToImVec4(palette[n]);
197 }
198
199 // Context menu
200 if (ImGui::BeginPopupContextItem()) {
201 SnesColorCopyMenuItems(palette[n]);
202 ImGui::EndPopup();
203 }
204
205 ImGui::PopID();
206 }
207 ImGui::EndGroup();
208
209 return absl::OkStatus();
210}
211
212IMGUI_API bool PopupPaletteEditor(const char* popup_id,
213 gfx::SnesPalette& palette,
214 ImGuiColorEditFlags flags) {
215 bool modified = false;
216
217 if (ImGui::BeginPopup(popup_id)) {
218 static int selected_color = 0;
219 static ImVec4 current_color = ImVec4(0, 0, 0, 1.0f);
220
221 // Compact color picker
222 if (ImGui::ColorPicker4("##popuppicker", (float*)&current_color,
223 ImGuiColorEditFlags_NoSidePreview |
224 ImGuiColorEditFlags_NoSmallPreview)) {
225 gfx::SnesColor snes_color(current_color);
226 palette.UpdateColor(selected_color, snes_color);
227 modified = true;
228 }
229
230 ImGui::Separator();
231
232 // Palette grid
233 ImGui::BeginGroup();
234 for (int n = 0; n < palette.size() && n < 64;
235 n++) { // Limit to 64 for popup
236 ImGui::PushID(n);
237 if ((n % 8) != 0) {
238 ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.y);
239 }
240
241 if (SnesColorButton(
242 "##popuppal", palette[n],
243 ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoPicker,
244 ImVec2(20, 20))) {
245 selected_color = n;
246 current_color = ConvertSnesColorToImVec4(palette[n]);
247 }
248
249 ImGui::PopID();
250 }
251 ImGui::EndGroup();
252
253 ImGui::EndPopup();
254 }
255
256 return modified;
257}
258
259// ============================================================================
260// Legacy Functions (for compatibility)
261// ============================================================================
262
263IMGUI_API bool DisplayPalette(gfx::SnesPalette& palette, bool loaded) {
264 static ImVec4 color = ImVec4(0, 0, 0, 1.0f);
265 ImGuiColorEditFlags misc_flags = ImGuiColorEditFlags_AlphaPreview |
266 ImGuiColorEditFlags_NoDragDrop |
267 ImGuiColorEditFlags_NoOptions;
268
269 // Generate a default palette. The palette will persist and can be edited.
270 static ImVec4 saved_palette[32] = {};
271 const int max_colors =
272 std::min<int>(static_cast<int>(palette.size()),
273 static_cast<int>(IM_ARRAYSIZE(saved_palette)));
274 if (loaded) {
275 for (int n = 0; n < max_colors; n++) {
276 auto color = palette[n];
277 saved_palette[n].x = color.rgb().x / 255.0f;
278 saved_palette[n].y = color.rgb().y / 255.0f;
279 saved_palette[n].z = color.rgb().z / 255.0f;
280 saved_palette[n].w = 1.0f;
281 }
282 }
283
284 static ImVec4 backup_color;
285 ImGui::Text(tr("Current ==>"));
286 ImGui::SameLine();
287 ImGui::Text(tr("Previous"));
288
289 ImGui::ColorButton(
290 "##current", color,
291 ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf,
292 ImVec2(60, 40));
293 ImGui::SameLine();
294
295 if (ImGui::ColorButton(
296 "##previous", backup_color,
297 ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf,
298 ImVec2(60, 40)))
299 color = backup_color;
300 ImGui::Separator();
301
302 ImGui::BeginGroup(); // Lock X position
303 ImGui::Text(tr("Palette"));
304 for (int n = 0; n < max_colors; n++) {
305 ImGui::PushID(n);
306 if ((n % 4) != 0)
307 ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.y);
308
309 ImGuiColorEditFlags palette_button_flags = ImGuiColorEditFlags_NoAlpha |
310 ImGuiColorEditFlags_NoPicker |
311 ImGuiColorEditFlags_NoTooltip;
312 if (ImGui::ColorButton("##palette", saved_palette[n], palette_button_flags,
313 ImVec2(20, 20)))
314 color = ImVec4(saved_palette[n].x, saved_palette[n].y, saved_palette[n].z,
315 color.w); // Preserve alpha!
316
317 if (ImGui::BeginDragDropTarget()) {
318 if (const ImGuiPayload* payload =
319 ImGui::AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))
320 memcpy((float*)&saved_palette[n], payload->Data, sizeof(float) * 3);
321 if (const ImGuiPayload* payload =
322 ImGui::AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))
323 memcpy((float*)&saved_palette[n], payload->Data, sizeof(float) * 4);
324 ImGui::EndDragDropTarget();
325 }
326
327 ImGui::PopID();
328 }
329 ImGui::EndGroup();
330 ImGui::SameLine();
331
332 ImGui::ColorPicker4("##picker", (float*)&color,
333 misc_flags | ImGuiColorEditFlags_NoSidePreview |
334 ImGuiColorEditFlags_NoSmallPreview);
335 return true;
336}
337
338void SelectablePalettePipeline(uint64_t& palette_id, bool& refresh_graphics,
339 gfx::SnesPalette& palette) {
340 const auto palette_row_size = 7;
341 if (ImGuiID child_id = ImGui::GetID((void*)(intptr_t)100);
342 ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(), true,
343 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
344 ImGui::BeginGroup(); // Lock X position
345 ImGui::Text(tr("Palette"));
346 for (int n = 0; n < palette.size(); n++) {
347 ImGui::PushID(n);
348 if ((n % palette_row_size) != 0)
349 ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.y);
350
351 // Check if the current row is selected
352 bool is_selected = (palette_id == n / palette_row_size);
353
354 // Add outline rectangle to the selected row
355 if (is_selected) {
356 ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(1.0f, 1.0f, 0.0f, 1.0f));
357 ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 2.0f);
358 }
359
360 if (gui::SnesColorButton("##palette", palette[n],
361 ImGuiColorEditFlags_NoAlpha |
362 ImGuiColorEditFlags_NoPicker |
363 ImGuiColorEditFlags_NoTooltip,
364 ImVec2(20, 20))) {
365 palette_id = n / palette_row_size;
366 refresh_graphics = true;
367 }
368
369 if (is_selected) {
370 ImGui::PopStyleColor();
371 ImGui::PopStyleVar();
372 }
373
374 ImGui::PopID();
375 }
376 ImGui::EndGroup();
377 }
378 ImGui::EndChild();
379}
380
382 const std::string& title,
383 bool show_color_picker, int colors_per_row,
384 ImGuiColorEditFlags flags) {
385 // Default flags if none provided
386 if (flags == 0) {
387 flags = ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoPicker |
388 ImGuiColorEditFlags_NoTooltip;
389 }
390
391 // Display title if provided
392 if (!title.empty()) {
393 ImGui::Text("%s", title.c_str());
394 }
395 static int selected_color = 0;
396
397 if (show_color_picker) {
398 ImGui::Separator();
399 static ImVec4 current_color = ImVec4(0, 0, 0, 1.0f);
400
401 if (ImGui::ColorPicker4("Color Picker", (float*)&current_color,
402 ImGuiColorEditFlags_NoSidePreview |
403 ImGuiColorEditFlags_NoSmallPreview)) {
404 // Convert the selected color to SNES format and add it to the palette
405 gfx::SnesColor snes_color(current_color);
406 palette.UpdateColor(selected_color, snes_color);
407 }
408 }
409
410 // Display the palette colors in a grid
411 ImGui::BeginGroup(); // Lock X position
412 for (int n = 0; n < palette.size(); n++) {
413 ImGui::PushID(n);
414 if ((n % colors_per_row) != 0) {
415 ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.y);
416 }
417
418 // Create a unique ID for this color button
419 std::string button_id = "##palette_" + std::to_string(n);
420
421 // Display the color button
422 if (SnesColorButton(button_id, palette[n], flags, ImVec2(20, 20))) {
423 // Color was clicked, could be used to select this color
424 selected_color = n;
425 }
426
427 // OpenPopup must run at this window's ID stack level, not inside the
428 // context popup, or BeginPopup below never finds it.
429 bool open_edit_color = false;
430 if (ImGui::BeginPopupContextItem()) {
431 if (ImGui::MenuItem(tr("Edit Color..."))) {
432 open_edit_color = true;
433 }
434 ImGui::Separator();
435 SnesColorCopyMenuItems(palette[n]);
436 ImGui::EndPopup();
437 }
438 if (open_edit_color) {
439 ImGui::OpenPopup(("Edit Color##" + std::to_string(n)).c_str());
440 }
441
442 // Color picker popup
443 if (ImGui::BeginPopup(("Edit Color##" + std::to_string(n)).c_str())) {
444 ImGuiColorEditFlags picker_flags = ImGuiColorEditFlags_NoSidePreview |
445 ImGuiColorEditFlags_NoSmallPreview;
446
447 ImVec4 color = ConvertSnesColorToImVec4(palette[n]);
448 if (ImGui::ColorPicker4("##picker", (float*)&color, picker_flags)) {
449 // Update the SNES color when the picker changes
450 palette[n] = ConvertImVec4ToSnesColor(color);
451 }
452
453 ImGui::EndPopup();
454 }
455
456 ImGui::PopID();
457 }
458 ImGui::EndGroup();
459
460 return absl::OkStatus();
461}
462
463IMGUI_API bool PaletteColorButton(const char* id, const gfx::SnesColor& color,
464 bool is_selected, bool is_modified,
465 const ImVec2& size,
466 ImGuiColorEditFlags flags) {
467 ImVec4 display_color = ConvertSnesColorToImVec4(color);
468
469 // Add visual indicators for selection and modification
470 ImGui::PushID(id);
471
472 // Selection border
473 if (is_selected) {
474 ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(1.0f, 0.8f, 0.0f, 1.0f));
475 ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 2.0f);
476 }
477
478 bool clicked = ImGui::ColorButton(id, display_color, flags, size);
479
480 if (is_selected) {
481 ImGui::PopStyleVar();
482 ImGui::PopStyleColor();
483 }
484
485 // Modification indicator (small dot in corner)
486 if (is_modified) {
487 ImVec2 pos = ImGui::GetItemRectMin();
488 ImVec2 dot_pos = ImVec2(pos.x + size.x - 6, pos.y + 2);
489 ImGui::GetWindowDrawList()->AddCircleFilled(dot_pos, 3.0f,
490 IM_COL32(255, 128, 0, 255));
491 }
492
493 // Tooltip with color info
494 if (ImGui::IsItemHovered()) {
495 ImGui::BeginTooltip();
496 ImGui::Text(tr("SNES: $%04X"), color.snes());
497 auto rgb = color.rgb();
498 ImGui::Text(tr("RGB: (%d, %d, %d)"), static_cast<int>(rgb.x),
499 static_cast<int>(rgb.y), static_cast<int>(rgb.z));
500 if (is_modified) {
501 ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f), tr("Modified"));
502 }
503 ImGui::EndTooltip();
504 }
505
506 ImGui::PopID();
507 return clicked;
508}
509
510} // namespace gui
511} // namespace yaze
SNES Color container.
Definition snes_color.h:110
constexpr ImVec4 rgb() const
Get RGB values (WARNING: stored as 0-255 in ImVec4)
Definition snes_color.h:183
constexpr uint16_t snes() const
Get SNES 15-bit color.
Definition snes_color.h:193
void set_rgb(const ImVec4 val)
Set color from ImVec4 (0.0-1.0 range)
Definition snes_color.cc:86
Represents a palette of colors for the Super Nintendo Entertainment System (SNES).
void UpdateColor(size_t index, const SnesColor &color)
#define ICON_MD_CONTENT_COPY
Definition icons.h:465
std::string SnesColorToRgbString(const gfx::SnesColor &color)
Definition color.cc:46
IMGUI_API bool InlinePaletteSelector(gfx::SnesPalette &palette, int num_colors, int *selected_index)
Small inline palette selector - just color buttons for selection.
Definition color.cc:121
IMGUI_API bool SnesColorButton(absl::string_view id, gfx::SnesColor &color, ImGuiColorEditFlags flags, const ImVec2 &size_arg)
Definition color.cc:80
IMGUI_API bool PopupPaletteEditor(const char *popup_id, gfx::SnesPalette &palette, ImGuiColorEditFlags flags)
Popup palette editor - same as inline but in a popup.
Definition color.cc:212
IMGUI_API absl::Status InlinePaletteEditor(gfx::SnesPalette &palette, const std::string &title, ImGuiColorEditFlags flags)
Full inline palette editor with color picker and copy options.
Definition color.cc:161
void SnesColorCopyMenuItems(const gfx::SnesColor &color)
Definition color.cc:59
IMGUI_API bool PaletteColorButton(const char *id, const gfx::SnesColor &color, bool is_selected, bool is_modified, const ImVec2 &size, ImGuiColorEditFlags flags)
Definition color.cc:463
ImVec4 ConvertSnesColorToImVec4(const gfx::SnesColor &color)
Convert SnesColor to standard ImVec4 for display.
Definition color.cc:24
void SelectablePalettePipeline(uint64_t &palette_id, bool &refresh_graphics, gfx::SnesPalette &palette)
Definition color.cc:338
IMGUI_API bool SnesColorEdit4(absl::string_view label, gfx::SnesColor *color, ImGuiColorEditFlags flags)
Definition color.cc:97
IMGUI_API bool DisplayPalette(gfx::SnesPalette &palette, bool loaded)
Definition color.cc:263
gfx::SnesColor ConvertImVec4ToSnesColor(const ImVec4 &color)
Convert standard ImVec4 to SnesColor.
Definition color.cc:37
std::string SnesColorToHexString(const gfx::SnesColor &color)
Definition color.cc:53
absl::Status DisplayEditablePalette(gfx::SnesPalette &palette, const std::string &title, bool show_color_picker, int colors_per_row, ImGuiColorEditFlags flags)
Definition color.cc:381
std::string SnesColorToSnesString(const gfx::SnesColor &color)
Definition color.cc:42
SNES color in 15-bit RGB format (BGR555)
struct snes_color snes_color
SNES color in 15-bit RGB format (BGR555)