yaze 0.2.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
palette_editor.cc
Go to the documentation of this file.
1#include "palette_editor.h"
2
3#include "absl/status/status.h"
4#include "absl/strings/str_cat.h"
6#include "app/gui/color.h"
7#include "imgui/imgui.h"
8
9namespace yaze {
10namespace editor {
11
12using ImGui::AcceptDragDropPayload;
13using ImGui::BeginChild;
14using ImGui::BeginDragDropTarget;
15using ImGui::BeginGroup;
16using ImGui::BeginPopup;
17using ImGui::BeginPopupContextItem;
18using ImGui::BeginTable;
19using ImGui::Button;
20using ImGui::ColorButton;
21using ImGui::ColorPicker4;
22using ImGui::EndChild;
23using ImGui::EndDragDropTarget;
24using ImGui::EndGroup;
25using ImGui::EndPopup;
26using ImGui::EndTable;
27using ImGui::GetContentRegionAvail;
28using ImGui::GetStyle;
29using ImGui::OpenPopup;
30using ImGui::PopID;
31using ImGui::PushID;
32using ImGui::SameLine;
33using ImGui::Selectable;
34using ImGui::Separator;
35using ImGui::SetClipboardText;
36using ImGui::TableHeadersRow;
37using ImGui::TableNextColumn;
38using ImGui::TableNextRow;
39using ImGui::TableSetupColumn;
40using ImGui::Text;
41
42using namespace gfx;
43
44constexpr ImGuiTableFlags kPaletteTableFlags =
45 ImGuiTableFlags_Reorderable | ImGuiTableFlags_Resizable |
46 ImGuiTableFlags_SizingStretchSame | ImGuiTableFlags_Hideable;
47
48constexpr ImGuiColorEditFlags kPalNoAlpha = ImGuiColorEditFlags_NoAlpha;
49
50constexpr ImGuiColorEditFlags kPalButtonFlags = ImGuiColorEditFlags_NoAlpha |
51 ImGuiColorEditFlags_NoPicker |
52 ImGuiColorEditFlags_NoTooltip;
53
54constexpr ImGuiColorEditFlags kColorPopupFlags =
55 ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoAlpha |
56 ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_DisplayHSV |
57 ImGuiColorEditFlags_DisplayHex;
58
59namespace {
60int CustomFormatString(char* buf, size_t buf_size, const char* fmt, ...) {
61 va_list args;
62 va_start(args, fmt);
63#ifdef IMGUI_USE_STB_SPRINTF
64 int w = stbsp_vsnprintf(buf, (int)buf_size, fmt, args);
65#else
66 int w = vsnprintf(buf, buf_size, fmt, args);
67#endif
68 va_end(args);
69 if (buf == nullptr) return w;
70 if (w == -1 || w >= (int)buf_size) w = (int)buf_size - 1;
71 buf[w] = 0;
72 return w;
73}
74
75static inline float color_saturate(float f) {
76 return (f < 0.0f) ? 0.0f : (f > 1.0f) ? 1.0f : f;
77}
78
79#define F32_TO_INT8_SAT(_VAL) \
80 ((int)(color_saturate(_VAL) * 255.0f + \
81 0.5f)) // Saturated, always output 0..255
82} // namespace
83
84absl::Status DisplayPalette(gfx::SnesPalette& palette, bool loaded) {
85 static ImVec4 color = ImVec4(0, 0, 0, 255.f);
86 static ImVec4 current_palette[256] = {};
87 ImGuiColorEditFlags misc_flags = ImGuiColorEditFlags_AlphaPreview |
88 ImGuiColorEditFlags_NoDragDrop |
89 ImGuiColorEditFlags_NoOptions;
90
91 // Generate a default palette. The palette will persist and can be edited.
92 static bool init = false;
93 if (loaded && !init) {
94 for (int n = 0; n < palette.size(); n++) {
95 auto color = palette[n];
96 current_palette[n].x = color.rgb().x / 255;
97 current_palette[n].y = color.rgb().y / 255;
98 current_palette[n].z = color.rgb().z / 255;
99 current_palette[n].w = 255; // Alpha
100 }
101 init = true;
102 }
103
104 static ImVec4 backup_color;
105 bool open_popup = ColorButton("MyColor##3b", color, misc_flags);
106 SameLine(0, GetStyle().ItemInnerSpacing.x);
107 open_popup |= Button("Palette");
108 if (open_popup) {
109 OpenPopup("mypicker");
110 backup_color = color;
111 }
112
113 if (BeginPopup("mypicker")) {
114 TEXT_WITH_SEPARATOR("Current Overworld Palette");
115 ColorPicker4("##picker", (float*)&color,
116 misc_flags | ImGuiColorEditFlags_NoSidePreview |
117 ImGuiColorEditFlags_NoSmallPreview);
118 SameLine();
119
120 BeginGroup(); // Lock X position
121 Text("Current ==>");
122 SameLine();
123 Text("Previous");
124
125 if (Button("Update Map Palette")) {
126 }
127
128 ColorButton(
129 "##current", color,
130 ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf,
131 ImVec2(60, 40));
132 SameLine();
133
134 if (ColorButton(
135 "##previous", backup_color,
136 ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf,
137 ImVec2(60, 40)))
138 color = backup_color;
139
140 // List of Colors in Overworld Palette
141 Separator();
142 Text("Palette");
143 for (int n = 0; n < IM_ARRAYSIZE(current_palette); n++) {
144 PushID(n);
145 if ((n % 8) != 0) SameLine(0.0f, GetStyle().ItemSpacing.y);
146
147 if (ColorButton("##palette", current_palette[n], kPalButtonFlags,
148 ImVec2(20, 20)))
149 color = ImVec4(current_palette[n].x, current_palette[n].y,
150 current_palette[n].z, color.w); // Preserve alpha!
151
152 if (BeginDragDropTarget()) {
153 if (const ImGuiPayload* payload =
154 AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))
155 memcpy((float*)&current_palette[n], payload->Data, sizeof(float) * 3);
156 if (const ImGuiPayload* payload =
157 AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))
158 memcpy((float*)&current_palette[n], payload->Data, sizeof(float) * 4);
159 EndDragDropTarget();
160 }
161
162 PopID();
163 }
164 EndGroup();
165 EndPopup();
166 }
167
168 return absl::OkStatus();
169}
170
172
173absl::Status PaletteEditor::Load() {
174 if (rom()->is_loaded()) {
175 // Initialize the labels
176 for (int i = 0; i < kNumPalettes; i++) {
178 "Palette Group Name", std::to_string(i),
179 std::string(kPaletteGroupNames[i]));
180 }
181 } else {
182 return absl::NotFoundError("ROM not open, no palettes to display");
183 }
184 return absl::OkStatus();
185}
186
187absl::Status PaletteEditor::Update() {
188 static int current_palette_group = 0;
189 if (BeginTable("paletteGroupsTable", 3, kPaletteTableFlags)) {
190 TableSetupColumn("Categories", ImGuiTableColumnFlags_WidthFixed, 200);
191 TableSetupColumn("Palette Editor", ImGuiTableColumnFlags_WidthStretch);
192 TableSetupColumn("Quick Access", ImGuiTableColumnFlags_WidthStretch);
193 TableHeadersRow();
194
195 TableNextRow();
196 TableNextColumn();
197
198 static int selected_category = 0;
199 BeginChild("CategoryList", ImVec2(0, GetContentRegionAvail().y), true);
200
201 for (int i = 0; i < kNumPalettes; i++) {
202 const bool is_selected = (selected_category == i);
203 if (Selectable(std::string(kPaletteCategoryNames[i]).c_str(),
204 is_selected)) {
205 selected_category = i;
206 }
207 }
208
209 EndChild();
210
211 TableNextColumn();
212 BeginChild("PaletteEditor", ImVec2(0, 0), true);
213
214 Text("%s", std::string(kPaletteCategoryNames[selected_category]).c_str());
215
216 Separator();
217
218 if (rom()->is_loaded()) {
219 status_ = DrawPaletteGroup(selected_category, true);
220 }
221
222 EndChild();
223
224 TableNextColumn();
226
227 EndTable();
228 }
229
230 return absl::OkStatus();
231}
232
234 BeginChild("QuickAccessPalettes", ImVec2(0, 0), true);
235
236 Text("Custom Palette");
238
239 Separator();
240
241 // Current color picker with more options
242 BeginGroup();
243 Text("Current Color");
244 gui::SnesColorEdit4("##CurrentColorPicker", &current_color_,
246
247 char buf[64];
248 auto col = current_color_.rgb();
249 int cr = F32_TO_INT8_SAT(col.x / 255.0f);
250 int cg = F32_TO_INT8_SAT(col.y / 255.0f);
251 int cb = F32_TO_INT8_SAT(col.z / 255.0f);
252
253 CustomFormatString(buf, IM_ARRAYSIZE(buf), "RGB: %d, %d, %d", cr, cg, cb);
254 Text("%s", buf);
255
256 CustomFormatString(buf, IM_ARRAYSIZE(buf), "SNES: $%04X",
257 current_color_.snes());
258 Text("%s", buf);
259
260 if (Button("Copy to Clipboard")) {
261 SetClipboardText(buf);
262 }
263 EndGroup();
264
265 Separator();
266
267 // Recently used colors
268 Text("Recently Used Colors");
269 for (int i = 0; i < recently_used_colors_.size(); i++) {
270 PushID(i);
271 if (i % 8 != 0) SameLine();
272 ImVec4 displayColor =
274 if (ImGui::ColorButton("##recent", displayColor)) {
275 // Set as current color
277 }
278 PopID();
279 }
280
281 EndChild();
282}
283
285 if (BeginChild("ColorPalette", ImVec2(0, 40), ImGuiChildFlags_None,
286 ImGuiWindowFlags_HorizontalScrollbar)) {
287 for (int i = 0; i < custom_palette_.size(); i++) {
288 PushID(i);
289 if (i > 0) SameLine(0.0f, GetStyle().ItemSpacing.y);
290
291 // Add a context menu to each color
292 ImVec4 displayColor = gui::ConvertSnesColorToImVec4(custom_palette_[i]);
293 bool open_color_picker = ImGui::ColorButton(
294 absl::StrFormat("##customPal%d", i).c_str(), displayColor);
295
296 if (open_color_picker) {
299 ImGui::OpenPopup("CustomPaletteColorEdit");
300 }
301
302 if (BeginPopupContextItem()) {
303 // Edit color directly in the popup
304 SnesColor original_color = custom_palette_[i];
305 if (gui::SnesColorEdit4("Edit Color", &custom_palette_[i],
307 // Color was changed, add to recently used
309 }
310
311 if (Button("Delete", ImVec2(-1, 0))) {
312 custom_palette_.erase(custom_palette_.begin() + i);
313 }
314 }
315
316 // Handle drag/drop for palette rearrangement
317 if (BeginDragDropTarget()) {
318 if (const ImGuiPayload* payload =
319 AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F)) {
320 ImVec4 color;
321 memcpy((float*)&color, payload->Data, sizeof(float) * 3);
322 color.w = 1.0f; // Set alpha to 1.0
323 custom_palette_[i] = SnesColor(color);
325 }
326 EndDragDropTarget();
327 }
328
329 PopID();
330 }
331
332 SameLine();
333 if (ImGui::Button("+")) {
334 custom_palette_.push_back(SnesColor(0x7FFF));
335 }
336
337 SameLine();
338 if (ImGui::Button("Clear")) {
339 custom_palette_.clear();
340 }
341
342 SameLine();
343 if (ImGui::Button("Export")) {
344 std::string clipboard;
345 for (const auto& color : custom_palette_) {
346 clipboard += absl::StrFormat("$%04X,", color.snes());
347 }
348 SetClipboardText(clipboard.c_str());
349 }
350 }
351 EndChild();
352
353 // Color picker popup for custom palette editing
354 if (ImGui::BeginPopup("CustomPaletteColorEdit")) {
355 if (edit_palette_index_ >= 0 &&
359 "Edit Color", &custom_palette_[edit_palette_index_],
360 kColorPopupFlags | ImGuiColorEditFlags_PickerHueWheel)) {
361 // Color was changed, add to recently used
363 }
364 }
365 ImGui::EndPopup();
366 }
367}
368
369absl::Status PaletteEditor::DrawPaletteGroup(int category, bool right_side) {
370 if (!rom()->is_loaded()) {
371 return absl::NotFoundError("ROM not open, no palettes to display");
372 }
373
374 auto palette_group_name = kPaletteGroupNames[category];
375 gfx::PaletteGroup* palette_group =
376 rom()->mutable_palette_group()->get_group(palette_group_name.data());
377 const auto size = palette_group->size();
378
379 for (int j = 0; j < size; j++) {
380 gfx::SnesPalette* palette = palette_group->mutable_palette(j);
381 auto pal_size = palette->size();
382
383 BeginGroup();
384
385 PushID(j);
386 BeginGroup();
388 false, palette_group_name.data(), /*key=*/std::to_string(j),
389 "Unnamed Palette");
390 EndGroup();
391
392 for (int n = 0; n < pal_size; n++) {
393 PushID(n);
394 if (n > 0 && n % 8 != 0) SameLine(0.0f, 2.0f);
395
396 auto popup_id =
397 absl::StrCat(kPaletteCategoryNames[category].data(), j, "_", n);
398
399 ImVec4 displayColor = gui::ConvertSnesColorToImVec4((*palette)[n]);
400 if (ImGui::ColorButton(popup_id.c_str(), displayColor)) {
401 current_color_ = (*palette)[n];
403 }
404
405 if (BeginPopupContextItem(popup_id.c_str())) {
406 RETURN_IF_ERROR(HandleColorPopup(*palette, category, j, n))
407 }
408 PopID();
409 }
410 PopID();
411 EndGroup();
412
413 if (j < size - 1) {
414 Separator();
415 }
416 }
417 return absl::OkStatus();
418}
419
421 // Check if color already exists in recently used
422 auto it = std::find_if(
424 [&color](const SnesColor& c) { return c.snes() == color.snes(); });
425
426 // If found, remove it to re-add at front
427 if (it != recently_used_colors_.end()) {
428 recently_used_colors_.erase(it);
429 }
430
431 // Add at front
432 recently_used_colors_.insert(recently_used_colors_.begin(), color);
433
434 // Limit size
435 if (recently_used_colors_.size() > 16) {
436 recently_used_colors_.pop_back();
437 }
438}
439
441 int j, int n) {
442 auto col = gfx::ToFloatArray(palette[n]);
443 auto original_color = palette[n];
444
445 if (gui::SnesColorEdit4("Edit Color", &palette[n], kColorPopupFlags)) {
446 history_.RecordChange(/*group_name=*/std::string(kPaletteGroupNames[i]),
447 /*palette_index=*/j, /*color_index=*/n,
448 original_color, palette[n]);
449 palette[n].set_modified(true);
450
451 // Add to recently used colors
452 AddRecentlyUsedColor(palette[n]);
453 }
454
455 // Color information display
456 char buf[64];
457 int cr = F32_TO_INT8_SAT(col[0]);
458 int cg = F32_TO_INT8_SAT(col[1]);
459 int cb = F32_TO_INT8_SAT(col[2]);
460
461 Text("RGB: %d, %d, %d", cr, cg, cb);
462 Text("SNES: $%04X", palette[n].snes());
463
464 Separator();
465
466 if (Button("Copy as..", ImVec2(-1, 0))) OpenPopup("Copy");
467 if (BeginPopup("Copy")) {
468 CustomFormatString(buf, IM_ARRAYSIZE(buf), "(%.3ff, %.3ff, %.3ff)", col[0],
469 col[1], col[2]);
470 if (Selectable(buf)) SetClipboardText(buf);
471
472 CustomFormatString(buf, IM_ARRAYSIZE(buf), "(%d,%d,%d)", cr, cg, cb);
473 if (Selectable(buf)) SetClipboardText(buf);
474
475 CustomFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", cr, cg, cb);
476 if (Selectable(buf)) SetClipboardText(buf);
477
478 // SNES Format
479 CustomFormatString(buf, IM_ARRAYSIZE(buf), "$%04X",
480 ConvertRgbToSnes(ImVec4(col[0], col[1], col[2], 1.0f)));
481 if (Selectable(buf)) SetClipboardText(buf);
482
483 EndPopup();
484 }
485
486 // Add a button to add this color to custom palette
487 if (Button("Add to Custom Palette", ImVec2(-1, 0))) {
488 custom_palette_.push_back(palette[n]);
489 }
490
491 EndPopup();
492 return absl::OkStatus();
493}
494
496 int index) {
497 if (index >= palette.size()) {
498 return absl::InvalidArgumentError("Index out of bounds");
499 }
500
501 // Get the current color
502 auto color = palette[index];
503 auto currentColor = color.rgb();
504 if (ColorPicker4("Color Picker", (float*)&palette[index])) {
505 // The color was modified, update it in the palette
506 palette[index] = gui::ConvertImVec4ToSnesColor(currentColor);
507
508 // Add to recently used colors
509 AddRecentlyUsedColor(palette[index]);
510 }
511 return absl::OkStatus();
512}
513
515 gfx::SnesPalette& palette, int index,
516 const gfx::SnesPalette& originalPalette) {
517 if (index >= palette.size() || index >= originalPalette.size()) {
518 return absl::InvalidArgumentError("Index out of bounds");
519 }
520 auto color = originalPalette[index];
521 auto originalColor = color.rgb();
522 palette[index] = gui::ConvertImVec4ToSnesColor(originalColor);
523 return absl::OkStatus();
524}
525
526} // namespace editor
527} // namespace yaze
auto mutable_palette_group()
Definition rom.h:188
ResourceLabelManager * resource_label()
Definition rom.h:194
absl::Status DrawPaletteGroup(int category, bool right_side=false)
absl::Status ResetColorToOriginal(gfx::SnesPalette &palette, int index, const gfx::SnesPalette &originalPalette)
std::vector< gfx::SnesColor > custom_palette_
void AddRecentlyUsedColor(const gfx::SnesColor &color)
absl::Status Update() override
absl::Status HandleColorPopup(gfx::SnesPalette &palette, int i, int j, int n)
std::vector< gfx::SnesColor > recently_used_colors_
palette_internal::PaletteEditorHistory history_
absl::Status Load() override
absl::Status EditColorInPalette(gfx::SnesPalette &palette, int index)
SNES Color container.
Definition snes_color.h:38
Represents a palette of colors for the Super Nintendo Entertainment System (SNES).
#define RETURN_IF_ERROR(expression)
Definition macro.h:51
#define TEXT_WITH_SEPARATOR(text)
Definition macro.h:87
int CustomFormatString(char *buf, size_t buf_size, const char *fmt,...)
Editors are the view controllers for the application.
constexpr ImGuiTableFlags kPaletteTableFlags
constexpr ImGuiColorEditFlags kPalNoAlpha
absl::Status DisplayPalette(gfx::SnesPalette &palette, bool loaded)
constexpr ImGuiColorEditFlags kColorPopupFlags
constexpr ImGuiColorEditFlags kPalButtonFlags
uint16_t ConvertRgbToSnes(const snes_color &color)
Definition snes_color.cc:33
std::array< float, 4 > ToFloatArray(const SnesColor &color)
constexpr int kNumPalettes
ImVec4 ConvertSnesColorToImVec4(const gfx::SnesColor &color)
Definition color.cc:10
IMGUI_API bool SnesColorEdit4(absl::string_view label, gfx::SnesColor *color, ImGuiColorEditFlags flags)
Definition color.cc:40
gfx::SnesColor ConvertImVec4ToSnesColor(const ImVec4 &color)
Definition color.cc:19
Main namespace for the application.
Definition controller.cc:18
#define F32_TO_INT8_SAT(_VAL)
std::string CreateOrGetLabel(const std::string &type, const std::string &key, const std::string &defaultValue)
Definition project.cc:175
void SelectableLabelWithNameEdit(bool selected, const std::string &type, const std::string &key, const std::string &defaultValue)
Definition project.cc:146
Represents a group of palettes.