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"
2
3#include "absl/status/status.h"
4#include "absl/strings/str_cat.h"
11#include "app/gui/core/color.h"
12#include "app/gui/core/icons.h"
14#include "app/gui/core/search.h"
15#include "imgui/imgui.h"
16
17namespace yaze {
18namespace editor {
19
20using ImGui::AcceptDragDropPayload;
21using ImGui::BeginChild;
22using ImGui::BeginDragDropTarget;
23using ImGui::BeginGroup;
24using ImGui::BeginPopup;
25using ImGui::BeginPopupContextItem;
26using ImGui::Button;
27using ImGui::ColorButton;
28using ImGui::ColorPicker4;
29using ImGui::EndChild;
30using ImGui::EndDragDropTarget;
31using ImGui::EndGroup;
32using ImGui::EndPopup;
33using ImGui::GetStyle;
34using ImGui::OpenPopup;
35using ImGui::PopID;
36using ImGui::PushID;
37using ImGui::SameLine;
38using ImGui::Selectable;
39using ImGui::Separator;
40using ImGui::SetClipboardText;
41using ImGui::Text;
42
43using namespace gfx;
44
45constexpr ImGuiTableFlags kPaletteTableFlags =
46 ImGuiTableFlags_Reorderable | ImGuiTableFlags_Resizable |
47 ImGuiTableFlags_SizingStretchSame | ImGuiTableFlags_Hideable;
48
49constexpr ImGuiColorEditFlags kPalNoAlpha = ImGuiColorEditFlags_NoAlpha;
50
51constexpr ImGuiColorEditFlags kPalButtonFlags = ImGuiColorEditFlags_NoAlpha |
52 ImGuiColorEditFlags_NoPicker |
53 ImGuiColorEditFlags_NoTooltip;
54
55constexpr ImGuiColorEditFlags kColorPopupFlags =
56 ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoAlpha |
57 ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_DisplayHSV |
58 ImGuiColorEditFlags_DisplayHex;
59
60namespace {
61int CustomFormatString(char* buf, size_t buf_size, const char* fmt, ...) {
62 va_list args;
63 va_start(args, fmt);
64#ifdef IMGUI_USE_STB_SPRINTF
65 int w = stbsp_vsnprintf(buf, (int)buf_size, fmt, args);
66#else
67 int w = vsnprintf(buf, buf_size, fmt, args);
68#endif
69 va_end(args);
70 if (buf == nullptr)
71 return w;
72 if (w == -1 || w >= (int)buf_size)
73 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
104absl::Status DisplayPalette(gfx::SnesPalette& palette, bool loaded) {
105 static ImVec4 color = ImVec4(0, 0, 0, 255.f);
106 static ImVec4 current_palette[256] = {};
107 ImGuiColorEditFlags misc_flags = ImGuiColorEditFlags_AlphaPreview |
108 ImGuiColorEditFlags_NoDragDrop |
109 ImGuiColorEditFlags_NoOptions;
110
111 // Generate a default palette. The palette will persist and can be edited.
112 static bool init = false;
113 if (loaded && !init) {
114 for (int n = 0; n < palette.size(); n++) {
115 auto color = palette[n];
116 current_palette[n].x = color.rgb().x / 255;
117 current_palette[n].y = color.rgb().y / 255;
118 current_palette[n].z = color.rgb().z / 255;
119 current_palette[n].w = 255; // Alpha
120 }
121 init = true;
122 }
123
124 static ImVec4 backup_color;
125 bool open_popup = ColorButton("MyColor##3b", color, misc_flags);
126 SameLine(0, GetStyle().ItemInnerSpacing.x);
127 open_popup |= Button("Palette");
128 if (open_popup) {
131 .c_str());
132 backup_color = color;
133 }
134
137 .c_str())) {
138 TEXT_WITH_SEPARATOR("Current Overworld Palette");
139 ColorPicker4("##picker", (float*)&color,
140 misc_flags | ImGuiColorEditFlags_NoSidePreview |
141 ImGuiColorEditFlags_NoSmallPreview);
142 SameLine();
143
144 BeginGroup(); // Lock X position
145 Text("Current ==>");
146 SameLine();
147 Text("Previous");
148
149 if (Button("Update Map Palette")) {}
150
151 ColorButton(
152 "##current", color,
153 ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf,
154 ImVec2(60, 40));
155 SameLine();
156
157 if (ColorButton(
158 "##previous", backup_color,
159 ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf,
160 ImVec2(60, 40)))
161 color = backup_color;
162
163 // List of Colors in Overworld Palette
164 Separator();
165 Text("Palette");
166 for (int n = 0; n < IM_ARRAYSIZE(current_palette); n++) {
167 PushID(n);
168 if ((n % 8) != 0)
169 SameLine(0.0f, GetStyle().ItemSpacing.y);
170
171 if (ColorButton("##palette", current_palette[n], kPalButtonFlags,
172 ImVec2(20, 20)))
173 color = ImVec4(current_palette[n].x, current_palette[n].y,
174 current_palette[n].z, color.w); // Preserve alpha!
175
176 if (BeginDragDropTarget()) {
177 if (const ImGuiPayload* payload =
178 AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))
179 memcpy((float*)&current_palette[n], payload->Data, sizeof(float) * 3);
180 if (const ImGuiPayload* payload =
181 AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))
182 memcpy((float*)&current_palette[n], payload->Data, sizeof(float) * 4);
183 EndDragDropTarget();
184 }
185
186 PopID();
187 }
188 EndGroup();
189 EndPopup();
190 }
191
192 return absl::OkStatus();
193}
194
196 // Register all panels with PanelManager (done once during
197 // initialization)
199 return;
200 auto* panel_manager = dependencies_.panel_manager;
201 const size_t session_id = dependencies_.session_id;
202
203 panel_manager->RegisterPanel({.card_id = "palette.control_panel",
204 .display_name = "Palette Controls",
205 .window_title = " Group Manager",
206 .icon = ICON_MD_PALETTE,
207 .category = "Palette",
208 .shortcut_hint = "Ctrl+Shift+P",
209 .visibility_flag = &show_control_panel_,
210 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
211 .disabled_tooltip = "Load a ROM first",
212 .priority = 10});
213
214 panel_manager->RegisterPanel({.card_id = "palette.ow_main",
215 .display_name = "Overworld Main",
216 .window_title = " Overworld Main",
217 .icon = ICON_MD_LANDSCAPE,
218 .category = "Palette",
219 .shortcut_hint = "Ctrl+Alt+1",
220 .visibility_flag = &show_ow_main_panel_,
221 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
222 .disabled_tooltip = "Load a ROM first",
223 .priority = 20});
224
225 panel_manager->RegisterPanel({.card_id = "palette.ow_animated",
226 .display_name = "Overworld Animated",
227 .window_title = " Overworld Animated",
228 .icon = ICON_MD_WATER,
229 .category = "Palette",
230 .shortcut_hint = "Ctrl+Alt+2",
231 .visibility_flag = &show_ow_animated_panel_,
232 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
233 .disabled_tooltip = "Load a ROM first",
234 .priority = 30});
235
236 panel_manager->RegisterPanel({.card_id = "palette.dungeon_main",
237 .display_name = "Dungeon Main",
238 .window_title = " Dungeon Main",
239 .icon = ICON_MD_CASTLE,
240 .category = "Palette",
241 .shortcut_hint = "Ctrl+Alt+3",
242 .visibility_flag = &show_dungeon_main_panel_,
243 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
244 .disabled_tooltip = "Load a ROM first",
245 .priority = 40});
246
247 panel_manager->RegisterPanel({.card_id = "palette.sprites",
248 .display_name = "Global Sprite Palettes",
249 .window_title = " SNES Palette",
250 .icon = ICON_MD_PETS,
251 .category = "Palette",
252 .shortcut_hint = "Ctrl+Alt+4",
253 .visibility_flag = &show_sprite_panel_,
254 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
255 .disabled_tooltip = "Load a ROM first",
256 .priority = 50});
257
258 panel_manager->RegisterPanel({.card_id = "palette.sprites_aux1",
259 .display_name = "Sprites Aux 1",
260 .window_title = " Sprites Aux 1",
261 .icon = ICON_MD_FILTER_1,
262 .category = "Palette",
263 .shortcut_hint = "Ctrl+Alt+7",
264 .visibility_flag = &show_sprites_aux1_panel_,
265 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
266 .disabled_tooltip = "Load a ROM first",
267 .priority = 51});
268
269 panel_manager->RegisterPanel({.card_id = "palette.sprites_aux2",
270 .display_name = "Sprites Aux 2",
271 .window_title = " Sprites Aux 2",
272 .icon = ICON_MD_FILTER_2,
273 .category = "Palette",
274 .shortcut_hint = "Ctrl+Alt+8",
275 .visibility_flag = &show_sprites_aux2_panel_,
276 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
277 .disabled_tooltip = "Load a ROM first",
278 .priority = 52});
279
280 panel_manager->RegisterPanel({.card_id = "palette.sprites_aux3",
281 .display_name = "Sprites Aux 3",
282 .window_title = " Sprites Aux 3",
283 .icon = ICON_MD_FILTER_3,
284 .category = "Palette",
285 .shortcut_hint = "Ctrl+Alt+9",
286 .visibility_flag = &show_sprites_aux3_panel_,
287 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
288 .disabled_tooltip = "Load a ROM first",
289 .priority = 53});
290
291 panel_manager->RegisterPanel({.card_id = "palette.equipment",
292 .display_name = "Equipment Palettes",
293 .window_title = " Equipment Palettes",
294 .icon = ICON_MD_SHIELD,
295 .category = "Palette",
296 .shortcut_hint = "Ctrl+Alt+5",
297 .visibility_flag = &show_equipment_panel_,
298 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
299 .disabled_tooltip = "Load a ROM first",
300 .priority = 60});
301
302 panel_manager->RegisterPanel({.card_id = "palette.quick_access",
303 .display_name = "Quick Access",
304 .window_title = " Color Harmony",
305 .icon = ICON_MD_COLOR_LENS,
306 .category = "Palette",
307 .shortcut_hint = "Ctrl+Alt+Q",
308 .visibility_flag = &show_quick_access_,
309 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
310 .disabled_tooltip = "Load a ROM first",
311 .priority = 70});
312
313 panel_manager->RegisterPanel({.card_id = "palette.custom",
314 .display_name = "Custom Palette",
315 .window_title = " Palette Editor",
316 .icon = ICON_MD_BRUSH,
317 .category = "Palette",
318 .shortcut_hint = "Ctrl+Alt+C",
319 .visibility_flag = &show_custom_palette_,
320 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
321 .disabled_tooltip = "Load a ROM first",
322 .priority = 80});
323
324 // Show control panel by default when Palette Editor is activated
325 panel_manager->ShowPanel(session_id, "palette.control_panel");
326}
327
328// ============================================================================
329// Helper Panel Classes
330// ============================================================================
331
333 public:
334 explicit PaletteControlPanel(std::function<void()> draw_callback)
335 : draw_callback_(std::move(draw_callback)) {}
336
337 std::string GetId() const override { return "palette.control_panel"; }
338 std::string GetDisplayName() const override { return "Palette Controls"; }
339 std::string GetIcon() const override { return ICON_MD_PALETTE; }
340 std::string GetEditorCategory() const override { return "Palette"; }
341 int GetPriority() const override { return 10; }
342
343 void Draw(bool* p_open) override {
344 if (p_open && !*p_open) return;
346 }
347
348 private:
349 std::function<void()> draw_callback_;
350};
351
353 public:
354 explicit QuickAccessPalettePanel(std::function<void()> draw_callback)
355 : draw_callback_(std::move(draw_callback)) {}
356
357 std::string GetId() const override { return "palette.quick_access"; }
358 std::string GetDisplayName() const override { return "Quick Access"; }
359 std::string GetIcon() const override { return ICON_MD_COLOR_LENS; }
360 std::string GetEditorCategory() const override { return "Palette"; }
361 int GetPriority() const override { return 70; }
362
363 void Draw(bool* p_open) override {
364 if (p_open && !*p_open) return;
366 }
367
368 private:
369 std::function<void()> draw_callback_;
370};
371
373 public:
374 explicit CustomPalettePanel(std::function<void()> draw_callback)
375 : draw_callback_(std::move(draw_callback)) {}
376
377 std::string GetId() const override { return "palette.custom"; }
378 std::string GetDisplayName() const override { return "Custom Palette"; }
379 std::string GetIcon() const override { return ICON_MD_BRUSH; }
380 std::string GetEditorCategory() const override { return "Palette"; }
381 int GetPriority() const override { return 80; }
382
383 void Draw(bool* p_open) override {
384 if (p_open && !*p_open) return;
386 }
387
388 private:
389 std::function<void()> draw_callback_;
390};
391
392absl::Status PaletteEditor::Load() {
393 gfx::ScopedTimer timer("PaletteEditor::Load");
394
395 if (!rom() || !rom()->is_loaded()) {
396 return absl::NotFoundError("ROM not open, no palettes to display");
397 }
398
399 // Initialize the labels
400 for (int i = 0; i < kNumPalettes; i++) {
402 "Palette Group Name", std::to_string(i),
403 std::string(kPaletteGroupNames[i]));
404 }
405
406 // Initialize the centralized PaletteManager with GameData
407 // This must be done before creating any palette cards
408 if (game_data()) {
410 } else {
411 // Fallback to legacy ROM-only initialization
413 }
414
415 // Also set up the embedded GfxGroupEditor
418
419 // Register EditorPanel instances with PanelManager
421 auto* panel_manager = dependencies_.panel_manager;
422
423 // Create and register palette panels
424 // Note: PanelManager takes ownership via unique_ptr
425
426 // Overworld Main
427 auto ow_main = std::make_unique<OverworldMainPalettePanel>(rom_, game_data());
428 ow_main_panel_ = ow_main.get();
429 panel_manager->RegisterEditorPanel(std::move(ow_main));
430
431 // Overworld Animated
432 auto ow_anim = std::make_unique<OverworldAnimatedPalettePanel>(rom_, game_data());
433 ow_anim_panel_ = ow_anim.get();
434 panel_manager->RegisterEditorPanel(std::move(ow_anim));
435
436 // Dungeon Main
437 auto dungeon_main = std::make_unique<DungeonMainPalettePanel>(rom_, game_data());
438 dungeon_main_panel_ = dungeon_main.get();
439 panel_manager->RegisterEditorPanel(std::move(dungeon_main));
440
441 // Global Sprites
442 auto sprite_global = std::make_unique<SpritePalettePanel>(rom_, game_data());
443 sprite_global_panel_ = sprite_global.get();
444 panel_manager->RegisterEditorPanel(std::move(sprite_global));
445
446 // Sprites Aux 1
447 auto sprite_aux1 = std::make_unique<SpritesAux1PalettePanel>(rom_, game_data());
448 sprite_aux1_panel_ = sprite_aux1.get();
449 panel_manager->RegisterEditorPanel(std::move(sprite_aux1));
450
451 // Sprites Aux 2
452 auto sprite_aux2 = std::make_unique<SpritesAux2PalettePanel>(rom_, game_data());
453 sprite_aux2_panel_ = sprite_aux2.get();
454 panel_manager->RegisterEditorPanel(std::move(sprite_aux2));
455
456 // Sprites Aux 3
457 auto sprite_aux3 = std::make_unique<SpritesAux3PalettePanel>(rom_, game_data());
458 sprite_aux3_panel_ = sprite_aux3.get();
459 panel_manager->RegisterEditorPanel(std::move(sprite_aux3));
460
461 // Equipment
462 auto equipment = std::make_unique<EquipmentPalettePanel>(rom_, game_data());
463 equipment_panel_ = equipment.get();
464 panel_manager->RegisterEditorPanel(std::move(equipment));
465
466 // Register utility panels with callbacks
467 panel_manager->RegisterEditorPanel(std::make_unique<PaletteControlPanel>(
468 [this]() { DrawControlPanel(); }));
469 panel_manager->RegisterEditorPanel(std::make_unique<QuickAccessPalettePanel>(
470 [this]() { DrawQuickAccessPanel(); }));
471 panel_manager->RegisterEditorPanel(std::make_unique<CustomPalettePanel>(
472 [this]() { DrawCustomPalettePanel(); }));
473 }
474
475 return absl::OkStatus();
476}
477
478absl::Status PaletteEditor::Save() {
479 if (!rom_ || !rom_->is_loaded()) {
480 return absl::FailedPreconditionError("ROM not loaded");
481 }
482
483 // Delegate to PaletteManager for centralized save
485
486 // Mark ROM as needing file save
487 rom_->set_dirty(true);
488
489 return absl::OkStatus();
490}
491
492absl::Status PaletteEditor::Undo() {
493 if (!gfx::PaletteManager::Get().IsInitialized()) {
494 return absl::FailedPreconditionError("PaletteManager not initialized");
495 }
496
498 return absl::OkStatus();
499}
500
501absl::Status PaletteEditor::Redo() {
502 if (!gfx::PaletteManager::Get().IsInitialized()) {
503 return absl::FailedPreconditionError("PaletteManager not initialized");
504 }
505
507 return absl::OkStatus();
508}
509
510absl::Status PaletteEditor::Update() {
511 // Panel drawing is handled centrally by PanelManager::DrawAllVisiblePanels()
512 // via the EditorPanel implementations registered in Load().
513 // No local drawing needed here - this fixes duplicate panel rendering.
514 return absl::OkStatus();
515}
516
518 BeginChild("QuickAccessPalettes", ImVec2(0, 0), true);
519
520 Text("Custom Palette");
522
523 Separator();
524
525 // Current color picker with more options
526 BeginGroup();
527 Text("Current Color");
528 gui::SnesColorEdit4("##CurrentColorPicker", &current_color_,
530
531 char buf[64];
532 auto col = current_color_.rgb();
533 int cr = F32_TO_INT8_SAT(col.x / 255.0f);
534 int cg = F32_TO_INT8_SAT(col.y / 255.0f);
535 int cb = F32_TO_INT8_SAT(col.z / 255.0f);
536
537 CustomFormatString(buf, IM_ARRAYSIZE(buf), "RGB: %d, %d, %d", cr, cg, cb);
538 Text("%s", buf);
539
540 CustomFormatString(buf, IM_ARRAYSIZE(buf), "SNES: $%04X",
542 Text("%s", buf);
543
544 if (Button("Copy to Clipboard")) {
545 SetClipboardText(buf);
546 }
547 EndGroup();
548
549 Separator();
550
551 // Recently used colors
552 Text("Recently Used Colors");
553 for (int i = 0; i < recently_used_colors_.size(); i++) {
554 PushID(i);
555 if (i % 8 != 0)
556 SameLine();
557 ImVec4 displayColor =
559 if (ImGui::ColorButton("##recent", displayColor)) {
560 // Set as current color
562 }
563 PopID();
564 }
565
566 EndChild();
567}
568
585 if (BeginChild("ColorPalette", ImVec2(0, 40), ImGuiChildFlags_None,
586 ImGuiWindowFlags_HorizontalScrollbar)) {
587 for (int i = 0; i < custom_palette_.size(); i++) {
588 PushID(i);
589 if (i > 0)
590 SameLine(0.0f, GetStyle().ItemSpacing.y);
591
592 // Enhanced color button with context menu and drag-drop support
593 ImVec4 displayColor = gui::ConvertSnesColorToImVec4(custom_palette_[i]);
594 bool open_color_picker = ImGui::ColorButton(
595 absl::StrFormat("##customPal%d", i).c_str(), displayColor);
596
597 if (open_color_picker) {
600 ImGui::OpenPopup(
601 gui::MakePopupId(gui::EditorNames::kPalette, "CustomPaletteColorEdit")
602 .c_str());
603 }
604
605 if (BeginPopupContextItem()) {
606 // Edit color directly in the popup
607 SnesColor original_color = custom_palette_[i];
608 if (gui::SnesColorEdit4("Edit Color", &custom_palette_[i],
610 // Color was changed, add to recently used
612 }
613
614 if (Button("Delete", ImVec2(-1, 0))) {
615 custom_palette_.erase(custom_palette_.begin() + i);
616 }
617 }
618
619 // Handle drag/drop for palette rearrangement
620 if (BeginDragDropTarget()) {
621 if (const ImGuiPayload* payload =
622 AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F)) {
623 ImVec4 color;
624 memcpy((float*)&color, payload->Data, sizeof(float) * 3);
625 color.w = 1.0f; // Set alpha to 1.0
626 custom_palette_[i] = SnesColor(color);
628 }
629 EndDragDropTarget();
630 }
631
632 PopID();
633 }
634
635 SameLine();
636 if (ImGui::Button("+")) {
637 custom_palette_.push_back(SnesColor(0x7FFF));
638 }
639
640 SameLine();
641 if (ImGui::Button("Clear")) {
642 custom_palette_.clear();
643 }
644
645 SameLine();
646 if (ImGui::Button("Export")) {
647 std::string clipboard;
648 for (const auto& color : custom_palette_) {
649 clipboard += absl::StrFormat("$%04X,", color.snes());
650 }
651 SetClipboardText(clipboard.c_str());
652 }
653 }
654 EndChild();
655
656 // Color picker popup for custom palette editing
657 if (ImGui::BeginPopup(
658 gui::MakePopupId(gui::EditorNames::kPalette, "CustomPaletteColorEdit")
659 .c_str())) {
660 if (edit_palette_index_ >= 0 &&
664 "Edit Color", &custom_palette_[edit_palette_index_],
665 kColorPopupFlags | ImGuiColorEditFlags_PickerHueWheel)) {
666 // Color was changed, add to recently used
668 }
669 }
670 ImGui::EndPopup();
671 }
672}
673
674absl::Status PaletteEditor::DrawPaletteGroup(int category,
675 bool /*right_side*/) {
676 if (!rom()->is_loaded() || !game_data()) {
677 return absl::NotFoundError("ROM not open, no palettes to display");
678 }
679
680 auto palette_group_name = kPaletteGroupNames[category];
681 gfx::PaletteGroup* palette_group =
682 game_data()->palette_groups.get_group(palette_group_name.data());
683 const auto size = palette_group->size();
684
685 for (int j = 0; j < size; j++) {
686 gfx::SnesPalette* palette = palette_group->mutable_palette(j);
687 auto pal_size = palette->size();
688
689 BeginGroup();
690
691 PushID(j);
692 BeginGroup();
694 false, palette_group_name.data(), /*key=*/std::to_string(j),
695 "Unnamed Palette");
696 EndGroup();
697
698 for (int n = 0; n < pal_size; n++) {
699 PushID(n);
700 if (n > 0 && n % 8 != 0)
701 SameLine(0.0f, 2.0f);
702
703 auto popup_id =
704 absl::StrCat(kPaletteCategoryNames[category].data(), j, "_", n);
705
706 ImVec4 displayColor = gui::ConvertSnesColorToImVec4((*palette)[n]);
707 if (ImGui::ColorButton(popup_id.c_str(), displayColor)) {
708 current_color_ = (*palette)[n];
710 }
711
712 if (BeginPopupContextItem(popup_id.c_str())) {
713 RETURN_IF_ERROR(HandleColorPopup(*palette, category, j, n))
714 }
715 PopID();
716 }
717 PopID();
718 EndGroup();
719
720 if (j < size - 1) {
721 Separator();
722 }
723 }
724 return absl::OkStatus();
725}
726
728 // Check if color already exists in recently used
729 auto it = std::find_if(
731 [&color](const SnesColor& c) { return c.snes() == color.snes(); });
732
733 // If found, remove it to re-add at front
734 if (it != recently_used_colors_.end()) {
735 recently_used_colors_.erase(it);
736 }
737
738 // Add at front
739 recently_used_colors_.insert(recently_used_colors_.begin(), color);
740
741 // Limit size
742 if (recently_used_colors_.size() > 16) {
743 recently_used_colors_.pop_back();
744 }
745}
746
748 int j, int n) {
749 auto col = gfx::ToFloatArray(palette[n]);
750 auto original_color = palette[n];
751
752 if (gui::SnesColorEdit4("Edit Color", &palette[n], kColorPopupFlags)) {
753 history_.RecordChange(/*group_name=*/std::string(kPaletteGroupNames[i]),
754 /*palette_index=*/j, /*color_index=*/n,
755 original_color, palette[n]);
756 palette[n].set_modified(true);
757
758 // Add to recently used colors
759 AddRecentlyUsedColor(palette[n]);
760 }
761
762 // Color information display
763 char buf[64];
764 int cr = F32_TO_INT8_SAT(col[0]);
765 int cg = F32_TO_INT8_SAT(col[1]);
766 int cb = F32_TO_INT8_SAT(col[2]);
767
768 Text("RGB: %d, %d, %d", cr, cg, cb);
769 Text("SNES: $%04X", palette[n].snes());
770
771 Separator();
772
773 if (Button("Copy as..", ImVec2(-1, 0)))
776 .c_str());
779 .c_str())) {
780 CustomFormatString(buf, IM_ARRAYSIZE(buf), "(%.3ff, %.3ff, %.3ff)", col[0],
781 col[1], col[2]);
782 if (Selectable(buf))
783 SetClipboardText(buf);
784
785 CustomFormatString(buf, IM_ARRAYSIZE(buf), "(%d,%d,%d)", cr, cg, cb);
786 if (Selectable(buf))
787 SetClipboardText(buf);
788
789 CustomFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", cr, cg, cb);
790 if (Selectable(buf))
791 SetClipboardText(buf);
792
793 // SNES Format
794 CustomFormatString(buf, IM_ARRAYSIZE(buf), "$%04X",
795 ConvertRgbToSnes(ImVec4(col[0], col[1], col[2], 1.0f)));
796 if (Selectable(buf))
797 SetClipboardText(buf);
798
799 EndPopup();
800 }
801
802 // Add a button to add this color to custom palette
803 if (Button("Add to Custom Palette", ImVec2(-1, 0))) {
804 custom_palette_.push_back(palette[n]);
805 }
806
807 EndPopup();
808 return absl::OkStatus();
809}
810
812 int index) {
813 if (index >= palette.size()) {
814 return absl::InvalidArgumentError("Index out of bounds");
815 }
816
817 // Get the current color
818 auto color = palette[index];
819 auto currentColor = color.rgb();
820 if (ColorPicker4("Color Picker", (float*)&palette[index])) {
821 // The color was modified, update it in the palette
822 palette[index] = gui::ConvertImVec4ToSnesColor(currentColor);
823
824 // Add to recently used colors
825 AddRecentlyUsedColor(palette[index]);
826 }
827 return absl::OkStatus();
828}
829
831 gfx::SnesPalette& palette, int index,
832 const gfx::SnesPalette& originalPalette) {
833 if (index >= palette.size() || index >= originalPalette.size()) {
834 return absl::InvalidArgumentError("Index out of bounds");
835 }
836 auto color = originalPalette[index];
837 auto originalColor = color.rgb();
838 palette[index] = gui::ConvertImVec4ToSnesColor(originalColor);
839 return absl::OkStatus();
840}
841
842// ============================================================================
843// Panel-Based UI Methods
844// ============================================================================
845
847 // Sidebar is drawn by PanelManager in EditorManager
848 // Panels registered in Initialize() appear in the sidebar automatically
849}
850
852 ImGui::SetNextWindowSize(ImVec2(320, 420), ImGuiCond_FirstUseEver);
853 ImGui::SetNextWindowPos(ImVec2(10, 100), ImGuiCond_FirstUseEver);
854
855 ImGuiWindowFlags flags = ImGuiWindowFlags_None;
856
857 if (ImGui::Begin(ICON_MD_PALETTE " Palette Controls", &show_control_panel_,
858 flags)) {
859 // Toolbar with quick toggles
860 DrawToolset();
861
862 ImGui::Separator();
863
864 // Categorized palette list with search
866
867 ImGui::Separator();
868
869 // Modified status indicator
870 ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f), "Modified Panels:");
871 bool any_modified = false;
872
874 ImGui::BulletText("Overworld Main");
875 any_modified = true;
876 }
878 ImGui::BulletText("Overworld Animated");
879 any_modified = true;
880 }
882 ImGui::BulletText("Dungeon Main");
883 any_modified = true;
884 }
886 ImGui::BulletText("Global Sprite Palettes");
887 any_modified = true;
888 }
890 ImGui::BulletText("Sprites Aux 1");
891 any_modified = true;
892 }
894 ImGui::BulletText("Sprites Aux 2");
895 any_modified = true;
896 }
898 ImGui::BulletText("Sprites Aux 3");
899 any_modified = true;
900 }
902 ImGui::BulletText("Equipment Palettes");
903 any_modified = true;
904 }
905
906 if (!any_modified) {
907 ImGui::TextDisabled("No unsaved changes");
908 }
909
910 ImGui::Separator();
911
912 // Quick actions
913 ImGui::Text("Quick Actions:");
914
915 // Use centralized PaletteManager for global operations
916 bool has_unsaved = gfx::PaletteManager::Get().HasUnsavedChanges();
917 size_t modified_count = gfx::PaletteManager::Get().GetModifiedColorCount();
918
919 ImGui::BeginDisabled(!has_unsaved);
920 if (ImGui::Button(
921 absl::StrFormat(ICON_MD_SAVE " Save All (%zu colors)",
922 modified_count)
923 .c_str(),
924 ImVec2(-1, 0))) {
925 auto status = gfx::PaletteManager::Get().SaveAllToRom();
926 if (!status.ok()) {
927 // TODO: Show error toast/notification
928 ImGui::OpenPopup(
931 .c_str());
932 }
933 }
934 ImGui::EndDisabled();
935
936 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
937 if (has_unsaved) {
938 ImGui::SetTooltip("Save all modified colors to ROM");
939 } else {
940 ImGui::SetTooltip("No unsaved changes");
941 }
942 }
943
944 // Apply to Editors button - preview changes without saving to ROM
945 ImGui::BeginDisabled(!has_unsaved);
946 if (ImGui::Button(ICON_MD_VISIBILITY " Apply to Editors", ImVec2(-1, 0))) {
948 if (!status.ok()) {
949 ImGui::OpenPopup(
952 .c_str());
953 }
954 }
955 ImGui::EndDisabled();
956
957 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
958 if (has_unsaved) {
959 ImGui::SetTooltip(
960 "Preview palette changes in other editors without saving to ROM");
961 } else {
962 ImGui::SetTooltip("No changes to preview");
963 }
964 }
965
966 ImGui::BeginDisabled(!has_unsaved);
967 if (ImGui::Button(ICON_MD_UNDO " Discard All Changes", ImVec2(-1, 0))) {
968 ImGui::OpenPopup(
971 .c_str());
972 }
973 ImGui::EndDisabled();
974
975 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
976 if (has_unsaved) {
977 ImGui::SetTooltip("Discard all unsaved changes");
978 } else {
979 ImGui::SetTooltip("No changes to discard");
980 }
981 }
982
983 // Confirmation popup for discard
984 if (ImGui::BeginPopupModal(
987 .c_str(),
988 nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
989 ImGui::Text("Discard all unsaved changes?");
990 ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f),
991 "This will revert %zu modified colors.",
992 modified_count);
993 ImGui::Separator();
994
995 if (ImGui::Button("Discard", ImVec2(120, 0))) {
997 ImGui::CloseCurrentPopup();
998 }
999 ImGui::SameLine();
1000 if (ImGui::Button("Cancel", ImVec2(120, 0))) {
1001 ImGui::CloseCurrentPopup();
1002 }
1003 ImGui::EndPopup();
1004 }
1005
1006 // Error popup for save failures
1007 if (ImGui::BeginPopupModal(
1010 .c_str(),
1011 nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
1012 ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f),
1013 "Failed to save changes");
1014 ImGui::Text("An error occurred while saving to ROM.");
1015 ImGui::Separator();
1016
1017 if (ImGui::Button("OK", ImVec2(120, 0))) {
1018 ImGui::CloseCurrentPopup();
1019 }
1020 ImGui::EndPopup();
1021 }
1022
1023 ImGui::Separator();
1024
1025 // Editor Manager Menu Button
1026 if (ImGui::Button(ICON_MD_DASHBOARD " Panel Manager", ImVec2(-1, 0))) {
1027 ImGui::OpenPopup(
1030 .c_str());
1031 }
1032
1033 if (ImGui::BeginPopup(
1036 .c_str())) {
1037 ImGui::TextColored(ImVec4(0.7f, 0.9f, 1.0f, 1.0f),
1038 "%s Palette Panel Manager", ICON_MD_PALETTE);
1039 ImGui::Separator();
1040
1041 // View menu section now handled by PanelManager in EditorManager
1043 return;
1044 auto* panel_manager = dependencies_.panel_manager;
1045
1046 ImGui::EndPopup();
1047 }
1048
1049 ImGui::Separator();
1050
1051 // Minimize button
1052 if (ImGui::SmallButton(ICON_MD_MINIMIZE " Minimize to Icon")) {
1054 show_control_panel_ = false;
1055 }
1056 }
1057 ImGui::End();
1058}
1059
1061 gui::PanelWindow card("Quick Access Palette", ICON_MD_COLOR_LENS,
1063 card.SetDefaultSize(340, 300);
1065
1066 if (card.Begin(&show_quick_access_)) {
1067 // Current color picker with more options
1068 ImGui::BeginGroup();
1069 ImGui::Text("Current Color");
1070 gui::SnesColorEdit4("##CurrentColorPicker", &current_color_,
1072
1073 char buf[64];
1074 auto col = current_color_.rgb();
1075 int cr = F32_TO_INT8_SAT(col.x / 255.0f);
1076 int cg = F32_TO_INT8_SAT(col.y / 255.0f);
1077 int cb = F32_TO_INT8_SAT(col.z / 255.0f);
1078
1079 CustomFormatString(buf, IM_ARRAYSIZE(buf), "RGB: %d, %d, %d", cr, cg, cb);
1080 ImGui::Text("%s", buf);
1081
1082 CustomFormatString(buf, IM_ARRAYSIZE(buf), "SNES: $%04X",
1084 ImGui::Text("%s", buf);
1085
1086 if (ImGui::Button("Copy to Clipboard", ImVec2(-1, 0))) {
1087 SetClipboardText(buf);
1088 }
1089 ImGui::EndGroup();
1090
1091 ImGui::Separator();
1092
1093 // Recently used colors
1094 ImGui::Text("Recently Used Colors");
1095 if (recently_used_colors_.empty()) {
1096 ImGui::TextDisabled("No recently used colors yet");
1097 } else {
1098 for (int i = 0; i < recently_used_colors_.size(); i++) {
1099 PushID(i);
1100 if (i % 8 != 0)
1101 SameLine();
1102 ImVec4 displayColor =
1104 if (ImGui::ColorButton("##recent", displayColor, kPalButtonFlags,
1105 ImVec2(28, 28))) {
1106 // Set as current color
1108 }
1109 if (ImGui::IsItemHovered()) {
1110 ImGui::SetTooltip("SNES: $%04X", recently_used_colors_[i].snes());
1111 }
1112 PopID();
1113 }
1114 }
1115 }
1116 card.End();
1117}
1118
1120 gui::PanelWindow card("Custom Palette", ICON_MD_BRUSH, &show_custom_palette_);
1121 card.SetDefaultSize(420, 200);
1123
1124 if (card.Begin(&show_custom_palette_)) {
1125 ImGui::TextWrapped(
1126 "Create your own custom color palette for reference. "
1127 "Colors can be added from any palette group or created from scratch.");
1128
1129 ImGui::Separator();
1130
1131 // Custom palette color grid
1132 if (custom_palette_.empty()) {
1133 ImGui::TextDisabled("Your custom palette is empty.");
1134 ImGui::Text("Click + to add colors or drag colors from any palette.");
1135 } else {
1136 for (int i = 0; i < custom_palette_.size(); i++) {
1137 PushID(i);
1138 if (i > 0 && i % 16 != 0)
1139 SameLine(0.0f, 2.0f);
1140
1141 // Enhanced color button with context menu and drag-drop support
1142 ImVec4 displayColor = gui::ConvertSnesColorToImVec4(custom_palette_[i]);
1143 bool open_color_picker =
1144 ImGui::ColorButton(absl::StrFormat("##customPal%d", i).c_str(),
1145 displayColor, kPalButtonFlags, ImVec2(28, 28));
1146
1147 if (open_color_picker) {
1150 ImGui::OpenPopup(
1152 "PanelCustomPaletteColorEdit")
1153 .c_str());
1154 }
1155
1156 if (BeginPopupContextItem()) {
1157 // Edit color directly in the popup
1158 SnesColor original_color = custom_palette_[i];
1159 if (gui::SnesColorEdit4("Edit Color", &custom_palette_[i],
1161 // Color was changed, add to recently used
1163 }
1164
1165 if (ImGui::Button("Delete", ImVec2(-1, 0))) {
1166 custom_palette_.erase(custom_palette_.begin() + i);
1167 ImGui::CloseCurrentPopup();
1168 }
1169 ImGui::EndPopup();
1170 }
1171
1172 // Handle drag/drop for palette rearrangement
1173 if (BeginDragDropTarget()) {
1174 if (const ImGuiPayload* payload =
1175 AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F)) {
1176 ImVec4 color;
1177 memcpy((float*)&color, payload->Data, sizeof(float) * 3);
1178 color.w = 1.0f; // Set alpha to 1.0
1179 custom_palette_[i] = SnesColor(color);
1181 }
1182 EndDragDropTarget();
1183 }
1184
1185 PopID();
1186 }
1187 }
1188
1189 ImGui::Separator();
1190
1191 // Buttons for palette management
1192 if (ImGui::Button(ICON_MD_ADD " Add Color")) {
1193 custom_palette_.push_back(SnesColor(0x7FFF));
1194 }
1195
1196 ImGui::SameLine();
1197 if (ImGui::Button(ICON_MD_DELETE " Clear All")) {
1198 custom_palette_.clear();
1199 }
1200
1201 ImGui::SameLine();
1202 if (ImGui::Button(ICON_MD_CONTENT_COPY " Export")) {
1203 std::string clipboard;
1204 for (const auto& color : custom_palette_) {
1205 clipboard += absl::StrFormat("$%04X,", color.snes());
1206 }
1207 if (!clipboard.empty()) {
1208 clipboard.pop_back(); // Remove trailing comma
1209 }
1210 SetClipboardText(clipboard.c_str());
1211 }
1212 if (ImGui::IsItemHovered()) {
1213 ImGui::SetTooltip("Copy palette as comma-separated SNES values");
1214 }
1215 }
1216 card.End();
1217
1218 // Color picker popup for custom palette editing
1219 if (ImGui::BeginPopup(gui::MakePopupId(gui::EditorNames::kPalette,
1220 "PanelCustomPaletteColorEdit")
1221 .c_str())) {
1222 if (edit_palette_index_ >= 0 &&
1226 "Edit Color", &custom_palette_[edit_palette_index_],
1227 kColorPopupFlags | ImGuiColorEditFlags_PickerHueWheel)) {
1228 // Color was changed, add to recently used
1230 }
1231 }
1232 ImGui::EndPopup();
1233 }
1234}
1235
1236void PaletteEditor::JumpToPalette(const std::string& group_name,
1237 int palette_index) {
1239 return;
1240 }
1241 auto* panel_manager = dependencies_.panel_manager;
1242 const size_t session_id = dependencies_.session_id;
1243
1244 // Show and focus the appropriate card
1245 if (group_name == "ow_main") {
1246 panel_manager->ShowPanel(session_id, "palette.ow_main");
1247 if (ow_main_panel_) {
1250 }
1251 } else if (group_name == "ow_animated") {
1252 panel_manager->ShowPanel(session_id, "palette.ow_animated");
1253 if (ow_anim_panel_) {
1256 }
1257 } else if (group_name == "dungeon_main") {
1258 panel_manager->ShowPanel(session_id, "palette.dungeon_main");
1259 if (dungeon_main_panel_) {
1262 }
1263 } else if (group_name == "global_sprites") {
1264 panel_manager->ShowPanel(session_id, "palette.sprites");
1268 }
1269 } else if (group_name == "sprites_aux1") {
1270 panel_manager->ShowPanel(session_id, "palette.sprites_aux1");
1271 if (sprite_aux1_panel_) {
1274 }
1275 } else if (group_name == "sprites_aux2") {
1276 panel_manager->ShowPanel(session_id, "palette.sprites_aux2");
1277 if (sprite_aux2_panel_) {
1280 }
1281 } else if (group_name == "sprites_aux3") {
1282 panel_manager->ShowPanel(session_id, "palette.sprites_aux3");
1283 if (sprite_aux3_panel_) {
1286 }
1287 } else if (group_name == "armors") {
1288 panel_manager->ShowPanel(session_id, "palette.equipment");
1289 if (equipment_panel_) {
1292 }
1293 }
1294
1295 // Show control panel too for easy navigation
1296 panel_manager->ShowPanel(session_id, "palette.control_panel");
1297}
1298
1299// ============================================================================
1300// Category and Search UI Methods
1301// ============================================================================
1302
1304 ImGui::SetNextItemWidth(-1);
1305 if (ImGui::InputTextWithHint("##PaletteSearch",
1306 ICON_MD_SEARCH " Search palettes...",
1307 search_buffer_, sizeof(search_buffer_))) {
1308 // Search text changed - UI will update automatically
1309 }
1310}
1311
1312bool PaletteEditor::PassesSearchFilter(const std::string& group_name) const {
1313 if (search_buffer_[0] == '\0') return true;
1314
1315 // Check if group name or display name matches
1316 return gui::FuzzyMatch(search_buffer_, group_name) ||
1318}
1319
1320bool* PaletteEditor::GetShowFlagForGroup(const std::string& group_name) {
1321 if (group_name == "ow_main") return &show_ow_main_panel_;
1322 if (group_name == "ow_animated") return &show_ow_animated_panel_;
1323 if (group_name == "dungeon_main") return &show_dungeon_main_panel_;
1324 if (group_name == "global_sprites") return &show_sprite_panel_;
1325 if (group_name == "sprites_aux1") return &show_sprites_aux1_panel_;
1326 if (group_name == "sprites_aux2") return &show_sprites_aux2_panel_;
1327 if (group_name == "sprites_aux3") return &show_sprites_aux3_panel_;
1328 if (group_name == "armors") return &show_equipment_panel_;
1329 return nullptr;
1330}
1331
1333 // Search bar at top
1334 DrawSearchBar();
1335 ImGui::Separator();
1336
1337 const auto& categories = GetPaletteCategories();
1338
1339 for (size_t cat_idx = 0; cat_idx < categories.size(); cat_idx++) {
1340 const auto& cat = categories[cat_idx];
1341
1342 // Check if any items in category match search
1343 bool has_visible_items = false;
1344 for (const auto& group_name : cat.group_names) {
1345 if (PassesSearchFilter(group_name)) {
1346 has_visible_items = true;
1347 break;
1348 }
1349 }
1350
1351 if (!has_visible_items) continue;
1352
1353 ImGui::PushID(static_cast<int>(cat_idx));
1354
1355 // Collapsible header for category with icon
1356 std::string header_text =
1357 absl::StrFormat("%s %s", cat.icon, cat.display_name);
1358 bool open = ImGui::CollapsingHeader(header_text.c_str(),
1359 ImGuiTreeNodeFlags_DefaultOpen);
1360
1361 if (open) {
1362 ImGui::Indent(10.0f);
1363 for (const auto& group_name : cat.group_names) {
1364 if (!PassesSearchFilter(group_name)) continue;
1365
1366 bool* show_flag = GetShowFlagForGroup(group_name);
1367 if (show_flag) {
1368 std::string label = GetGroupDisplayName(group_name);
1369
1370 // Show modified indicator
1371 if (gfx::PaletteManager::Get().IsGroupModified(group_name)) {
1372 label += " *";
1373 ImGui::PushStyleColor(ImGuiCol_Text,
1374 ImVec4(1.0f, 0.6f, 0.0f, 1.0f));
1375 }
1376
1377 ImGui::Checkbox(label.c_str(), show_flag);
1378
1379 if (gfx::PaletteManager::Get().IsGroupModified(group_name)) {
1380 ImGui::PopStyleColor();
1381 }
1382 }
1383 }
1384 ImGui::Unindent(10.0f);
1385 }
1386 ImGui::PopID();
1387 }
1388
1389 ImGui::Separator();
1390
1391 // Utilities section
1392 ImGui::Text("Utilities:");
1393 ImGui::Indent(10.0f);
1394 ImGui::Checkbox("Quick Access", &show_quick_access_);
1395 ImGui::Checkbox("Custom Palette", &show_custom_palette_);
1396 ImGui::Unindent(10.0f);
1397}
1398
1399} // namespace editor
1400} // namespace yaze
#define F32_TO_INT8_SAT(_VAL)
project::ResourceLabelManager * resource_label()
Definition rom.h:146
void set_dirty(bool dirty)
Definition rom.h:130
bool is_loaded() const
Definition rom.h:128
std::function< void()> draw_callback_
std::string GetId() const override
Unique identifier for this panel.
std::string GetDisplayName() const override
Human-readable name shown in menus and title bars.
void Draw(bool *p_open) override
Draw the panel content.
CustomPalettePanel(std::function< void()> draw_callback)
std::string GetEditorCategory() const override
Editor category this panel belongs to.
int GetPriority() const override
Get display priority for menu ordering.
std::string GetIcon() const override
Material Design icon for this panel.
Base interface for all logical panel components.
zelda3::GameData * game_data() const
Definition editor.h:228
EditorDependencies dependencies_
Definition editor.h:237
void SetGameData(zelda3::GameData *data)
std::string GetIcon() const override
Material Design icon for this panel.
std::function< void()> draw_callback_
std::string GetId() const override
Unique identifier for this panel.
void Draw(bool *p_open) override
Draw the panel content.
int GetPriority() const override
Get display priority for menu ordering.
std::string GetEditorCategory() const override
Editor category this panel belongs to.
PaletteControlPanel(std::function< void()> draw_callback)
std::string GetDisplayName() const override
Human-readable name shown in menus and title bars.
absl::Status DrawPaletteGroup(int category, bool right_side=false)
bool * GetShowFlagForGroup(const std::string &group_name)
OverworldAnimatedPalettePanel * ow_anim_panel_
absl::Status ResetColorToOriginal(gfx::SnesPalette &palette, int index, const gfx::SnesPalette &originalPalette)
std::vector< gfx::SnesColor > custom_palette_
SpritesAux1PalettePanel * sprite_aux1_panel_
void DrawCustomPalette()
Draw custom palette editor with enhanced ROM hacking features.
void AddRecentlyUsedColor(const gfx::SnesColor &color)
absl::Status Update() override
bool PassesSearchFilter(const std::string &group_name) const
absl::Status Undo() override
EquipmentPalettePanel * equipment_panel_
SpritePalettePanel * sprite_global_panel_
absl::Status HandleColorPopup(gfx::SnesPalette &palette, int i, int j, int n)
SpritesAux2PalettePanel * sprite_aux2_panel_
std::vector< gfx::SnesColor > recently_used_colors_
palette_internal::PaletteEditorHistory history_
absl::Status Save() override
absl::Status Load() override
void JumpToPalette(const std::string &group_name, int palette_index)
Jump to a specific palette by group and index.
DungeonMainPalettePanel * dungeon_main_panel_
SpritesAux3PalettePanel * sprite_aux3_panel_
OverworldMainPalettePanel * ow_main_panel_
absl::Status Redo() override
absl::Status EditColorInPalette(gfx::SnesPalette &palette, int index)
QuickAccessPalettePanel(std::function< void()> draw_callback)
std::string GetId() const override
Unique identifier for this panel.
void Draw(bool *p_open) override
Draw the panel content.
std::string GetIcon() const override
Material Design icon for this panel.
std::string GetEditorCategory() const override
Editor category this panel belongs to.
int GetPriority() const override
Get display priority for menu ordering.
std::string GetDisplayName() const override
Human-readable name shown in menus and title bars.
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 Undo()
Undo the most recent change.
void Initialize(zelda3::GameData *game_data)
Initialize the palette manager with GameData.
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.
absl::Status ApplyPreviewChanges()
Apply preview changes to other editors without saving to ROM.
void Redo()
Redo the most recently undone change.
RAII timer for automatic timing management.
SNES Color container.
Definition snes_color.h:110
constexpr ImVec4 rgb() const
Get RGB values (WARNING: stored as 0-255 in ImVec4)
Definition snes_color.h:183
constexpr uint16_t snes() const
Get SNES 15-bit color.
Definition snes_color.h:193
Represents a palette of colors for the Super Nintendo Entertainment System (SNES).
Draggable, dockable panel for editor sub-windows.
void SetPosition(Position pos)
bool Begin(bool *p_open=nullptr)
void SetDefaultSize(float width, float height)
#define ICON_MD_MINIMIZE
Definition icons.h:1211
#define ICON_MD_LANDSCAPE
Definition icons.h:1059
#define ICON_MD_PETS
Definition icons.h:1431
#define ICON_MD_SHIELD
Definition icons.h:1724
#define ICON_MD_SEARCH
Definition icons.h:1673
#define ICON_MD_BRUSH
Definition icons.h:325
#define ICON_MD_FILTER_2
Definition icons.h:752
#define ICON_MD_VISIBILITY
Definition icons.h:2101
#define ICON_MD_CASTLE
Definition icons.h:380
#define ICON_MD_FILTER_3
Definition icons.h:753
#define ICON_MD_ADD
Definition icons.h:86
#define ICON_MD_DASHBOARD
Definition icons.h:517
#define ICON_MD_SAVE
Definition icons.h:1644
#define ICON_MD_FILTER_1
Definition icons.h:751
#define ICON_MD_DELETE
Definition icons.h:530
#define ICON_MD_PALETTE
Definition icons.h:1370
#define ICON_MD_CONTENT_COPY
Definition icons.h:465
#define ICON_MD_COLOR_LENS
Definition icons.h:440
#define ICON_MD_WATER
Definition icons.h:2129
#define ICON_MD_UNDO
Definition icons.h:2039
#define TEXT_WITH_SEPARATOR(text)
Definition macro.h:90
int CustomFormatString(char *buf, size_t buf_size, const char *fmt,...)
constexpr ImGuiTableFlags kPaletteTableFlags
constexpr ImGuiColorEditFlags kPalNoAlpha
const std::vector< PaletteCategoryInfo > & GetPaletteCategories()
Get all palette categories with their associated groups.
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
std::string GetGroupDisplayName(const std::string &group_name)
Get display name for a palette group.
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)
constexpr const char * kPalette
Definition popup_id.h:56
constexpr const char * kSaveError
Definition popup_id.h:85
constexpr const char * kConfirmDiscardAll
Definition popup_id.h:86
constexpr const char * kPalettePanelManager
Definition popup_id.h:87
constexpr const char * kCopyPopup
Definition popup_id.h:84
constexpr const char * kColorPicker
Definition popup_id.h:83
bool FuzzyMatch(const std::string &pattern, const std::string &str)
Simple fuzzy match - returns true if all chars in pattern appear in str in order.
Definition search.h:23
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
std::string MakePopupId(size_t session_id, const std::string &editor_name, const std::string &popup_name)
Generate session-aware popup IDs to prevent conflicts in multi-editor layouts.
Definition popup_id.h:23
gfx::SnesColor ConvertImVec4ToSnesColor(const ImVec4 &color)
Convert standard ImVec4 to SnesColor.
Definition color.cc:32
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
PaletteGroup * get_group(const std::string &group_name)
Represents a group of palettes.
std::string CreateOrGetLabel(const std::string &type, const std::string &key, const std::string &defaultValue)
Definition project.cc:1319
void SelectableLabelWithNameEdit(bool selected, const std::string &type, const std::string &key, const std::string &defaultValue)
Definition project.cc:1294
gfx::PaletteGroupMap palette_groups
Definition game_data.h:89