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