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