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