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
173absl::Status PaletteEditor::Update() {
174 if (rom()->is_loaded()) {
175 // Initialize the labels
176 for (int i = 0; i < kNumPalettes; i++) {
177 rom()->resource_label()->CreateOrGetLabel(
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
185 if (BeginTable("paletteEditorTable", 2, kPaletteTableFlags, ImVec2(0, 0))) {
186 TableSetupColumn("Palette Groups", ImGuiTableColumnFlags_WidthStretch,
187 GetContentRegionAvail().x);
188 TableSetupColumn("Palette Sets and Metadata",
189 ImGuiTableColumnFlags_WidthStretch,
190 GetContentRegionAvail().x);
191 TableHeadersRow();
192 TableNextRow();
193 TableNextColumn();
195
197 Separator();
198 gui::SnesColorEdit4("Current Color Picker", &current_color_,
199 ImGuiColorEditFlags_NoAlpha);
200 Separator();
202
203 TableNextColumn();
204 gfx_group_editor_.DrawPaletteViewer();
205 Separator();
206 static bool in_use = false;
207 ImGui::Checkbox("Palette in use? ", &in_use);
208 Separator();
209 static std::string palette_notes = "Notes about the palette";
210 ImGui::InputTextMultiline("Notes", palette_notes.data(), 1024,
211 ImVec2(-1, ImGui::GetTextLineHeight() * 4),
212 ImGuiInputTextFlags_AllowTabInput);
213
214 EndTable();
215 }
216
218
219 return absl::OkStatus();
220}
221
223 if (BeginChild("ColorPalette", ImVec2(0, 40), true,
224 ImGuiWindowFlags_HorizontalScrollbar)) {
225 for (int i = 0; i < custom_palette_.size(); i++) {
226 PushID(i);
227 SameLine(0.0f, GetStyle().ItemSpacing.y);
228 gui::SnesColorEdit4("##customPalette", &custom_palette_[i],
229 ImGuiColorEditFlags_NoInputs);
230 // Accept a drag drop target which adds a color to the custom_palette_
231 if (BeginDragDropTarget()) {
232 if (const ImGuiPayload* payload =
233 AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F)) {
234 ImVec4 color = ImVec4(0, 0, 0, 1.0f);
235 memcpy((float*)&color, payload->Data, sizeof(float));
236 custom_palette_.push_back(SnesColor(color));
237 }
238 EndDragDropTarget();
239 }
240
241 PopID();
242 }
243 SameLine();
244 if (ImGui::Button("Add Color")) {
245 custom_palette_.push_back(SnesColor(0x7FFF));
246 }
247 SameLine();
248 if (ImGui::Button("Export to Clipboard")) {
249 std::string clipboard;
250 for (const auto& color : custom_palette_) {
251 clipboard += absl::StrFormat("$%04X,", color.snes());
252 }
253 SetClipboardText(clipboard.c_str());
254 }
255 }
256 EndChild();
257}
258
260 if (BeginTable("Category Table", 8,
261 ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable |
262 ImGuiTableFlags_SizingStretchSame |
263 ImGuiTableFlags_Hideable,
264 ImVec2(0, 0))) {
265 TableSetupColumn("Weapons and Gear");
266 TableSetupColumn("Overworld and Area Colors");
267 TableSetupColumn("Global Sprites");
268 TableSetupColumn("Sprites Aux1");
269 TableSetupColumn("Sprites Aux2");
270 TableSetupColumn("Sprites Aux3");
271 TableSetupColumn("Maps and Items");
272 TableSetupColumn("Dungeons");
273 TableHeadersRow();
274 TableNextRow();
275
276 TableSetColumnIndex(0);
277 if (TreeNode("Sword")) {
278 status_ = DrawPaletteGroup(PaletteCategory::kSword);
279 TreePop();
280 }
281 if (TreeNode("Shield")) {
282 status_ = DrawPaletteGroup(PaletteCategory::kShield);
283 TreePop();
284 }
285 if (TreeNode("Clothes")) {
286 status_ = DrawPaletteGroup(PaletteCategory::kClothes, true);
287 TreePop();
288 }
289
290 TableSetColumnIndex(1);
291 gui::BeginChildWithScrollbar("##WorldPaletteScrollRegion");
292 if (TreeNode("World Colors")) {
293 status_ = DrawPaletteGroup(PaletteCategory::kWorldColors);
294 TreePop();
295 }
296 if (TreeNode("Area Colors")) {
297 status_ = DrawPaletteGroup(PaletteCategory::kAreaColors);
298 TreePop();
299 }
300 EndChild();
301
302 TableSetColumnIndex(2);
303 status_ = DrawPaletteGroup(PaletteCategory::kGlobalSprites, true);
304
305 TableSetColumnIndex(3);
306 status_ = DrawPaletteGroup(PaletteCategory::kSpritesAux1);
307
308 TableSetColumnIndex(4);
309 status_ = DrawPaletteGroup(PaletteCategory::kSpritesAux2);
310
311 TableSetColumnIndex(5);
312 status_ = DrawPaletteGroup(PaletteCategory::kSpritesAux3);
313
314 TableSetColumnIndex(6);
315 gui::BeginChildWithScrollbar("##MapPaletteScrollRegion");
316 if (TreeNode("World Map")) {
317 status_ = DrawPaletteGroup(PaletteCategory::kWorldMap, true);
318 TreePop();
319 }
320 if (TreeNode("Dungeon Map")) {
321 status_ = DrawPaletteGroup(PaletteCategory::kDungeonMap);
322 TreePop();
323 }
324 if (TreeNode("Triforce")) {
325 status_ = DrawPaletteGroup(PaletteCategory::kTriforce);
326 TreePop();
327 }
328 if (TreeNode("Crystal")) {
329 status_ = DrawPaletteGroup(PaletteCategory::kCrystal);
330 TreePop();
331 }
332 EndChild();
333
334 TableSetColumnIndex(7);
335 gui::BeginChildWithScrollbar("##DungeonPaletteScrollRegion");
336 status_ = DrawPaletteGroup(PaletteCategory::kDungeons, true);
337 EndChild();
338
339 EndTable();
340 }
341}
342
343absl::Status PaletteEditor::DrawPaletteGroup(int category, bool right_side) {
344 if (!rom()->is_loaded()) {
345 return absl::NotFoundError("ROM not open, no palettes to display");
346 }
347
348 auto palette_group_name = kPaletteGroupNames[category];
349 gfx::PaletteGroup* palette_group =
350 rom()->mutable_palette_group()->get_group(palette_group_name.data());
351 const auto size = palette_group->size();
352
353 static bool edit_color = false;
354 for (int j = 0; j < size; j++) {
355 gfx::SnesPalette* palette = palette_group->mutable_palette(j);
356 auto pal_size = palette->size();
357
358 for (int n = 0; n < pal_size; n++) {
359 PushID(n);
360 if (!right_side) {
361 if ((n % 7) != 0) SameLine(0.0f, GetStyle().ItemSpacing.y);
362 } else {
363 if ((n % 15) != 0) SameLine(0.0f, GetStyle().ItemSpacing.y);
364 }
365
366 auto popup_id =
367 absl::StrCat(kPaletteCategoryNames[category].data(), j, "_", n);
368
369 // Small icon of the color in the palette
370 if (gui::SnesColorButton(popup_id, *palette->mutable_color(n),
371 kPalNoAlpha)) {
373 }
374
375 if (BeginPopupContextItem(popup_id.c_str())) {
376 RETURN_IF_ERROR(HandleColorPopup(*palette, category, j, n))
377 }
378 PopID();
379 }
380 SameLine();
381 rom()->resource_label()->SelectableLabelWithNameEdit(
382 false, palette_group_name.data(), /*key=*/std::to_string(j),
383 "Unnamed Palette");
384 if (right_side) Separator();
385 }
386 return absl::OkStatus();
387}
388
390 if (BeginChild("ModifiedColors", ImVec2(0, 100), true,
391 ImGuiWindowFlags_HorizontalScrollbar)) {
392 for (int i = 0; i < history_.size(); i++) {
393 PushID(i);
394 gui::SnesColorEdit4("Original ", &history_.GetOriginalColor(i),
395 ImGuiColorEditFlags_NoInputs);
396 SameLine(0.0f, GetStyle().ItemSpacing.y);
397 gui::SnesColorEdit4("Modified ", &history_.GetModifiedColor(i),
398 ImGuiColorEditFlags_NoInputs);
399 PopID();
400 }
401 }
402 EndChild();
403}
404
406 int j, int n) {
407 auto col = gfx::ToFloatArray(palette[n]);
408 auto original_color = palette[n];
409 if (gui::SnesColorEdit4("Edit Color", &palette[n], kColorPopupFlags)) {
410 history_.RecordChange(/*group_name=*/std::string(kPaletteGroupNames[i]),
411 /*palette_index=*/j, /*color_index=*/n,
412 original_color, palette[n]);
413 palette[n].set_modified(true);
414 }
415
416 if (Button("Copy as..", ImVec2(-1, 0))) OpenPopup("Copy");
417 if (BeginPopup("Copy")) {
418 int cr = F32_TO_INT8_SAT(col[0]);
419 int cg = F32_TO_INT8_SAT(col[1]);
420 int cb = F32_TO_INT8_SAT(col[2]);
421 char buf[64];
422
423 CustomFormatString(buf, IM_ARRAYSIZE(buf), "(%.3ff, %.3ff, %.3ff)", col[0],
424 col[1], col[2]);
425 if (Selectable(buf)) SetClipboardText(buf);
426
427 CustomFormatString(buf, IM_ARRAYSIZE(buf), "(%d,%d,%d)", cr, cg, cb);
428 if (Selectable(buf)) SetClipboardText(buf);
429
430 CustomFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", cr, cg, cb);
431 if (Selectable(buf)) SetClipboardText(buf);
432
433 // SNES Format
434 CustomFormatString(buf, IM_ARRAYSIZE(buf), "$%04X",
435 ConvertRgbToSnes(ImVec4(col[0], col[1], col[2], 1.0f)));
436 if (Selectable(buf)) SetClipboardText(buf);
437
438 EndPopup();
439 }
440
441 EndPopup();
442 return absl::OkStatus();
443}
444
446 int index) {
447 if (index >= palette.size()) {
448 return absl::InvalidArgumentError("Index out of bounds");
449 }
450
451 // Get the current color
452 ASSIGN_OR_RETURN(auto color, palette.GetColor(index));
453 auto currentColor = color.rgb();
454 if (ColorPicker4("Color Picker", (float*)&palette[index])) {
455 // The color was modified, update it in the palette
456 palette(index, currentColor);
457 }
458 return absl::OkStatus();
459}
460
462 gfx::SnesPalette& palette, int index,
463 const gfx::SnesPalette& originalPalette) {
464 if (index >= palette.size() || index >= originalPalette.size()) {
465 return absl::InvalidArgumentError("Index out of bounds");
466 }
467 ASSIGN_OR_RETURN(auto color, originalPalette.GetColor(index));
468 auto originalColor = color.rgb();
469 palette(index, originalColor);
470 return absl::OkStatus();
471}
472
473} // namespace editor
474} // namespace yaze
auto rom()
Definition rom.h:383
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 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:62
#define TEXT_WITH_SEPARATOR(text)
Definition macro.h:98
#define ASSIGN_OR_RETURN(type_variable_name, expression)
Definition macro.h:70
#define CLEAR_AND_RETURN_STATUS(status)
Definition macro.h:105
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:370
Main namespace for the application.
Definition controller.cc:18
#define F32_TO_INT8_SAT(_VAL)
Represents a group of palettes.