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