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"
3
4#include "absl/status/status.h"
5#include "absl/strings/str_cat.h"
11#include "app/gui/core/icons.h"
12#include "imgui/imgui.h"
13
14namespace yaze {
15namespace editor {
16
17using ImGui::AcceptDragDropPayload;
18using ImGui::BeginChild;
19using ImGui::BeginDragDropTarget;
20using ImGui::BeginGroup;
21using ImGui::BeginPopup;
22using ImGui::BeginPopupContextItem;
23using ImGui::Button;
24using ImGui::ColorButton;
25using ImGui::ColorPicker4;
26using ImGui::EndChild;
27using ImGui::EndDragDropTarget;
28using ImGui::EndGroup;
29using ImGui::EndPopup;
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::Text;
39
40using namespace gfx;
41
42constexpr ImGuiTableFlags kPaletteTableFlags =
43 ImGuiTableFlags_Reorderable | ImGuiTableFlags_Resizable |
44 ImGuiTableFlags_SizingStretchSame | ImGuiTableFlags_Hideable;
45
46constexpr ImGuiColorEditFlags kPalNoAlpha = ImGuiColorEditFlags_NoAlpha;
47
48constexpr ImGuiColorEditFlags kPalButtonFlags = ImGuiColorEditFlags_NoAlpha |
49 ImGuiColorEditFlags_NoPicker |
50 ImGuiColorEditFlags_NoTooltip;
51
52constexpr ImGuiColorEditFlags kColorPopupFlags =
53 ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoAlpha |
54 ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_DisplayHSV |
55 ImGuiColorEditFlags_DisplayHex;
56
57namespace {
58int CustomFormatString(char* buf, size_t buf_size, const char* fmt, ...) {
59 va_list args;
60 va_start(args, fmt);
61#ifdef IMGUI_USE_STB_SPRINTF
62 int w = stbsp_vsnprintf(buf, (int)buf_size, fmt, args);
63#else
64 int w = vsnprintf(buf, buf_size, fmt, args);
65#endif
66 va_end(args);
67 if (buf == nullptr) return w;
68 if (w == -1 || w >= (int)buf_size) w = (int)buf_size - 1;
69 buf[w] = 0;
70 return w;
71}
72
73static inline float color_saturate(float f) {
74 return (f < 0.0f) ? 0.0f : (f > 1.0f) ? 1.0f : f;
75}
76
77#define F32_TO_INT8_SAT(_VAL) \
78 ((int)(color_saturate(_VAL) * 255.0f + \
79 0.5f)) // Saturated, always output 0..255
80} // namespace
81
99absl::Status DisplayPalette(gfx::SnesPalette& palette, bool loaded) {
100 static ImVec4 color = ImVec4(0, 0, 0, 255.f);
101 static ImVec4 current_palette[256] = {};
102 ImGuiColorEditFlags misc_flags = ImGuiColorEditFlags_AlphaPreview |
103 ImGuiColorEditFlags_NoDragDrop |
104 ImGuiColorEditFlags_NoOptions;
105
106 // Generate a default palette. The palette will persist and can be edited.
107 static bool init = false;
108 if (loaded && !init) {
109 for (int n = 0; n < palette.size(); n++) {
110 auto color = palette[n];
111 current_palette[n].x = color.rgb().x / 255;
112 current_palette[n].y = color.rgb().y / 255;
113 current_palette[n].z = color.rgb().z / 255;
114 current_palette[n].w = 255; // Alpha
115 }
116 init = true;
117 }
118
119 static ImVec4 backup_color;
120 bool open_popup = ColorButton("MyColor##3b", color, misc_flags);
121 SameLine(0, GetStyle().ItemInnerSpacing.x);
122 open_popup |= Button("Palette");
123 if (open_popup) {
124 OpenPopup("mypicker");
125 backup_color = color;
126 }
127
128 if (BeginPopup("mypicker")) {
129 TEXT_WITH_SEPARATOR("Current Overworld Palette");
130 ColorPicker4("##picker", (float*)&color,
131 misc_flags | ImGuiColorEditFlags_NoSidePreview |
132 ImGuiColorEditFlags_NoSmallPreview);
133 SameLine();
134
135 BeginGroup(); // Lock X position
136 Text("Current ==>");
137 SameLine();
138 Text("Previous");
139
140 if (Button("Update Map Palette")) {
141 }
142
143 ColorButton(
144 "##current", color,
145 ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf,
146 ImVec2(60, 40));
147 SameLine();
148
149 if (ColorButton(
150 "##previous", backup_color,
151 ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf,
152 ImVec2(60, 40)))
153 color = backup_color;
154
155 // List of Colors in Overworld Palette
156 Separator();
157 Text("Palette");
158 for (int n = 0; n < IM_ARRAYSIZE(current_palette); n++) {
159 PushID(n);
160 if ((n % 8) != 0) SameLine(0.0f, GetStyle().ItemSpacing.y);
161
162 if (ColorButton("##palette", current_palette[n], kPalButtonFlags,
163 ImVec2(20, 20)))
164 color = ImVec4(current_palette[n].x, current_palette[n].y,
165 current_palette[n].z, color.w); // Preserve alpha!
166
167 if (BeginDragDropTarget()) {
168 if (const ImGuiPayload* payload =
169 AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))
170 memcpy((float*)&current_palette[n], payload->Data, sizeof(float) * 3);
171 if (const ImGuiPayload* payload =
172 AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))
173 memcpy((float*)&current_palette[n], payload->Data, sizeof(float) * 4);
174 EndDragDropTarget();
175 }
176
177 PopID();
178 }
179 EndGroup();
180 EndPopup();
181 }
182
183 return absl::OkStatus();
184}
185
187 // Register all cards with EditorCardRegistry (done once during initialization)
188 if (!dependencies_.card_registry) return;
189 auto* card_registry = dependencies_.card_registry;
190
191 card_registry->RegisterCard({
192 .card_id = "palette.control_panel",
193 .display_name = "Palette Controls",
194 .icon = ICON_MD_PALETTE,
195 .category = "Palette",
196 .shortcut_hint = "Ctrl+Shift+P",
197 .visibility_flag = &show_control_panel_,
198 .priority = 10
199 });
200
201 card_registry->RegisterCard({
202 .card_id = "palette.ow_main",
203 .display_name = "Overworld Main",
204 .icon = ICON_MD_LANDSCAPE,
205 .category = "Palette",
206 .shortcut_hint = "Ctrl+Alt+1",
207 .visibility_flag = &show_ow_main_card_,
208 .priority = 20
209 });
210
211 card_registry->RegisterCard({
212 .card_id = "palette.ow_animated",
213 .display_name = "Overworld Animated",
214 .icon = ICON_MD_WATER,
215 .category = "Palette",
216 .shortcut_hint = "Ctrl+Alt+2",
217 .visibility_flag = &show_ow_animated_card_,
218 .priority = 30
219 });
220
221 card_registry->RegisterCard({
222 .card_id = "palette.dungeon_main",
223 .display_name = "Dungeon Main",
224 .icon = ICON_MD_CASTLE,
225 .category = "Palette",
226 .shortcut_hint = "Ctrl+Alt+3",
227 .visibility_flag = &show_dungeon_main_card_,
228 .priority = 40
229 });
230
231 card_registry->RegisterCard({
232 .card_id = "palette.sprites",
233 .display_name = "Global Sprite Palettes",
234 .icon = ICON_MD_PETS,
235 .category = "Palette",
236 .shortcut_hint = "Ctrl+Alt+4",
237 .visibility_flag = &show_sprite_card_,
238 .priority = 50
239 });
240
241 card_registry->RegisterCard({
242 .card_id = "palette.sprites_aux1",
243 .display_name = "Sprites Aux 1",
244 .icon = ICON_MD_FILTER_1,
245 .category = "Palette",
246 .shortcut_hint = "Ctrl+Alt+7",
247 .visibility_flag = &show_sprites_aux1_card_,
248 .priority = 51
249 });
250
251 card_registry->RegisterCard({
252 .card_id = "palette.sprites_aux2",
253 .display_name = "Sprites Aux 2",
254 .icon = ICON_MD_FILTER_2,
255 .category = "Palette",
256 .shortcut_hint = "Ctrl+Alt+8",
257 .visibility_flag = &show_sprites_aux2_card_,
258 .priority = 52
259 });
260
261 card_registry->RegisterCard({
262 .card_id = "palette.sprites_aux3",
263 .display_name = "Sprites Aux 3",
264 .icon = ICON_MD_FILTER_3,
265 .category = "Palette",
266 .shortcut_hint = "Ctrl+Alt+9",
267 .visibility_flag = &show_sprites_aux3_card_,
268 .priority = 53
269 });
270
271 card_registry->RegisterCard({
272 .card_id = "palette.equipment",
273 .display_name = "Equipment Palettes",
274 .icon = ICON_MD_SHIELD,
275 .category = "Palette",
276 .shortcut_hint = "Ctrl+Alt+5",
277 .visibility_flag = &show_equipment_card_,
278 .priority = 60
279 });
280
281 card_registry->RegisterCard({
282 .card_id = "palette.quick_access",
283 .display_name = "Quick Access",
284 .icon = ICON_MD_COLOR_LENS,
285 .category = "Palette",
286 .shortcut_hint = "Ctrl+Alt+Q",
287 .visibility_flag = &show_quick_access_,
288 .priority = 70
289 });
290
291 card_registry->RegisterCard({
292 .card_id = "palette.custom",
293 .display_name = "Custom Palette",
294 .icon = ICON_MD_BRUSH,
295 .category = "Palette",
296 .shortcut_hint = "Ctrl+Alt+C",
297 .visibility_flag = &show_custom_palette_,
298 .priority = 80
299 });
300
301 // Show control panel by default when Palette Editor is activated
302 show_control_panel_ = true;
303}
304
305absl::Status PaletteEditor::Load() {
306 gfx::ScopedTimer timer("PaletteEditor::Load");
307
308 if (!rom() || !rom()->is_loaded()) {
309 return absl::NotFoundError("ROM not open, no palettes to display");
310 }
311
312 // Initialize the labels
313 for (int i = 0; i < kNumPalettes; i++) {
315 "Palette Group Name", std::to_string(i),
316 std::string(kPaletteGroupNames[i]));
317 }
318
319 // Initialize the centralized PaletteManager with ROM data
320 // This must be done before creating any palette cards
322
323 // Initialize palette card instances NOW (after ROM is loaded)
324 ow_main_card_ = std::make_unique<OverworldMainPaletteCard>(rom_);
325 ow_animated_card_ = std::make_unique<OverworldAnimatedPaletteCard>(rom_);
326 dungeon_main_card_ = std::make_unique<DungeonMainPaletteCard>(rom_);
327 sprite_card_ = std::make_unique<SpritePaletteCard>(rom_);
328 sprites_aux1_card_ = std::make_unique<SpritesAux1PaletteCard>(rom_);
329 sprites_aux2_card_ = std::make_unique<SpritesAux2PaletteCard>(rom_);
330 sprites_aux3_card_ = std::make_unique<SpritesAux3PaletteCard>(rom_);
331 equipment_card_ = std::make_unique<EquipmentPaletteCard>(rom_);
332
333 return absl::OkStatus();
334}
335
336absl::Status PaletteEditor::Update() {
337 if (!rom_ || !rom_->is_loaded()) {
338 // Create a minimal loading card
339 gui::EditorCard loading_card("Palette Editor Loading", ICON_MD_PALETTE);
340 loading_card.SetDefaultSize(400, 200);
341 if (loading_card.Begin()) {
342 ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "Loading palette data...");
343 ImGui::TextWrapped("Palette cards will appear once ROM data is loaded.");
344 }
345 loading_card.End();
346 return absl::OkStatus();
347 }
348
349 // CARD-BASED EDITOR: All windows are independent top-level cards
350 // No parent wrapper - this allows closing control panel without affecting palettes
351
352 // Optional control panel (can be hidden/minimized)
355 } else if (control_panel_minimized_) {
356 // Draw floating icon button to reopen
357 ImGui::SetNextWindowPos(ImVec2(10, 100));
358 ImGui::SetNextWindowSize(ImVec2(50, 50));
359 ImGuiWindowFlags icon_flags = ImGuiWindowFlags_NoTitleBar |
360 ImGuiWindowFlags_NoResize |
361 ImGuiWindowFlags_NoScrollbar |
362 ImGuiWindowFlags_NoCollapse |
363 ImGuiWindowFlags_NoDocking;
364
365 if (ImGui::Begin("##PaletteControlIcon", nullptr, icon_flags)) {
366 if (ImGui::Button(ICON_MD_PALETTE, ImVec2(40, 40))) {
367 show_control_panel_ = true;
369 }
370 if (ImGui::IsItemHovered()) {
371 ImGui::SetTooltip("Open Palette Controls");
372 }
373 }
374 ImGui::End();
375 }
376
377 // Draw all independent palette cards
378 // Each card has its own show_ flag that needs to be synced with our visibility flags
380 if (!ow_main_card_->IsVisible()) ow_main_card_->Show();
381 ow_main_card_->Draw();
382 // Sync back if user closed the card with X button
383 if (!ow_main_card_->IsVisible()) show_ow_main_card_ = false;
384 }
385
387 if (!ow_animated_card_->IsVisible()) ow_animated_card_->Show();
388 ow_animated_card_->Draw();
389 if (!ow_animated_card_->IsVisible()) show_ow_animated_card_ = false;
390 }
391
393 if (!dungeon_main_card_->IsVisible()) dungeon_main_card_->Show();
394 dungeon_main_card_->Draw();
395 if (!dungeon_main_card_->IsVisible()) show_dungeon_main_card_ = false;
396 }
397
399 if (!sprite_card_->IsVisible()) sprite_card_->Show();
400 sprite_card_->Draw();
401 if (!sprite_card_->IsVisible()) show_sprite_card_ = false;
402 }
403
405 if (!sprites_aux1_card_->IsVisible()) sprites_aux1_card_->Show();
406 sprites_aux1_card_->Draw();
407 if (!sprites_aux1_card_->IsVisible()) show_sprites_aux1_card_ = false;
408 }
409
411 if (!sprites_aux2_card_->IsVisible()) sprites_aux2_card_->Show();
412 sprites_aux2_card_->Draw();
413 if (!sprites_aux2_card_->IsVisible()) show_sprites_aux2_card_ = false;
414 }
415
417 if (!sprites_aux3_card_->IsVisible()) sprites_aux3_card_->Show();
418 sprites_aux3_card_->Draw();
419 if (!sprites_aux3_card_->IsVisible()) show_sprites_aux3_card_ = false;
420 }
421
423 if (!equipment_card_->IsVisible()) equipment_card_->Show();
424 equipment_card_->Draw();
425 if (!equipment_card_->IsVisible()) show_equipment_card_ = false;
426 }
427
428 // Draw quick access and custom palette cards
429 if (show_quick_access_) {
431 }
432
435 }
436
437 return absl::OkStatus();
438}
439
441 BeginChild("QuickAccessPalettes", ImVec2(0, 0), true);
442
443 Text("Custom Palette");
445
446 Separator();
447
448 // Current color picker with more options
449 BeginGroup();
450 Text("Current Color");
451 gui::SnesColorEdit4("##CurrentColorPicker", &current_color_,
453
454 char buf[64];
455 auto col = current_color_.rgb();
456 int cr = F32_TO_INT8_SAT(col.x / 255.0f);
457 int cg = F32_TO_INT8_SAT(col.y / 255.0f);
458 int cb = F32_TO_INT8_SAT(col.z / 255.0f);
459
460 CustomFormatString(buf, IM_ARRAYSIZE(buf), "RGB: %d, %d, %d", cr, cg, cb);
461 Text("%s", buf);
462
463 CustomFormatString(buf, IM_ARRAYSIZE(buf), "SNES: $%04X",
465 Text("%s", buf);
466
467 if (Button("Copy to Clipboard")) {
468 SetClipboardText(buf);
469 }
470 EndGroup();
471
472 Separator();
473
474 // Recently used colors
475 Text("Recently Used Colors");
476 for (int i = 0; i < recently_used_colors_.size(); i++) {
477 PushID(i);
478 if (i % 8 != 0) SameLine();
479 ImVec4 displayColor =
481 if (ImGui::ColorButton("##recent", displayColor)) {
482 // Set as current color
484 }
485 PopID();
486 }
487
488 EndChild();
489}
490
507 if (BeginChild("ColorPalette", ImVec2(0, 40), ImGuiChildFlags_None,
508 ImGuiWindowFlags_HorizontalScrollbar)) {
509 for (int i = 0; i < custom_palette_.size(); i++) {
510 PushID(i);
511 if (i > 0) SameLine(0.0f, GetStyle().ItemSpacing.y);
512
513 // Enhanced color button with context menu and drag-drop support
514 ImVec4 displayColor = gui::ConvertSnesColorToImVec4(custom_palette_[i]);
515 bool open_color_picker = ImGui::ColorButton(
516 absl::StrFormat("##customPal%d", i).c_str(), displayColor);
517
518 if (open_color_picker) {
521 ImGui::OpenPopup("CustomPaletteColorEdit");
522 }
523
524 if (BeginPopupContextItem()) {
525 // Edit color directly in the popup
526 SnesColor original_color = custom_palette_[i];
527 if (gui::SnesColorEdit4("Edit Color", &custom_palette_[i],
529 // Color was changed, add to recently used
531 }
532
533 if (Button("Delete", ImVec2(-1, 0))) {
534 custom_palette_.erase(custom_palette_.begin() + i);
535 }
536 }
537
538 // Handle drag/drop for palette rearrangement
539 if (BeginDragDropTarget()) {
540 if (const ImGuiPayload* payload =
541 AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F)) {
542 ImVec4 color;
543 memcpy((float*)&color, payload->Data, sizeof(float) * 3);
544 color.w = 1.0f; // Set alpha to 1.0
545 custom_palette_[i] = SnesColor(color);
547 }
548 EndDragDropTarget();
549 }
550
551 PopID();
552 }
553
554 SameLine();
555 if (ImGui::Button("+")) {
556 custom_palette_.push_back(SnesColor(0x7FFF));
557 }
558
559 SameLine();
560 if (ImGui::Button("Clear")) {
561 custom_palette_.clear();
562 }
563
564 SameLine();
565 if (ImGui::Button("Export")) {
566 std::string clipboard;
567 for (const auto& color : custom_palette_) {
568 clipboard += absl::StrFormat("$%04X,", color.snes());
569 }
570 SetClipboardText(clipboard.c_str());
571 }
572 }
573 EndChild();
574
575 // Color picker popup for custom palette editing
576 if (ImGui::BeginPopup("CustomPaletteColorEdit")) {
577 if (edit_palette_index_ >= 0 &&
581 "Edit Color", &custom_palette_[edit_palette_index_],
582 kColorPopupFlags | ImGuiColorEditFlags_PickerHueWheel)) {
583 // Color was changed, add to recently used
585 }
586 }
587 ImGui::EndPopup();
588 }
589}
590
591absl::Status PaletteEditor::DrawPaletteGroup(int category, bool /*right_side*/) {
592 if (!rom()->is_loaded()) {
593 return absl::NotFoundError("ROM not open, no palettes to display");
594 }
595
596 auto palette_group_name = kPaletteGroupNames[category];
597 gfx::PaletteGroup* palette_group =
598 rom()->mutable_palette_group()->get_group(palette_group_name.data());
599 const auto size = palette_group->size();
600
601 for (int j = 0; j < size; j++) {
602 gfx::SnesPalette* palette = palette_group->mutable_palette(j);
603 auto pal_size = palette->size();
604
605 BeginGroup();
606
607 PushID(j);
608 BeginGroup();
610 false, palette_group_name.data(), /*key=*/std::to_string(j),
611 "Unnamed Palette");
612 EndGroup();
613
614 for (int n = 0; n < pal_size; n++) {
615 PushID(n);
616 if (n > 0 && n % 8 != 0) SameLine(0.0f, 2.0f);
617
618 auto popup_id =
619 absl::StrCat(kPaletteCategoryNames[category].data(), j, "_", n);
620
621 ImVec4 displayColor = gui::ConvertSnesColorToImVec4((*palette)[n]);
622 if (ImGui::ColorButton(popup_id.c_str(), displayColor)) {
623 current_color_ = (*palette)[n];
625 }
626
627 if (BeginPopupContextItem(popup_id.c_str())) {
628 RETURN_IF_ERROR(HandleColorPopup(*palette, category, j, n))
629 }
630 PopID();
631 }
632 PopID();
633 EndGroup();
634
635 if (j < size - 1) {
636 Separator();
637 }
638 }
639 return absl::OkStatus();
640}
641
643 // Check if color already exists in recently used
644 auto it = std::find_if(
646 [&color](const SnesColor& c) { return c.snes() == color.snes(); });
647
648 // If found, remove it to re-add at front
649 if (it != recently_used_colors_.end()) {
650 recently_used_colors_.erase(it);
651 }
652
653 // Add at front
654 recently_used_colors_.insert(recently_used_colors_.begin(), color);
655
656 // Limit size
657 if (recently_used_colors_.size() > 16) {
658 recently_used_colors_.pop_back();
659 }
660}
661
663 int j, int n) {
664 auto col = gfx::ToFloatArray(palette[n]);
665 auto original_color = palette[n];
666
667 if (gui::SnesColorEdit4("Edit Color", &palette[n], kColorPopupFlags)) {
668 history_.RecordChange(/*group_name=*/std::string(kPaletteGroupNames[i]),
669 /*palette_index=*/j, /*color_index=*/n,
670 original_color, palette[n]);
671 palette[n].set_modified(true);
672
673 // Add to recently used colors
674 AddRecentlyUsedColor(palette[n]);
675 }
676
677 // Color information display
678 char buf[64];
679 int cr = F32_TO_INT8_SAT(col[0]);
680 int cg = F32_TO_INT8_SAT(col[1]);
681 int cb = F32_TO_INT8_SAT(col[2]);
682
683 Text("RGB: %d, %d, %d", cr, cg, cb);
684 Text("SNES: $%04X", palette[n].snes());
685
686 Separator();
687
688 if (Button("Copy as..", ImVec2(-1, 0))) OpenPopup("Copy");
689 if (BeginPopup("Copy")) {
690 CustomFormatString(buf, IM_ARRAYSIZE(buf), "(%.3ff, %.3ff, %.3ff)", col[0],
691 col[1], col[2]);
692 if (Selectable(buf)) SetClipboardText(buf);
693
694 CustomFormatString(buf, IM_ARRAYSIZE(buf), "(%d,%d,%d)", cr, cg, cb);
695 if (Selectable(buf)) SetClipboardText(buf);
696
697 CustomFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", cr, cg, cb);
698 if (Selectable(buf)) SetClipboardText(buf);
699
700 // SNES Format
701 CustomFormatString(buf, IM_ARRAYSIZE(buf), "$%04X",
702 ConvertRgbToSnes(ImVec4(col[0], col[1], col[2], 1.0f)));
703 if (Selectable(buf)) SetClipboardText(buf);
704
705 EndPopup();
706 }
707
708 // Add a button to add this color to custom palette
709 if (Button("Add to Custom Palette", ImVec2(-1, 0))) {
710 custom_palette_.push_back(palette[n]);
711 }
712
713 EndPopup();
714 return absl::OkStatus();
715}
716
718 int index) {
719 if (index >= palette.size()) {
720 return absl::InvalidArgumentError("Index out of bounds");
721 }
722
723 // Get the current color
724 auto color = palette[index];
725 auto currentColor = color.rgb();
726 if (ColorPicker4("Color Picker", (float*)&palette[index])) {
727 // The color was modified, update it in the palette
728 palette[index] = gui::ConvertImVec4ToSnesColor(currentColor);
729
730 // Add to recently used colors
731 AddRecentlyUsedColor(palette[index]);
732 }
733 return absl::OkStatus();
734}
735
737 gfx::SnesPalette& palette, int index,
738 const gfx::SnesPalette& originalPalette) {
739 if (index >= palette.size() || index >= originalPalette.size()) {
740 return absl::InvalidArgumentError("Index out of bounds");
741 }
742 auto color = originalPalette[index];
743 auto originalColor = color.rgb();
744 palette[index] = gui::ConvertImVec4ToSnesColor(originalColor);
745 return absl::OkStatus();
746}
747
748// ============================================================================
749// Card-Based UI Methods
750// ============================================================================
751
753 // Sidebar is drawn by EditorCardRegistry in EditorManager
754 // Cards registered in Initialize() appear in the sidebar automatically
755}
756
758 ImGui::SetNextWindowSize(ImVec2(320, 420), ImGuiCond_FirstUseEver);
759 ImGui::SetNextWindowPos(ImVec2(10, 100), ImGuiCond_FirstUseEver);
760
761 ImGuiWindowFlags flags = ImGuiWindowFlags_None;
762
763 if (ImGui::Begin(ICON_MD_PALETTE " Palette Controls", &show_control_panel_, flags)) {
764 // Toolbar with quick toggles
765 DrawToolset();
766
767 ImGui::Separator();
768
769 // Quick toggle checkboxes in a table
770 ImGui::Text("Palette Groups:");
771 if (ImGui::BeginTable("##PaletteToggles", 2,
772 ImGuiTableFlags_SizingStretchSame)) {
773 ImGui::TableNextRow();
774 ImGui::TableNextColumn();
775 ImGui::Checkbox("OW Main", &show_ow_main_card_);
776 ImGui::TableNextColumn();
777 ImGui::Checkbox("OW Animated", &show_ow_animated_card_);
778
779 ImGui::TableNextRow();
780 ImGui::TableNextColumn();
781 ImGui::Checkbox("Dungeon", &show_dungeon_main_card_);
782 ImGui::TableNextColumn();
783 ImGui::Checkbox("Sprites", &show_sprite_card_);
784
785 ImGui::TableNextRow();
786 ImGui::TableNextColumn();
787 ImGui::Checkbox("Equipment", &show_equipment_card_);
788 ImGui::TableNextColumn();
789 // Empty cell
790
791 ImGui::EndTable();
792 }
793
794 ImGui::Separator();
795
796 ImGui::Text("Utilities:");
797 if (ImGui::BeginTable("##UtilityToggles", 2,
798 ImGuiTableFlags_SizingStretchSame)) {
799 ImGui::TableNextRow();
800 ImGui::TableNextColumn();
801 ImGui::Checkbox("Quick Access", &show_quick_access_);
802 ImGui::TableNextColumn();
803 ImGui::Checkbox("Custom", &show_custom_palette_);
804
805 ImGui::EndTable();
806 }
807
808 ImGui::Separator();
809
810 // Modified status indicator
811 ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f), "Modified Cards:");
812 bool any_modified = false;
813
814 if (ow_main_card_ && ow_main_card_->HasUnsavedChanges()) {
815 ImGui::BulletText("Overworld Main");
816 any_modified = true;
817 }
818 if (ow_animated_card_ && ow_animated_card_->HasUnsavedChanges()) {
819 ImGui::BulletText("Overworld Animated");
820 any_modified = true;
821 }
822 if (dungeon_main_card_ && dungeon_main_card_->HasUnsavedChanges()) {
823 ImGui::BulletText("Dungeon Main");
824 any_modified = true;
825 }
826 if (sprite_card_ && sprite_card_->HasUnsavedChanges()) {
827 ImGui::BulletText("Global Sprite Palettes");
828 any_modified = true;
829 }
830 if (sprites_aux1_card_ && sprites_aux1_card_->HasUnsavedChanges()) {
831 ImGui::BulletText("Sprites Aux 1");
832 any_modified = true;
833 }
834 if (sprites_aux2_card_ && sprites_aux2_card_->HasUnsavedChanges()) {
835 ImGui::BulletText("Sprites Aux 2");
836 any_modified = true;
837 }
838 if (sprites_aux3_card_ && sprites_aux3_card_->HasUnsavedChanges()) {
839 ImGui::BulletText("Sprites Aux 3");
840 any_modified = true;
841 }
842 if (equipment_card_ && equipment_card_->HasUnsavedChanges()) {
843 ImGui::BulletText("Equipment Palettes");
844 any_modified = true;
845 }
846
847 if (!any_modified) {
848 ImGui::TextDisabled("No unsaved changes");
849 }
850
851 ImGui::Separator();
852
853 // Quick actions
854 ImGui::Text("Quick Actions:");
855
856 // Use centralized PaletteManager for global operations
857 bool has_unsaved = gfx::PaletteManager::Get().HasUnsavedChanges();
858 size_t modified_count = gfx::PaletteManager::Get().GetModifiedColorCount();
859
860 ImGui::BeginDisabled(!has_unsaved);
861 if (ImGui::Button(absl::StrFormat("Save All (%zu colors)", modified_count).c_str(),
862 ImVec2(-1, 0))) {
863 auto status = gfx::PaletteManager::Get().SaveAllToRom();
864 if (!status.ok()) {
865 // TODO: Show error toast/notification
866 ImGui::OpenPopup("SaveError");
867 }
868 }
869 ImGui::EndDisabled();
870
871 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
872 if (has_unsaved) {
873 ImGui::SetTooltip("Save all modified colors to ROM");
874 } else {
875 ImGui::SetTooltip("No unsaved changes");
876 }
877 }
878
879 ImGui::BeginDisabled(!has_unsaved);
880 if (ImGui::Button("Discard All Changes", ImVec2(-1, 0))) {
881 ImGui::OpenPopup("ConfirmDiscardAll");
882 }
883 ImGui::EndDisabled();
884
885 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
886 if (has_unsaved) {
887 ImGui::SetTooltip("Discard all unsaved changes");
888 } else {
889 ImGui::SetTooltip("No changes to discard");
890 }
891 }
892
893 // Confirmation popup for discard
894 if (ImGui::BeginPopupModal("ConfirmDiscardAll", nullptr,
895 ImGuiWindowFlags_AlwaysAutoResize)) {
896 ImGui::Text("Discard all unsaved changes?");
897 ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f),
898 "This will revert %zu modified colors.", modified_count);
899 ImGui::Separator();
900
901 if (ImGui::Button("Discard", ImVec2(120, 0))) {
903 ImGui::CloseCurrentPopup();
904 }
905 ImGui::SameLine();
906 if (ImGui::Button("Cancel", ImVec2(120, 0))) {
907 ImGui::CloseCurrentPopup();
908 }
909 ImGui::EndPopup();
910 }
911
912 // Error popup for save failures
913 if (ImGui::BeginPopupModal("SaveError", nullptr,
914 ImGuiWindowFlags_AlwaysAutoResize)) {
915 ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), "Failed to save changes");
916 ImGui::Text("An error occurred while saving to ROM.");
917 ImGui::Separator();
918
919 if (ImGui::Button("OK", ImVec2(120, 0))) {
920 ImGui::CloseCurrentPopup();
921 }
922 ImGui::EndPopup();
923 }
924
925 ImGui::Separator();
926
927 // Editor Manager Menu Button
928 if (ImGui::Button(ICON_MD_DASHBOARD " Card Manager", ImVec2(-1, 0))) {
929 ImGui::OpenPopup("PaletteCardManager");
930 }
931
932 if (ImGui::BeginPopup("PaletteCardManager")) {
933 ImGui::TextColored(ImVec4(0.7f, 0.9f, 1.0f, 1.0f),
934 "%s Palette Card Manager", ICON_MD_PALETTE);
935 ImGui::Separator();
936
937 // View menu section now handled by EditorCardRegistry in EditorManager
938 if (!dependencies_.card_registry) return;
939 auto* card_registry = dependencies_.card_registry;
940
941 ImGui::EndPopup();
942 }
943
944 ImGui::Separator();
945
946 // Minimize button
947 if (ImGui::SmallButton(ICON_MD_MINIMIZE " Minimize to Icon")) {
949 show_control_panel_ = false;
950 }
951 }
952 ImGui::End();
953}
954
956 gui::EditorCard card("Quick Access Palette", ICON_MD_COLOR_LENS,
958 card.SetDefaultSize(340, 300);
960
961 if (card.Begin(&show_quick_access_)) {
962 // Current color picker with more options
963 ImGui::BeginGroup();
964 ImGui::Text("Current Color");
965 gui::SnesColorEdit4("##CurrentColorPicker", &current_color_,
967
968 char buf[64];
969 auto col = current_color_.rgb();
970 int cr = F32_TO_INT8_SAT(col.x / 255.0f);
971 int cg = F32_TO_INT8_SAT(col.y / 255.0f);
972 int cb = F32_TO_INT8_SAT(col.z / 255.0f);
973
974 CustomFormatString(buf, IM_ARRAYSIZE(buf), "RGB: %d, %d, %d", cr, cg, cb);
975 ImGui::Text("%s", buf);
976
977 CustomFormatString(buf, IM_ARRAYSIZE(buf), "SNES: $%04X",
979 ImGui::Text("%s", buf);
980
981 if (ImGui::Button("Copy to Clipboard", ImVec2(-1, 0))) {
982 SetClipboardText(buf);
983 }
984 ImGui::EndGroup();
985
986 ImGui::Separator();
987
988 // Recently used colors
989 ImGui::Text("Recently Used Colors");
990 if (recently_used_colors_.empty()) {
991 ImGui::TextDisabled("No recently used colors yet");
992 } else {
993 for (int i = 0; i < recently_used_colors_.size(); i++) {
994 PushID(i);
995 if (i % 8 != 0) SameLine();
996 ImVec4 displayColor =
998 if (ImGui::ColorButton("##recent", displayColor, kPalButtonFlags,
999 ImVec2(28, 28))) {
1000 // Set as current color
1002 }
1003 if (ImGui::IsItemHovered()) {
1004 ImGui::SetTooltip("SNES: $%04X", recently_used_colors_[i].snes());
1005 }
1006 PopID();
1007 }
1008 }
1009 }
1010 card.End();
1011}
1012
1014 gui::EditorCard card("Custom Palette", ICON_MD_BRUSH,
1016 card.SetDefaultSize(420, 200);
1018
1019 if (card.Begin(&show_custom_palette_)) {
1020 ImGui::TextWrapped(
1021 "Create your own custom color palette for reference. "
1022 "Colors can be added from any palette group or created from scratch.");
1023
1024 ImGui::Separator();
1025
1026 // Custom palette color grid
1027 if (custom_palette_.empty()) {
1028 ImGui::TextDisabled("Your custom palette is empty.");
1029 ImGui::Text("Click + to add colors or drag colors from any palette.");
1030 } else {
1031 for (int i = 0; i < custom_palette_.size(); i++) {
1032 PushID(i);
1033 if (i > 0 && i % 16 != 0) SameLine(0.0f, 2.0f);
1034
1035 // Enhanced color button with context menu and drag-drop support
1036 ImVec4 displayColor = gui::ConvertSnesColorToImVec4(custom_palette_[i]);
1037 bool open_color_picker = ImGui::ColorButton(
1038 absl::StrFormat("##customPal%d", i).c_str(), displayColor,
1039 kPalButtonFlags, ImVec2(28, 28));
1040
1041 if (open_color_picker) {
1044 ImGui::OpenPopup("CustomPaletteColorEdit");
1045 }
1046
1047 if (BeginPopupContextItem()) {
1048 // Edit color directly in the popup
1049 SnesColor original_color = custom_palette_[i];
1050 if (gui::SnesColorEdit4("Edit Color", &custom_palette_[i],
1052 // Color was changed, add to recently used
1054 }
1055
1056 if (ImGui::Button("Delete", ImVec2(-1, 0))) {
1057 custom_palette_.erase(custom_palette_.begin() + i);
1058 ImGui::CloseCurrentPopup();
1059 }
1060 ImGui::EndPopup();
1061 }
1062
1063 // Handle drag/drop for palette rearrangement
1064 if (BeginDragDropTarget()) {
1065 if (const ImGuiPayload* payload =
1066 AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F)) {
1067 ImVec4 color;
1068 memcpy((float*)&color, payload->Data, sizeof(float) * 3);
1069 color.w = 1.0f; // Set alpha to 1.0
1070 custom_palette_[i] = SnesColor(color);
1072 }
1073 EndDragDropTarget();
1074 }
1075
1076 PopID();
1077 }
1078 }
1079
1080 ImGui::Separator();
1081
1082 // Buttons for palette management
1083 if (ImGui::Button(ICON_MD_ADD " Add Color")) {
1084 custom_palette_.push_back(SnesColor(0x7FFF));
1085 }
1086
1087 ImGui::SameLine();
1088 if (ImGui::Button(ICON_MD_DELETE " Clear All")) {
1089 custom_palette_.clear();
1090 }
1091
1092 ImGui::SameLine();
1093 if (ImGui::Button(ICON_MD_CONTENT_COPY " Export")) {
1094 std::string clipboard;
1095 for (const auto& color : custom_palette_) {
1096 clipboard += absl::StrFormat("$%04X,", color.snes());
1097 }
1098 if (!clipboard.empty()) {
1099 clipboard.pop_back(); // Remove trailing comma
1100 }
1101 SetClipboardText(clipboard.c_str());
1102 }
1103 if (ImGui::IsItemHovered()) {
1104 ImGui::SetTooltip("Copy palette as comma-separated SNES values");
1105 }
1106 }
1107 card.End();
1108
1109 // Color picker popup for custom palette editing
1110 if (ImGui::BeginPopup("CustomPaletteColorEdit")) {
1111 if (edit_palette_index_ >= 0 &&
1115 "Edit Color", &custom_palette_[edit_palette_index_],
1116 kColorPopupFlags | ImGuiColorEditFlags_PickerHueWheel)) {
1117 // Color was changed, add to recently used
1119 }
1120 }
1121 ImGui::EndPopup();
1122 }
1123}
1124
1125void PaletteEditor::JumpToPalette(const std::string& group_name, int palette_index) {
1126 // Hide all cards first
1127 show_ow_main_card_ = false;
1128 show_ow_animated_card_ = false;
1130 show_sprite_card_ = false;
1134 show_equipment_card_ = false;
1135
1136 // Show and focus the appropriate card
1137 if (group_name == "ow_main") {
1138 show_ow_main_card_ = true;
1139 if (ow_main_card_) {
1140 ow_main_card_->Show();
1141 ow_main_card_->SetSelectedPaletteIndex(palette_index);
1142 }
1143 } else if (group_name == "ow_animated") {
1145 if (ow_animated_card_) {
1146 ow_animated_card_->Show();
1147 ow_animated_card_->SetSelectedPaletteIndex(palette_index);
1148 }
1149 } else if (group_name == "dungeon_main") {
1151 if (dungeon_main_card_) {
1152 dungeon_main_card_->Show();
1153 dungeon_main_card_->SetSelectedPaletteIndex(palette_index);
1154 }
1155 } else if (group_name == "global_sprites") {
1156 show_sprite_card_ = true;
1157 if (sprite_card_) {
1158 sprite_card_->Show();
1159 sprite_card_->SetSelectedPaletteIndex(palette_index);
1160 }
1161 } else if (group_name == "sprites_aux1") {
1163 if (sprites_aux1_card_) {
1164 sprites_aux1_card_->Show();
1165 sprites_aux1_card_->SetSelectedPaletteIndex(palette_index);
1166 }
1167 } else if (group_name == "sprites_aux2") {
1169 if (sprites_aux2_card_) {
1170 sprites_aux2_card_->Show();
1171 sprites_aux2_card_->SetSelectedPaletteIndex(palette_index);
1172 }
1173 } else if (group_name == "sprites_aux3") {
1175 if (sprites_aux3_card_) {
1176 sprites_aux3_card_->Show();
1177 sprites_aux3_card_->SetSelectedPaletteIndex(palette_index);
1178 }
1179 } else if (group_name == "armors") {
1180 show_equipment_card_ = true;
1181 if (equipment_card_) {
1182 equipment_card_->Show();
1183 equipment_card_->SetSelectedPaletteIndex(palette_index);
1184 }
1185 }
1186
1187 // Show control panel too for easy navigation
1188 show_control_panel_ = true;
1189}
1190
1191} // namespace editor
1192} // namespace yaze
#define F32_TO_INT8_SAT(_VAL)
project::ResourceLabelManager * resource_label()
Definition rom.h:223
auto mutable_palette_group()
Definition rom.h:217
bool is_loaded() const
Definition rom.h:200
void RegisterCard(size_t session_id, const CardInfo &base_info)
Register a card for a specific session.
EditorDependencies dependencies_
Definition editor.h:165
std::unique_ptr< SpritesAux3PaletteCard > sprites_aux3_card_
absl::Status DrawPaletteGroup(int category, bool right_side=false)
std::unique_ptr< DungeonMainPaletteCard > dungeon_main_card_
std::unique_ptr< OverworldAnimatedPaletteCard > ow_animated_card_
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::unique_ptr< SpritesAux2PaletteCard > sprites_aux2_card_
std::vector< gfx::SnesColor > recently_used_colors_
palette_internal::PaletteEditorHistory history_
std::unique_ptr< EquipmentPaletteCard > equipment_card_
absl::Status Load() override
std::unique_ptr< SpritePaletteCard > sprite_card_
void JumpToPalette(const std::string &group_name, int palette_index)
Jump to a specific palette by group and index.
absl::Status EditColorInPalette(gfx::SnesPalette &palette, int index)
std::unique_ptr< SpritesAux1PaletteCard > sprites_aux1_card_
std::unique_ptr< OverworldMainPaletteCard > ow_main_card_
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)
bool HasUnsavedChanges() const
Check if there are ANY unsaved changes.
void Initialize(Rom *rom)
Initialize the palette manager with ROM data.
void DiscardAllChanges()
Discard ALL unsaved changes.
size_t GetModifiedColorCount() const
Get count of modified colors across all groups.
static PaletteManager & Get()
Get the singleton instance.
absl::Status SaveAllToRom()
Save ALL modified palettes to ROM.
RAII timer for automatic timing management.
SNES Color container.
Definition snes_color.h:109
constexpr ImVec4 rgb() const
Get RGB values (WARNING: stored as 0-255 in ImVec4)
Definition snes_color.h:182
constexpr uint16_t snes() const
Get SNES 15-bit color.
Definition snes_color.h:192
Represents a palette of colors for the Super Nintendo Entertainment System (SNES).
Draggable, dockable card for editor sub-windows.
bool Begin(bool *p_open=nullptr)
void SetDefaultSize(float width, float height)
void SetPosition(Position pos)
#define ICON_MD_MINIMIZE
Definition icons.h:1209
#define ICON_MD_LANDSCAPE
Definition icons.h:1057
#define ICON_MD_PETS
Definition icons.h:1429
#define ICON_MD_SHIELD
Definition icons.h:1722
#define ICON_MD_BRUSH
Definition icons.h:323
#define ICON_MD_FILTER_2
Definition icons.h:750
#define ICON_MD_CASTLE
Definition icons.h:378
#define ICON_MD_FILTER_3
Definition icons.h:751
#define ICON_MD_ADD
Definition icons.h:84
#define ICON_MD_DASHBOARD
Definition icons.h:515
#define ICON_MD_FILTER_1
Definition icons.h:749
#define ICON_MD_DELETE
Definition icons.h:528
#define ICON_MD_PALETTE
Definition icons.h:1368
#define ICON_MD_CONTENT_COPY
Definition icons.h:463
#define ICON_MD_COLOR_LENS
Definition icons.h:438
#define ICON_MD_WATER
Definition icons.h:2127
#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)
Convert RGB (0-255) to SNES 15-bit color.
Definition snes_color.cc:33
std::array< float, 4 > ToFloatArray(const SnesColor &color)
ImVec4 ConvertSnesColorToImVec4(const gfx::SnesColor &color)
Convert SnesColor to standard ImVec4 for display.
Definition color.cc:19
IMGUI_API bool SnesColorEdit4(absl::string_view label, gfx::SnesColor *color, ImGuiColorEditFlags flags)
Definition color.cc:54
gfx::SnesColor ConvertImVec4ToSnesColor(const ImVec4 &color)
Convert standard ImVec4 to SnesColor.
Definition color.cc:32
Main namespace for the application.
Definition controller.cc:20
EditorCardRegistry * card_registry
Definition editor.h:80
Represents a group of palettes.
std::string CreateOrGetLabel(const std::string &type, const std::string &key, const std::string &defaultValue)
Definition project.cc:874
void SelectableLabelWithNameEdit(bool selected, const std::string &type, const std::string &key, const std::string &defaultValue)
Definition project.cc:855