yaze 0.2.0
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"
5#include "app/gui/canvas.h"
6#include "app/gui/color.h"
7#include "app/gui/icons.h"
8#include "app/gui/style.h"
9#include "imgui/imgui.h"
10
11namespace yaze {
12namespace app {
13namespace editor {
14
15using ImGui::AcceptDragDropPayload;
16using ImGui::BeginChild;
17using ImGui::BeginDragDropTarget;
18using ImGui::BeginGroup;
19using ImGui::BeginPopup;
20using ImGui::BeginPopupContextItem;
21using ImGui::BeginTable;
22using ImGui::Button;
23using ImGui::ColorButton;
24using ImGui::ColorPicker4;
25using ImGui::EndChild;
26using ImGui::EndDragDropTarget;
27using ImGui::EndGroup;
28using ImGui::EndPopup;
29using ImGui::EndTable;
30using ImGui::GetContentRegionAvail;
31using ImGui::GetStyle;
32using ImGui::OpenPopup;
33using ImGui::PopID;
34using ImGui::PushID;
35using ImGui::SameLine;
36using ImGui::Selectable;
37using ImGui::Separator;
38using ImGui::SetClipboardText;
39using ImGui::TableHeadersRow;
40using ImGui::TableNextColumn;
41using ImGui::TableNextRow;
42using ImGui::TableSetColumnIndex;
43using ImGui::TableSetupColumn;
44using ImGui::Text;
45using ImGui::TreeNode;
46using ImGui::TreePop;
47
48using namespace gfx;
49
50constexpr ImGuiTableFlags kPaletteTableFlags =
51 ImGuiTableFlags_Reorderable | ImGuiTableFlags_Resizable |
52 ImGuiTableFlags_SizingStretchSame | ImGuiTableFlags_Hideable;
53
54constexpr ImGuiColorEditFlags kPalNoAlpha = ImGuiColorEditFlags_NoAlpha;
55
56constexpr ImGuiColorEditFlags kPalButtonFlags2 = ImGuiColorEditFlags_NoAlpha |
57 ImGuiColorEditFlags_NoPicker |
58 ImGuiColorEditFlags_NoTooltip;
59
60constexpr ImGuiColorEditFlags kColorPopupFlags =
61 ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoAlpha;
62
63namespace {
64int CustomFormatString(char* buf, size_t buf_size, const char* fmt, ...) {
65 va_list args;
66 va_start(args, fmt);
67#ifdef IMGUI_USE_STB_SPRINTF
68 int w = stbsp_vsnprintf(buf, (int)buf_size, fmt, args);
69#else
70 int w = vsnprintf(buf, buf_size, fmt, args);
71#endif
72 va_end(args);
73 if (buf == nullptr) return w;
74 if (w == -1 || w >= (int)buf_size) w = (int)buf_size - 1;
75 buf[w] = 0;
76 return w;
77}
78
79static inline float color_saturate(float f) {
80 return (f < 0.0f) ? 0.0f : (f > 1.0f) ? 1.0f : f;
81}
82
83#define F32_TO_INT8_SAT(_VAL) \
84 ((int)(color_saturate(_VAL) * 255.0f + \
85 0.5f)) // Saturated, always output 0..255
86} // namespace
87
88absl::Status DisplayPalette(gfx::SnesPalette& palette, bool loaded) {
89 static ImVec4 color = ImVec4(0, 0, 0, 255.f);
90 static ImVec4 current_palette[256] = {};
91 ImGuiColorEditFlags misc_flags = ImGuiColorEditFlags_AlphaPreview |
92 ImGuiColorEditFlags_NoDragDrop |
93 ImGuiColorEditFlags_NoOptions;
94
95 // Generate a default palette. The palette will persist and can be edited.
96 static bool init = false;
97 if (loaded && !init) {
98 for (int n = 0; n < palette.size(); n++) {
99 ASSIGN_OR_RETURN(auto color, palette.GetColor(n));
100 current_palette[n].x = color.rgb().x / 255;
101 current_palette[n].y = color.rgb().y / 255;
102 current_palette[n].z = color.rgb().z / 255;
103 current_palette[n].w = 255; // Alpha
104 }
105 init = true;
106 }
107
108 static ImVec4 backup_color;
109 bool open_popup = ColorButton("MyColor##3b", color, misc_flags);
110 SameLine(0, GetStyle().ItemInnerSpacing.x);
111 open_popup |= Button("Palette");
112 if (open_popup) {
113 OpenPopup("mypicker");
114 backup_color = color;
115 }
116
117 if (BeginPopup("mypicker")) {
118 TEXT_WITH_SEPARATOR("Current Overworld Palette");
119 ColorPicker4("##picker", (float*)&color,
120 misc_flags | ImGuiColorEditFlags_NoSidePreview |
121 ImGuiColorEditFlags_NoSmallPreview);
122 SameLine();
123
124 BeginGroup(); // Lock X position
125 Text("Current ==>");
126 SameLine();
127 Text("Previous");
128
129 if (Button("Update Map Palette")) {
130 }
131
132 ColorButton(
133 "##current", color,
134 ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf,
135 ImVec2(60, 40));
136 SameLine();
137
138 if (ColorButton(
139 "##previous", backup_color,
140 ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf,
141 ImVec2(60, 40)))
142 color = backup_color;
143
144 // List of Colors in Overworld Palette
145 Separator();
146 Text("Palette");
147 for (int n = 0; n < IM_ARRAYSIZE(current_palette); n++) {
148 PushID(n);
149 if ((n % 8) != 0) SameLine(0.0f, GetStyle().ItemSpacing.y);
150
151 if (ColorButton("##palette", current_palette[n], kPalButtonFlags2,
152 ImVec2(20, 20)))
153 color = ImVec4(current_palette[n].x, current_palette[n].y,
154 current_palette[n].z, color.w); // Preserve alpha!
155
156 if (BeginDragDropTarget()) {
157 if (const ImGuiPayload* payload =
158 AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))
159 memcpy((float*)&current_palette[n], payload->Data, sizeof(float) * 3);
160 if (const ImGuiPayload* payload =
161 AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))
162 memcpy((float*)&current_palette[n], payload->Data, sizeof(float) * 4);
163 EndDragDropTarget();
164 }
165
166 PopID();
167 }
168 EndGroup();
169 EndPopup();
170 }
171
172 return absl::OkStatus();
173}
174
175absl::Status PaletteEditor::Update() {
176 if (rom()->is_loaded()) {
177 // Initialize the labels
178 for (int i = 0; i < kNumPalettes; i++) {
179 rom()->resource_label()->CreateOrGetLabel(
180 "Palette Group Name", std::to_string(i),
181 std::string(kPaletteGroupNames[i]));
182 }
183 } else {
184 return absl::NotFoundError("ROM not open, no palettes to display");
185 }
186
187 if (BeginTable("paletteEditorTable", 2, kPaletteTableFlags, ImVec2(0, 0))) {
188 TableSetupColumn("Palette Groups", ImGuiTableColumnFlags_WidthStretch,
189 GetContentRegionAvail().x);
190 TableSetupColumn("Palette Sets and Metadata",
191 ImGuiTableColumnFlags_WidthStretch,
192 GetContentRegionAvail().x);
193 TableHeadersRow();
194 TableNextRow();
195 TableNextColumn();
197
199 Separator();
200 gui::SnesColorEdit4("Current Color Picker", &current_color_,
201 ImGuiColorEditFlags_NoAlpha);
202 Separator();
204
205 TableNextColumn();
207 Separator();
208 static bool in_use = false;
209 ImGui::Checkbox("Palette in use? ", &in_use);
210 Separator();
211 static std::string palette_notes = "Notes about the palette";
212 ImGui::InputTextMultiline("Notes", palette_notes.data(), 1024,
213 ImVec2(-1, ImGui::GetTextLineHeight() * 4),
214 ImGuiInputTextFlags_AllowTabInput);
215
216 EndTable();
217 }
218
220
221 return absl::OkStatus();
222}
223
225 if (BeginChild("ColorPalette", ImVec2(0, 40), true,
226 ImGuiWindowFlags_HorizontalScrollbar)) {
227 for (int i = 0; i < custom_palette_.size(); i++) {
228 PushID(i);
229 SameLine(0.0f, GetStyle().ItemSpacing.y);
230 gui::SnesColorEdit4("##customPalette", &custom_palette_[i],
231 ImGuiColorEditFlags_NoInputs);
232 // Accept a drag drop target which adds a color to the custom_palette_
233 if (BeginDragDropTarget()) {
234 if (const ImGuiPayload* payload =
235 AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F)) {
236 ImVec4 color = ImVec4(0, 0, 0, 1.0f);
237 memcpy((float*)&color, payload->Data, sizeof(float));
238 custom_palette_.push_back(SnesColor(color));
239 }
240 EndDragDropTarget();
241 }
242
243 PopID();
244 }
245 SameLine();
246 if (ImGui::Button("Add Color")) {
247 custom_palette_.push_back(SnesColor(0x7FFF));
248 }
249 SameLine();
250 if (ImGui::Button("Export to Clipboard")) {
251 std::string clipboard;
252 for (const auto& color : custom_palette_) {
253 clipboard += absl::StrFormat("$%04X,", color.snes());
254 }
255 SetClipboardText(clipboard.c_str());
256 }
257 }
258 EndChild();
259}
260
262 if (BeginTable("Category Table", 8,
263 ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable |
264 ImGuiTableFlags_SizingStretchSame |
265 ImGuiTableFlags_Hideable,
266 ImVec2(0, 0))) {
267 TableSetupColumn("Weapons and Gear");
268 TableSetupColumn("Overworld and Area Colors");
269 TableSetupColumn("Global Sprites");
270 TableSetupColumn("Sprites Aux1");
271 TableSetupColumn("Sprites Aux2");
272 TableSetupColumn("Sprites Aux3");
273 TableSetupColumn("Maps and Items");
274 TableSetupColumn("Dungeons");
275 TableHeadersRow();
276 TableNextRow();
277
278 TableSetColumnIndex(0);
279 if (TreeNode("Sword")) {
281 TreePop();
282 }
283 if (TreeNode("Shield")) {
285 TreePop();
286 }
287 if (TreeNode("Clothes")) {
289 TreePop();
290 }
291
292 TableSetColumnIndex(1);
293 gui::BeginChildWithScrollbar("##WorldPaletteScrollRegion");
294 if (TreeNode("World Colors")) {
296 TreePop();
297 }
298 if (TreeNode("Area Colors")) {
300 TreePop();
301 }
302 EndChild();
303
304 TableSetColumnIndex(2);
306
307 TableSetColumnIndex(3);
309
310 TableSetColumnIndex(4);
312
313 TableSetColumnIndex(5);
315
316 TableSetColumnIndex(6);
317 gui::BeginChildWithScrollbar("##MapPaletteScrollRegion");
318 if (TreeNode("World Map")) {
320 TreePop();
321 }
322 if (TreeNode("Dungeon Map")) {
324 TreePop();
325 }
326 if (TreeNode("Triforce")) {
328 TreePop();
329 }
330 if (TreeNode("Crystal")) {
332 TreePop();
333 }
334 EndChild();
335
336 TableSetColumnIndex(7);
337 gui::BeginChildWithScrollbar("##DungeonPaletteScrollRegion");
339 EndChild();
340
341 EndTable();
342 }
343}
344
345absl::Status PaletteEditor::DrawPaletteGroup(int category, bool right_side) {
346 if (!rom()->is_loaded()) {
347 return absl::NotFoundError("ROM not open, no palettes to display");
348 }
349
350 auto palette_group_name = kPaletteGroupNames[category];
351 gfx::PaletteGroup* palette_group =
352 rom()->mutable_palette_group()->get_group(palette_group_name.data());
353 const auto size = palette_group->size();
354
355 static bool edit_color = false;
356 for (int j = 0; j < size; j++) {
357 gfx::SnesPalette* palette = palette_group->mutable_palette(j);
358 auto pal_size = palette->size();
359
360 for (int n = 0; n < pal_size; n++) {
361 PushID(n);
362 if (!right_side) {
363 if ((n % 7) != 0) SameLine(0.0f, GetStyle().ItemSpacing.y);
364 } else {
365 if ((n % 15) != 0) SameLine(0.0f, GetStyle().ItemSpacing.y);
366 }
367
368 auto popup_id =
369 absl::StrCat(kPaletteCategoryNames[category].data(), j, "_", n);
370
371 // Small icon of the color in the palette
372 if (gui::SnesColorButton(popup_id, *palette->mutable_color(n),
373 kPalNoAlpha)) {
375 }
376
377 if (BeginPopupContextItem(popup_id.c_str())) {
378 RETURN_IF_ERROR(HandleColorPopup(*palette, category, j, n))
379 }
380 PopID();
381 }
382 SameLine();
383 rom()->resource_label()->SelectableLabelWithNameEdit(
384 false, palette_group_name.data(), /*key=*/std::to_string(j),
385 "Unnamed Palette");
386 if (right_side) Separator();
387 }
388 return absl::OkStatus();
389}
390
392 if (BeginChild("ModifiedColors", ImVec2(0, 100), true,
393 ImGuiWindowFlags_HorizontalScrollbar)) {
394 for (int i = 0; i < history_.size(); i++) {
395 PushID(i);
397 ImGuiColorEditFlags_NoInputs);
398 SameLine(0.0f, GetStyle().ItemSpacing.y);
400 ImGuiColorEditFlags_NoInputs);
401 PopID();
402 }
403 }
404 EndChild();
405}
406
408 int j, int n) {
409 auto col = gfx::ToFloatArray(palette[n]);
410 auto original_color = palette[n];
411 if (gui::SnesColorEdit4("Edit Color", &palette[n], kColorPopupFlags)) {
412 history_.RecordChange(/*group_name=*/std::string(kPaletteGroupNames[i]),
413 /*palette_index=*/j, /*color_index=*/n,
414 original_color, palette[n]);
415 palette[n].set_modified(true);
416 }
417
418 if (Button("Copy as..", ImVec2(-1, 0))) OpenPopup("Copy");
419 if (BeginPopup("Copy")) {
420 int cr = F32_TO_INT8_SAT(col[0]);
421 int cg = F32_TO_INT8_SAT(col[1]);
422 int cb = F32_TO_INT8_SAT(col[2]);
423 char buf[64];
424
425 CustomFormatString(buf, IM_ARRAYSIZE(buf), "(%.3ff, %.3ff, %.3ff)", col[0],
426 col[1], col[2]);
427 if (Selectable(buf)) SetClipboardText(buf);
428
429 CustomFormatString(buf, IM_ARRAYSIZE(buf), "(%d,%d,%d)", cr, cg, cb);
430 if (Selectable(buf)) SetClipboardText(buf);
431
432 CustomFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", cr, cg, cb);
433 if (Selectable(buf)) SetClipboardText(buf);
434
435 // SNES Format
436 CustomFormatString(buf, IM_ARRAYSIZE(buf), "$%04X",
437 ConvertRGBtoSNES(ImVec4(col[0], col[1], col[2], 1.0f)));
438 if (Selectable(buf)) SetClipboardText(buf);
439
440 EndPopup();
441 }
442
443 EndPopup();
444 return absl::OkStatus();
445}
446
448 int index) {
449 if (index >= palette.size()) {
450 return absl::InvalidArgumentError("Index out of bounds");
451 }
452
453 // Get the current color
454 ASSIGN_OR_RETURN(auto color, palette.GetColor(index));
455 auto currentColor = color.rgb();
456 if (ColorPicker4("Color Picker", (float*)&palette[index])) {
457 // The color was modified, update it in the palette
458 palette(index, currentColor);
459 }
460 return absl::OkStatus();
461}
462
464 gfx::SnesPalette& palette, int index,
465 const gfx::SnesPalette& originalPalette) {
466 if (index >= palette.size() || index >= originalPalette.size()) {
467 return absl::InvalidArgumentError("Index out of bounds");
468 }
469 ASSIGN_OR_RETURN(auto color, originalPalette.GetColor(index));
470 auto originalColor = color.rgb();
471 palette(index, originalColor);
472 return absl::OkStatus();
473}
474
475} // namespace editor
476} // namespace app
477} // namespace yaze
absl::Status ResetColorToOriginal(gfx::SnesPalette &palette, int index, const gfx::SnesPalette &originalPalette)
absl::Status Update() override
absl::Status HandleColorPopup(gfx::SnesPalette &palette, int i, int j, int n)
std::vector< gfx::SnesColor > custom_palette_
absl::Status EditColorInPalette(gfx::SnesPalette &palette, int index)
absl::Status DrawPaletteGroup(int category, bool right_side=false)
palette_internal::PaletteEditorHistory history_
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)
SNES Color container.
Definition snes_color.h:38
Represents a palette of colors for the Super Nintendo Entertainment System (SNES).
absl::StatusOr< SnesColor > GetColor(int i) const
#define RETURN_IF_ERROR(expression)
Definition constants.h:69
#define TEXT_WITH_SEPARATOR(text)
Definition constants.h:105
#define ASSIGN_OR_RETURN(type_variable_name, expression)
Definition constants.h:77
#define CLEAR_AND_RETURN_STATUS(status)
Definition constants.h:112
int CustomFormatString(char *buf, size_t buf_size, const char *fmt,...)
constexpr ImGuiColorEditFlags kColorPopupFlags
constexpr ImGuiColorEditFlags kPalNoAlpha
constexpr ImGuiColorEditFlags kPalButtonFlags2
constexpr ImGuiTableFlags kPaletteTableFlags
absl::Status DisplayPalette(gfx::SnesPalette &palette, bool loaded)
uint16_t ConvertRGBtoSNES(const snes_color &color)
Definition snes_color.cc:34
std::array< float, 4 > ToFloatArray(const SnesColor &color)
constexpr int kNumPalettes
IMGUI_API bool SnesColorEdit4(absl::string_view label, SnesColor *color, ImGuiColorEditFlags flags)
Definition color.cc:42
void BeginChildWithScrollbar(const char *str_id)
Definition style.cc:48
IMGUI_API bool SnesColorButton(absl::string_view id, SnesColor &color, ImGuiColorEditFlags flags, const ImVec2 &size_arg)
Definition color.cc:25
Definition common.cc:21
#define F32_TO_INT8_SAT(_VAL)
Represents a group of palettes.