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 <algorithm>
4
5#include "absl/status/status.h"
6#include "absl/strings/str_cat.h"
7#include "absl/strings/str_format.h"
15#include "app/gui/core/color.h"
16#include "app/gui/core/icons.h"
18#include "app/gui/core/search.h"
21#include "imgui/imgui.h"
22
23namespace yaze {
24namespace editor {
25
26using ImGui::AcceptDragDropPayload;
27using ImGui::BeginChild;
28using ImGui::BeginDragDropTarget;
29using ImGui::BeginGroup;
30using ImGui::BeginPopup;
31using ImGui::BeginPopupContextItem;
32using ImGui::Button;
33using ImGui::ColorButton;
34using ImGui::ColorPicker4;
35using ImGui::EndChild;
36using ImGui::EndDragDropTarget;
37using ImGui::EndGroup;
38using ImGui::EndPopup;
39using ImGui::GetStyle;
40using ImGui::OpenPopup;
41using ImGui::PopID;
42using ImGui::PushID;
43using ImGui::SameLine;
44using ImGui::Selectable;
45using ImGui::Separator;
46using ImGui::SetClipboardText;
47using ImGui::Text;
48
49using namespace gfx;
50
51constexpr ImGuiTableFlags kPaletteTableFlags =
52 ImGuiTableFlags_Reorderable | ImGuiTableFlags_Resizable |
53 ImGuiTableFlags_SizingStretchSame | ImGuiTableFlags_Hideable;
54
55constexpr ImGuiColorEditFlags kPalNoAlpha = ImGuiColorEditFlags_NoAlpha;
56
57constexpr ImGuiColorEditFlags kPalButtonFlags = ImGuiColorEditFlags_NoAlpha |
58 ImGuiColorEditFlags_NoPicker |
59 ImGuiColorEditFlags_NoTooltip;
60
61constexpr ImGuiColorEditFlags kColorPopupFlags =
62 ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoAlpha |
63 ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_DisplayHSV |
64 ImGuiColorEditFlags_DisplayHex;
65
66namespace {
67int CustomFormatString(char* buf, size_t buf_size, const char* fmt, ...) {
68 va_list args;
69 va_start(args, fmt);
70#ifdef IMGUI_USE_STB_SPRINTF
71 int w = stbsp_vsnprintf(buf, (int)buf_size, fmt, args);
72#else
73 int w = vsnprintf(buf, buf_size, fmt, args);
74#endif
75 va_end(args);
76 if (buf == nullptr)
77 return w;
78 if (w == -1 || w >= (int)buf_size)
79 w = (int)buf_size - 1;
80 buf[w] = 0;
81 return w;
82}
83
84static inline float color_saturate(float f) {
85 return (f < 0.0f) ? 0.0f : (f > 1.0f) ? 1.0f : f;
86}
87
88#define F32_TO_INT8_SAT(_VAL) \
89 ((int)(color_saturate(_VAL) * 255.0f + \
90 0.5f)) // Saturated, always output 0..255
91} // namespace
92
110absl::Status DisplayPalette(gfx::SnesPalette& palette, bool loaded) {
111 static ImVec4 color = ImVec4(0, 0, 0, 1.0f);
112 static ImVec4 current_palette[256] = {};
113 static int current_palette_count = 0;
114 ImGuiColorEditFlags misc_flags = ImGuiColorEditFlags_AlphaPreview |
115 ImGuiColorEditFlags_NoDragDrop |
116 ImGuiColorEditFlags_NoOptions;
117
118 // Reload palette colors whenever the palette data is available.
119 if (loaded) {
120 current_palette_count =
121 std::min<int>(static_cast<int>(palette.size()),
122 static_cast<int>(IM_ARRAYSIZE(current_palette)));
123
124 for (int n = 0; n < current_palette_count; ++n) {
125 const auto palette_color = palette[static_cast<size_t>(n)];
126 current_palette[n].x = palette_color.rgb().x / 255.0f;
127 current_palette[n].y = palette_color.rgb().y / 255.0f;
128 current_palette[n].z = palette_color.rgb().z / 255.0f;
129 current_palette[n].w = 1.0f;
130 }
131 for (int n = current_palette_count;
132 n < static_cast<int>(IM_ARRAYSIZE(current_palette)); ++n) {
133 current_palette[n] = ImVec4(0, 0, 0, 1.0f);
134 }
135 } else {
136 current_palette_count = 0;
137 }
138
139 static ImVec4 backup_color;
140 bool open_popup = ColorButton("MyColor##3b", color, misc_flags);
141 SameLine(0, GetStyle().ItemInnerSpacing.x);
142 open_popup |= Button("Palette");
143 if (open_popup) {
146 .c_str());
147 backup_color = color;
148 }
149
152 .c_str())) {
153 TEXT_WITH_SEPARATOR("Current Overworld Palette");
154 ColorPicker4("##picker", (float*)&color,
155 misc_flags | ImGuiColorEditFlags_NoSidePreview |
156 ImGuiColorEditFlags_NoSmallPreview);
157 SameLine();
158
159 BeginGroup(); // Lock X position
160 Text("Current ==>");
161 SameLine();
162 Text("Previous");
163
164 if (Button("Update Map Palette")) {}
165
166 ColorButton(
167 "##current", color,
168 ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf,
169 ImVec2(60, 40));
170 SameLine();
171
172 if (ColorButton(
173 "##previous", backup_color,
174 ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf,
175 ImVec2(60, 40)))
176 color = backup_color;
177
178 // List of Colors in Overworld Palette
179 Separator();
180 Text("Palette");
181 if (current_palette_count <= 0) {
182 ImGui::TextDisabled("No palette entries loaded.");
183 }
184 for (int n = 0; n < current_palette_count; n++) {
185 PushID(n);
186 if ((n % 8) != 0)
187 SameLine(0.0f, GetStyle().ItemSpacing.y);
188
189 if (ColorButton("##palette", current_palette[n], kPalButtonFlags,
190 ImVec2(20, 20)))
191 color = ImVec4(current_palette[n].x, current_palette[n].y,
192 current_palette[n].z, color.w); // Preserve alpha!
193
194 if (BeginDragDropTarget()) {
195 if (const ImGuiPayload* payload =
196 AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))
197 memcpy((float*)&current_palette[n], payload->Data, sizeof(float) * 3);
198 if (const ImGuiPayload* payload =
199 AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))
200 memcpy((float*)&current_palette[n], payload->Data, sizeof(float) * 4);
201 EndDragDropTarget();
202 }
203
204 PopID();
205 }
206 EndGroup();
207 EndPopup();
208 }
209
210 return absl::OkStatus();
211}
212
214 // Register all panels with PanelManager (done once during
215 // initialization)
217 return;
218 auto* panel_manager = dependencies_.panel_manager;
219 const size_t session_id = dependencies_.session_id;
220
221 panel_manager->RegisterPanel(
222 {.card_id = "palette.control_panel",
223 .display_name = "Palette Controls",
224 .window_title = " Palette Controls",
225 .icon = ICON_MD_PALETTE,
226 .category = "Palette",
227 .shortcut_hint = "Ctrl+Shift+P",
228 .visibility_flag = &show_control_panel_,
229 .priority = 10,
230 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
231 .disabled_tooltip = "Load a ROM first"});
232
233 panel_manager->RegisterPanel(
234 {.card_id = "palette.ow_main",
235 .display_name = "Overworld Main",
236 .window_title = " Overworld Main",
237 .icon = ICON_MD_LANDSCAPE,
238 .category = "Palette",
239 .shortcut_hint = "Ctrl+Alt+1",
240 .visibility_flag = &show_ow_main_panel_,
241 .priority = 20,
242 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
243 .disabled_tooltip = "Load a ROM first"});
244
245 panel_manager->RegisterPanel(
246 {.card_id = "palette.ow_animated",
247 .display_name = "Overworld Animated",
248 .window_title = " Overworld Animated",
249 .icon = ICON_MD_WATER,
250 .category = "Palette",
251 .shortcut_hint = "Ctrl+Alt+2",
252 .visibility_flag = &show_ow_animated_panel_,
253 .priority = 30,
254 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
255 .disabled_tooltip = "Load a ROM first"});
256
257 panel_manager->RegisterPanel(
258 {.card_id = "palette.dungeon_main",
259 .display_name = "Dungeon Main",
260 .window_title = " Dungeon Main",
261 .icon = ICON_MD_CASTLE,
262 .category = "Palette",
263 .shortcut_hint = "Ctrl+Alt+3",
264 .visibility_flag = &show_dungeon_main_panel_,
265 .priority = 40,
266 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
267 .disabled_tooltip = "Load a ROM first"});
268
269 panel_manager->RegisterPanel(
270 {.card_id = "palette.sprites",
271 .display_name = "Global Sprite Palettes",
272 .window_title = " SNES Palette",
273 .icon = ICON_MD_PETS,
274 .category = "Palette",
275 .shortcut_hint = "Ctrl+Alt+4",
276 .visibility_flag = &show_sprite_panel_,
277 .priority = 50,
278 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
279 .disabled_tooltip = "Load a ROM first"});
280
281 panel_manager->RegisterPanel(
282 {.card_id = "palette.sprites_aux1",
283 .display_name = "Sprites Aux 1",
284 .window_title = " Sprites Aux 1",
285 .icon = ICON_MD_FILTER_1,
286 .category = "Palette",
287 .shortcut_hint = "Ctrl+Alt+7",
288 .visibility_flag = &show_sprites_aux1_panel_,
289 .priority = 51,
290 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
291 .disabled_tooltip = "Load a ROM first"});
292
293 panel_manager->RegisterPanel(
294 {.card_id = "palette.sprites_aux2",
295 .display_name = "Sprites Aux 2",
296 .window_title = " Sprites Aux 2",
297 .icon = ICON_MD_FILTER_2,
298 .category = "Palette",
299 .shortcut_hint = "Ctrl+Alt+8",
300 .visibility_flag = &show_sprites_aux2_panel_,
301 .priority = 52,
302 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
303 .disabled_tooltip = "Load a ROM first"});
304
305 panel_manager->RegisterPanel(
306 {.card_id = "palette.sprites_aux3",
307 .display_name = "Sprites Aux 3",
308 .window_title = " Sprites Aux 3",
309 .icon = ICON_MD_FILTER_3,
310 .category = "Palette",
311 .shortcut_hint = "Ctrl+Alt+9",
312 .visibility_flag = &show_sprites_aux3_panel_,
313 .priority = 53,
314 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
315 .disabled_tooltip = "Load a ROM first"});
316
317 panel_manager->RegisterPanel(
318 {.card_id = "palette.equipment",
319 .display_name = "Equipment Palettes",
320 .window_title = " Equipment Palettes",
321 .icon = ICON_MD_SHIELD,
322 .category = "Palette",
323 .shortcut_hint = "Ctrl+Alt+5",
324 .visibility_flag = &show_equipment_panel_,
325 .priority = 60,
326 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
327 .disabled_tooltip = "Load a ROM first"});
328
329 panel_manager->RegisterPanel(
330 {.card_id = "palette.quick_access",
331 .display_name = "Quick Access",
332 .window_title = " Color Harmony",
333 .icon = ICON_MD_COLOR_LENS,
334 .category = "Palette",
335 .shortcut_hint = "Ctrl+Alt+Q",
336 .visibility_flag = &show_quick_access_,
337 .priority = 70,
338 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
339 .disabled_tooltip = "Load a ROM first"});
340
341 panel_manager->RegisterPanel(
342 {.card_id = "palette.custom",
343 .display_name = "Custom Palette",
344 .window_title = " Palette Editor",
345 .icon = ICON_MD_BRUSH,
346 .category = "Palette",
347 .shortcut_hint = "Ctrl+Alt+C",
348 .visibility_flag = &show_custom_palette_,
349 .priority = 80,
350 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
351 .disabled_tooltip = "Load a ROM first"});
352
353 // Show control panel by default when Palette Editor is activated
354 panel_manager->ShowPanel(session_id, "palette.control_panel");
355}
356
357// ============================================================================
358// Helper Panel Classes
359// ============================================================================
360
362 public:
363 explicit PaletteControlPanel(std::function<void()> draw_callback)
364 : draw_callback_(std::move(draw_callback)) {}
365
366 std::string GetId() const override { return "palette.control_panel"; }
367 std::string GetDisplayName() const override { return "Palette Controls"; }
368 std::string GetIcon() const override { return ICON_MD_PALETTE; }
369 std::string GetEditorCategory() const override { return "Palette"; }
370 int GetPriority() const override { return 10; }
371 float GetPreferredWidth() const override { return 320.0f; }
372
373 void Draw(bool* p_open) override {
374 if (p_open && !*p_open)
375 return;
376 if (draw_callback_)
378 }
379
380 private:
381 std::function<void()> draw_callback_;
382};
383
385 public:
386 explicit QuickAccessPalettePanel(std::function<void()> draw_callback)
387 : draw_callback_(std::move(draw_callback)) {}
388
389 std::string GetId() const override { return "palette.quick_access"; }
390 std::string GetDisplayName() const override { return "Quick Access"; }
391 std::string GetIcon() const override { return ICON_MD_COLOR_LENS; }
392 std::string GetEditorCategory() const override { return "Palette"; }
393 int GetPriority() const override { return 70; }
394 float GetPreferredWidth() const override { return 340.0f; }
395
396 void Draw(bool* p_open) override {
397 if (p_open && !*p_open)
398 return;
399 if (draw_callback_)
401 }
402
403 private:
404 std::function<void()> draw_callback_;
405};
406
408 public:
409 explicit CustomPalettePanel(std::function<void()> draw_callback)
410 : draw_callback_(std::move(draw_callback)) {}
411
412 std::string GetId() const override { return "palette.custom"; }
413 std::string GetDisplayName() const override { return "Custom Palette"; }
414 std::string GetIcon() const override { return ICON_MD_BRUSH; }
415 std::string GetEditorCategory() const override { return "Palette"; }
416 int GetPriority() const override { return 80; }
417 float GetPreferredWidth() const override { return 420.0f; }
418
419 void Draw(bool* p_open) override {
420 if (p_open && !*p_open)
421 return;
422 if (draw_callback_)
424 }
425
426 private:
427 std::function<void()> draw_callback_;
428};
429
430absl::Status PaletteEditor::Load() {
431 gfx::ScopedTimer timer("PaletteEditor::Load");
432
433 if (!rom() || !rom()->is_loaded()) {
434 return absl::NotFoundError("ROM not open, no palettes to display");
435 }
436
437 // Initialize the labels
438 for (int i = 0; i < kNumPalettes; i++) {
440 "Palette Group Name", std::to_string(i),
441 std::string(kPaletteGroupNames[i]));
442 }
443
444 // Initialize the centralized PaletteManager with GameData
445 // This must be done before creating any palette cards
446 if (game_data()) {
448 } else {
449 // Fallback to legacy ROM-only initialization
451 }
452
453 // Also set up the embedded GfxGroupEditor
456
457 // Register EditorPanel instances with PanelManager
459 auto* panel_manager = dependencies_.panel_manager;
460
461 // Create and register palette panels
462 // Note: PanelManager takes ownership via unique_ptr
463
464 // Overworld Main
465 auto ow_main =
466 std::make_unique<OverworldMainPalettePanel>(rom_, game_data());
467 ow_main_panel_ = ow_main.get();
468 panel_manager->RegisterEditorPanel(std::move(ow_main));
469
470 // Overworld Animated
471 auto ow_anim =
472 std::make_unique<OverworldAnimatedPalettePanel>(rom_, game_data());
473 ow_anim_panel_ = ow_anim.get();
474 panel_manager->RegisterEditorPanel(std::move(ow_anim));
475
476 // Dungeon Main
477 auto dungeon_main =
478 std::make_unique<DungeonMainPalettePanel>(rom_, game_data());
479 dungeon_main_panel_ = dungeon_main.get();
480 panel_manager->RegisterEditorPanel(std::move(dungeon_main));
481
482 // Global Sprites
483 auto sprite_global =
484 std::make_unique<SpritePalettePanel>(rom_, game_data());
485 sprite_global_panel_ = sprite_global.get();
486 panel_manager->RegisterEditorPanel(std::move(sprite_global));
487
488 // Sprites Aux 1
489 auto sprite_aux1 =
490 std::make_unique<SpritesAux1PalettePanel>(rom_, game_data());
491 sprite_aux1_panel_ = sprite_aux1.get();
492 panel_manager->RegisterEditorPanel(std::move(sprite_aux1));
493
494 // Sprites Aux 2
495 auto sprite_aux2 =
496 std::make_unique<SpritesAux2PalettePanel>(rom_, game_data());
497 sprite_aux2_panel_ = sprite_aux2.get();
498 panel_manager->RegisterEditorPanel(std::move(sprite_aux2));
499
500 // Sprites Aux 3
501 auto sprite_aux3 =
502 std::make_unique<SpritesAux3PalettePanel>(rom_, game_data());
503 sprite_aux3_panel_ = sprite_aux3.get();
504 panel_manager->RegisterEditorPanel(std::move(sprite_aux3));
505
506 // Equipment
507 auto equipment = std::make_unique<EquipmentPalettePanel>(rom_, game_data());
508 equipment_panel_ = equipment.get();
509 panel_manager->RegisterEditorPanel(std::move(equipment));
510
511 // Wire toast manager to all palette group panels
512 auto* toast = dependencies_.toast_manager;
513 if (toast) {
522 }
523
524 // Register utility panels with callbacks
525 panel_manager->RegisterEditorPanel(std::make_unique<PaletteControlPanel>(
526 [this]() { DrawControlPanel(); }));
527 panel_manager->RegisterEditorPanel(
528 std::make_unique<QuickAccessPalettePanel>(
529 [this]() { DrawQuickAccessPanel(); }));
530 panel_manager->RegisterEditorPanel(std::make_unique<CustomPalettePanel>(
531 [this]() { DrawCustomPalettePanel(); }));
532 }
533
534 return absl::OkStatus();
535}
536
537absl::Status PaletteEditor::Save() {
538 if (!rom_ || !rom_->is_loaded()) {
539 return absl::FailedPreconditionError("ROM not loaded");
540 }
541
542 // Delegate to PaletteManager for centralized save
544
545 // Mark ROM as needing file save
546 rom_->set_dirty(true);
547
548 return absl::OkStatus();
549}
550
551absl::Status PaletteEditor::Undo() {
552 if (!gfx::PaletteManager::Get().IsInitialized()) {
553 return absl::FailedPreconditionError("PaletteManager not initialized");
554 }
555
557 return absl::OkStatus();
558}
559
560absl::Status PaletteEditor::Redo() {
561 if (!gfx::PaletteManager::Get().IsInitialized()) {
562 return absl::FailedPreconditionError("PaletteManager not initialized");
563 }
564
566 return absl::OkStatus();
567}
568
569absl::Status PaletteEditor::Update() {
570 // Panel drawing is handled centrally by PanelManager::DrawAllVisiblePanels()
571 // via the EditorPanel implementations registered in Load().
572 // No local drawing needed here - this fixes duplicate panel rendering.
573 return absl::OkStatus();
574}
575
577 BeginChild("QuickAccessPalettes", ImVec2(0, 0), true);
578
579 Text("Custom Palette");
581
582 Separator();
583
584 // Current color picker with more options
585 BeginGroup();
586 Text("Current Color");
587 gui::SnesColorEdit4("##CurrentColorPicker", &current_color_,
589
590 char buf[64];
591 auto col = current_color_.rgb();
592 int cr = F32_TO_INT8_SAT(col.x / 255.0f);
593 int cg = F32_TO_INT8_SAT(col.y / 255.0f);
594 int cb = F32_TO_INT8_SAT(col.z / 255.0f);
595
596 CustomFormatString(buf, IM_ARRAYSIZE(buf), "RGB: %d, %d, %d", cr, cg, cb);
597 Text("%s", buf);
598
599 CustomFormatString(buf, IM_ARRAYSIZE(buf), "SNES: $%04X",
601 Text("%s", buf);
602
603 if (Button("Copy to Clipboard")) {
604 SetClipboardText(buf);
605 }
606 EndGroup();
607
608 Separator();
609
610 // Recently used colors
611 Text("Recently Used Colors");
612 for (int i = 0; i < recently_used_colors_.size(); i++) {
613 PushID(i);
614 if (i % 8 != 0)
615 SameLine();
616 ImVec4 displayColor =
618 if (ImGui::ColorButton("##recent", displayColor)) {
619 // Set as current color
621 }
622 PopID();
623 }
624
625 EndChild();
626}
627
644 if (BeginChild("ColorPalette", ImVec2(0, 40), ImGuiChildFlags_None,
645 ImGuiWindowFlags_HorizontalScrollbar)) {
646 for (int i = 0; i < custom_palette_.size(); i++) {
647 PushID(i);
648 if (i > 0)
649 SameLine(0.0f, GetStyle().ItemSpacing.y);
650
651 // Enhanced color button with context menu and drag-drop support
652 ImVec4 displayColor = gui::ConvertSnesColorToImVec4(custom_palette_[i]);
653 bool open_color_picker = ImGui::ColorButton(
654 absl::StrFormat("##customPal%d", i).c_str(), displayColor);
655
656 if (open_color_picker) {
660 "CustomPaletteColorEdit")
661 .c_str());
662 }
663
664 if (BeginPopupContextItem()) {
665 // Edit color directly in the popup
666 SnesColor original_color = custom_palette_[i];
667 if (gui::SnesColorEdit4("Edit Color", &custom_palette_[i],
669 // Color was changed, add to recently used
671 }
672
673 if (Button("Delete", ImVec2(-1, 0))) {
674 custom_palette_.erase(custom_palette_.begin() + i);
675 }
676 }
677
678 // Handle drag/drop for palette rearrangement
679 if (BeginDragDropTarget()) {
680 if (const ImGuiPayload* payload =
681 AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F)) {
682 ImVec4 color;
683 memcpy((float*)&color, payload->Data, sizeof(float) * 3);
684 color.w = 1.0f; // Set alpha to 1.0
685 custom_palette_[i] = SnesColor(color);
687 }
688 EndDragDropTarget();
689 }
690
691 PopID();
692 }
693
694 SameLine();
695 if (ImGui::Button("+")) {
696 custom_palette_.push_back(SnesColor(0x7FFF));
697 }
698
699 SameLine();
700 if (ImGui::Button("Clear")) {
701 custom_palette_.clear();
702 }
703
704 SameLine();
705 if (ImGui::Button("Export")) {
706 std::string clipboard;
707 for (const auto& color : custom_palette_) {
708 clipboard += absl::StrFormat("$%04X,", color.snes());
709 }
710 SetClipboardText(clipboard.c_str());
711 }
712 }
713 EndChild();
714
715 // Color picker popup for custom palette editing
716 if (ImGui::BeginPopup(
717 gui::MakePopupId(gui::EditorNames::kPalette, "CustomPaletteColorEdit")
718 .c_str())) {
719 if (edit_palette_index_ >= 0 &&
723 "Edit Color", &custom_palette_[edit_palette_index_],
724 kColorPopupFlags | ImGuiColorEditFlags_PickerHueWheel)) {
725 // Color was changed, add to recently used
727 }
728 }
729 ImGui::EndPopup();
730 }
731}
732
733absl::Status PaletteEditor::DrawPaletteGroup(int category,
734 bool /*right_side*/) {
735 if (!rom()->is_loaded() || !game_data()) {
736 return absl::NotFoundError("ROM not open, no palettes to display");
737 }
738
739 auto palette_group_name = kPaletteGroupNames[category];
740 gfx::PaletteGroup* palette_group =
741 game_data()->palette_groups.get_group(palette_group_name.data());
742 const auto size = palette_group->size();
743
744 for (int j = 0; j < size; j++) {
745 gfx::SnesPalette* palette = palette_group->mutable_palette(j);
746 auto pal_size = palette->size();
747
748 BeginGroup();
749
750 PushID(j);
751 BeginGroup();
753 false, palette_group_name.data(), /*key=*/std::to_string(j),
754 "Unnamed Palette");
755 EndGroup();
756
757 for (int n = 0; n < pal_size; n++) {
758 PushID(n);
759 if (n > 0 && n % 8 != 0)
760 SameLine(0.0f, 2.0f);
761
762 auto popup_id =
763 absl::StrCat(kPaletteCategoryNames[category].data(), j, "_", n);
764
765 ImVec4 displayColor = gui::ConvertSnesColorToImVec4((*palette)[n]);
766 if (ImGui::ColorButton(popup_id.c_str(), displayColor)) {
767 current_color_ = (*palette)[n];
769 }
770
771 if (BeginPopupContextItem(popup_id.c_str())) {
772 RETURN_IF_ERROR(HandleColorPopup(*palette, category, j, n))
773 }
774 PopID();
775 }
776 PopID();
777 EndGroup();
778
779 if (j < size - 1) {
780 Separator();
781 }
782 }
783 return absl::OkStatus();
784}
785
787 // Check if color already exists in recently used
788 auto it = std::find_if(
790 [&color](const SnesColor& c) { return c.snes() == color.snes(); });
791
792 // If found, remove it to re-add at front
793 if (it != recently_used_colors_.end()) {
794 recently_used_colors_.erase(it);
795 }
796
797 // Add at front
798 recently_used_colors_.insert(recently_used_colors_.begin(), color);
799
800 // Limit size
801 if (recently_used_colors_.size() > 16) {
802 recently_used_colors_.pop_back();
803 }
804}
805
807 int j, int n) {
808 auto col = gfx::ToFloatArray(palette[n]);
809 auto original_color = palette[n];
810
811 if (gui::SnesColorEdit4("Edit Color", &palette[n], kColorPopupFlags)) {
812 history_.RecordChange(/*group_name=*/std::string(kPaletteGroupNames[i]),
813 /*palette_index=*/j, /*color_index=*/n,
814 original_color, palette[n]);
815 palette[n].set_modified(true);
816
817 // Add to recently used colors
818 AddRecentlyUsedColor(palette[n]);
819 }
820
821 // Color information display
822 char buf[64];
823 int cr = F32_TO_INT8_SAT(col[0]);
824 int cg = F32_TO_INT8_SAT(col[1]);
825 int cb = F32_TO_INT8_SAT(col[2]);
826
827 Text("RGB: %d, %d, %d", cr, cg, cb);
828 Text("SNES: $%04X", palette[n].snes());
829
830 Separator();
831
832 if (Button("Copy as..", ImVec2(-1, 0)))
835 .c_str());
838 .c_str())) {
839 CustomFormatString(buf, IM_ARRAYSIZE(buf), "(%.3ff, %.3ff, %.3ff)", col[0],
840 col[1], col[2]);
841 if (Selectable(buf))
842 SetClipboardText(buf);
843
844 CustomFormatString(buf, IM_ARRAYSIZE(buf), "(%d,%d,%d)", cr, cg, cb);
845 if (Selectable(buf))
846 SetClipboardText(buf);
847
848 CustomFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", cr, cg, cb);
849 if (Selectable(buf))
850 SetClipboardText(buf);
851
852 // SNES Format
853 CustomFormatString(buf, IM_ARRAYSIZE(buf), "$%04X",
854 ConvertRgbToSnes(ImVec4(col[0], col[1], col[2], 1.0f)));
855 if (Selectable(buf))
856 SetClipboardText(buf);
857
858 EndPopup();
859 }
860
861 // Add a button to add this color to custom palette
862 if (Button("Add to Custom Palette", ImVec2(-1, 0))) {
863 custom_palette_.push_back(palette[n]);
864 }
865
866 EndPopup();
867 return absl::OkStatus();
868}
869
871 int index) {
872 if (index >= palette.size()) {
873 return absl::InvalidArgumentError("Index out of bounds");
874 }
875
876 // Get the current color
877 auto color = palette[index];
878 auto currentColor = color.rgb();
879 if (ColorPicker4("Color Picker", (float*)&palette[index])) {
880 // The color was modified, update it in the palette
881 palette[index] = gui::ConvertImVec4ToSnesColor(currentColor);
882
883 // Add to recently used colors
884 AddRecentlyUsedColor(palette[index]);
885 }
886 return absl::OkStatus();
887}
888
890 gfx::SnesPalette& palette, int index,
891 const gfx::SnesPalette& originalPalette) {
892 if (index >= palette.size() || index >= originalPalette.size()) {
893 return absl::InvalidArgumentError("Index out of bounds");
894 }
895 auto color = originalPalette[index];
896 auto originalColor = color.rgb();
897 palette[index] = gui::ConvertImVec4ToSnesColor(originalColor);
898 return absl::OkStatus();
899}
900
901// ============================================================================
902// Panel-Based UI Methods
903// ============================================================================
904
906 // Sidebar is drawn by PanelManager in EditorManager
907 // Panels registered in Initialize() appear in the sidebar automatically
908}
909
911 // Toolbar with quick toggles
912 DrawToolset();
913
914 ImGui::Separator();
915
916 // Categorized palette list with search
918
919 ImGui::Separator();
920
921 // Modified status indicator
922 ImGui::TextColored(gui::GetWarningColor(), "Modified Panels:");
923 bool any_modified = false;
924
926 ImGui::BulletText("Overworld Main");
927 any_modified = true;
928 }
930 ImGui::BulletText("Overworld Animated");
931 any_modified = true;
932 }
934 ImGui::BulletText("Dungeon Main");
935 any_modified = true;
936 }
938 ImGui::BulletText("Global Sprite Palettes");
939 any_modified = true;
940 }
942 ImGui::BulletText("Sprites Aux 1");
943 any_modified = true;
944 }
946 ImGui::BulletText("Sprites Aux 2");
947 any_modified = true;
948 }
950 ImGui::BulletText("Sprites Aux 3");
951 any_modified = true;
952 }
954 ImGui::BulletText("Equipment Palettes");
955 any_modified = true;
956 }
957
958 if (!any_modified) {
959 ImGui::TextDisabled("No unsaved changes");
960 }
961
962 ImGui::Separator();
963
964 // Quick actions
965 ImGui::Text("Quick Actions:");
966
967 // Use centralized PaletteManager for global operations
968 bool has_unsaved = gfx::PaletteManager::Get().HasUnsavedChanges();
969 size_t modified_count = gfx::PaletteManager::Get().GetModifiedColorCount();
970
971 ImGui::BeginDisabled(!has_unsaved);
972 if (ImGui::Button(
973 absl::StrFormat(ICON_MD_SAVE " Save All (%zu colors)", modified_count)
974 .c_str(),
975 ImVec2(-1, 0))) {
976 auto status = gfx::PaletteManager::Get().SaveAllToRom();
977 if (!status.ok()) {
980 absl::StrFormat("Failed to save palettes: %s", status.message()),
982 }
983 }
984 }
985 ImGui::EndDisabled();
986
987 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
988 if (has_unsaved) {
989 ImGui::SetTooltip("Save all modified colors to ROM");
990 } else {
991 ImGui::SetTooltip("No unsaved changes");
992 }
993 }
994
995 // Apply to Editors button - preview changes without saving to ROM
996 ImGui::BeginDisabled(!has_unsaved);
997 if (ImGui::Button(ICON_MD_VISIBILITY " Apply to Editors", ImVec2(-1, 0))) {
999 if (!status.ok()) {
1002 .c_str());
1003 }
1004 }
1005 ImGui::EndDisabled();
1006
1007 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
1008 if (has_unsaved) {
1009 ImGui::SetTooltip(
1010 "Preview palette changes in other editors without saving to ROM");
1011 } else {
1012 ImGui::SetTooltip("No changes to preview");
1013 }
1014 }
1015
1016 ImGui::BeginDisabled(!has_unsaved);
1017 if (ImGui::Button(ICON_MD_UNDO " Discard All Changes", ImVec2(-1, 0))) {
1020 .c_str());
1021 }
1022 ImGui::EndDisabled();
1023
1024 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
1025 if (has_unsaved) {
1026 ImGui::SetTooltip("Discard all unsaved changes");
1027 } else {
1028 ImGui::SetTooltip("No changes to discard");
1029 }
1030 }
1031
1032 // Confirmation popup for discard
1033 if (ImGui::BeginPopupModal(
1036 .c_str(),
1037 nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
1038 ImGui::Text("Discard all unsaved changes?");
1039 ImGui::TextColored(gui::GetWarningColor(),
1040 "This will revert %zu modified colors.", modified_count);
1041 ImGui::Separator();
1042
1043 if (ImGui::Button("Discard", ImVec2(120, 0))) {
1045 ImGui::CloseCurrentPopup();
1046 }
1047 ImGui::SameLine();
1048 if (ImGui::Button("Cancel", ImVec2(120, 0))) {
1049 ImGui::CloseCurrentPopup();
1050 }
1051 ImGui::EndPopup();
1052 }
1053
1054 // Error popup for save failures
1055 if (ImGui::BeginPopupModal(gui::MakePopupId(gui::EditorNames::kPalette,
1057 .c_str(),
1058 nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
1059 ImGui::TextColored(gui::GetErrorColor(), "Failed to save changes");
1060 ImGui::Text("An error occurred while saving to ROM.");
1061 ImGui::Separator();
1062
1063 if (ImGui::Button("OK", ImVec2(120, 0))) {
1064 ImGui::CloseCurrentPopup();
1065 }
1066 ImGui::EndPopup();
1067 }
1068
1069 ImGui::Separator();
1070
1071 // Panel management handled globally in the sidebar/menu system.
1072}
1073
1075 // Current color picker with more options
1076 ImGui::BeginGroup();
1077 ImGui::Text("Current Color");
1078 gui::SnesColorEdit4("##CurrentColorPicker", &current_color_,
1080
1081 char buf[64];
1082 auto col = current_color_.rgb();
1083 int cr = F32_TO_INT8_SAT(col.x / 255.0f);
1084 int cg = F32_TO_INT8_SAT(col.y / 255.0f);
1085 int cb = F32_TO_INT8_SAT(col.z / 255.0f);
1086
1087 CustomFormatString(buf, IM_ARRAYSIZE(buf), "RGB: %d, %d, %d", cr, cg, cb);
1088 ImGui::Text("%s", buf);
1089
1090 CustomFormatString(buf, IM_ARRAYSIZE(buf), "SNES: $%04X",
1092 ImGui::Text("%s", buf);
1093
1094 if (ImGui::Button("Copy to Clipboard", ImVec2(-1, 0))) {
1095 SetClipboardText(buf);
1096 }
1097 ImGui::EndGroup();
1098
1099 ImGui::Separator();
1100
1101 // Recently used colors
1102 ImGui::Text("Recently Used Colors");
1103 if (recently_used_colors_.empty()) {
1104 ImGui::TextDisabled("No recently used colors yet");
1105 } else {
1106 for (int i = 0; i < recently_used_colors_.size(); i++) {
1107 PushID(i);
1108 if (i % 8 != 0)
1109 SameLine();
1110 ImVec4 displayColor =
1112 if (ImGui::ColorButton("##recent", displayColor, kPalButtonFlags,
1113 ImVec2(28, 28))) {
1114 // Set as current color
1116 }
1117 if (ImGui::IsItemHovered()) {
1118 ImGui::SetTooltip("SNES: $%04X", recently_used_colors_[i].snes());
1119 }
1120 PopID();
1121 }
1122 }
1123}
1124
1126 ImGui::TextWrapped(
1127 "Create your own custom color palette for reference. "
1128 "Colors can be added from any palette group or created from scratch.");
1129
1130 ImGui::Separator();
1131
1132 // Custom palette color grid
1133 if (custom_palette_.empty()) {
1134 ImGui::TextDisabled("Your custom palette is empty.");
1135 ImGui::Text("Click + to add colors or drag colors from any palette.");
1136 } else {
1137 for (int i = 0; i < custom_palette_.size(); i++) {
1138 PushID(i);
1139 if (i > 0 && i % 16 != 0)
1140 SameLine(0.0f, 2.0f);
1141
1142 // Enhanced color button with context menu and drag-drop support
1143 ImVec4 displayColor = gui::ConvertSnesColorToImVec4(custom_palette_[i]);
1144 bool open_color_picker =
1145 ImGui::ColorButton(absl::StrFormat("##customPal%d", i).c_str(),
1146 displayColor, kPalButtonFlags, ImVec2(28, 28));
1147
1148 if (open_color_picker) {
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 // Color picker popup for custom palette editing
1217 if (ImGui::BeginPopup(gui::MakePopupId(gui::EditorNames::kPalette,
1218 "PanelCustomPaletteColorEdit")
1219 .c_str())) {
1220 if (edit_palette_index_ >= 0 &&
1224 "Edit Color", &custom_palette_[edit_palette_index_],
1225 kColorPopupFlags | ImGuiColorEditFlags_PickerHueWheel)) {
1226 // Color was changed, add to recently used
1228 }
1229 }
1230 ImGui::EndPopup();
1231 }
1232}
1233
1234void PaletteEditor::JumpToPalette(const std::string& group_name,
1235 int palette_index) {
1237 return;
1238 }
1239 auto* panel_manager = dependencies_.panel_manager;
1240 const size_t session_id = dependencies_.session_id;
1241
1242 // Show and focus the appropriate card
1243 if (group_name == "ow_main") {
1244 panel_manager->ShowPanel(session_id, "palette.ow_main");
1245 if (ow_main_panel_) {
1248 }
1249 } else if (group_name == "ow_animated") {
1250 panel_manager->ShowPanel(session_id, "palette.ow_animated");
1251 if (ow_anim_panel_) {
1254 }
1255 } else if (group_name == "dungeon_main") {
1256 panel_manager->ShowPanel(session_id, "palette.dungeon_main");
1257 if (dungeon_main_panel_) {
1260 }
1261 } else if (group_name == "global_sprites") {
1262 panel_manager->ShowPanel(session_id, "palette.sprites");
1266 }
1267 } else if (group_name == "sprites_aux1") {
1268 panel_manager->ShowPanel(session_id, "palette.sprites_aux1");
1269 if (sprite_aux1_panel_) {
1272 }
1273 } else if (group_name == "sprites_aux2") {
1274 panel_manager->ShowPanel(session_id, "palette.sprites_aux2");
1275 if (sprite_aux2_panel_) {
1278 }
1279 } else if (group_name == "sprites_aux3") {
1280 panel_manager->ShowPanel(session_id, "palette.sprites_aux3");
1281 if (sprite_aux3_panel_) {
1284 }
1285 } else if (group_name == "armors") {
1286 panel_manager->ShowPanel(session_id, "palette.equipment");
1287 if (equipment_panel_) {
1290 }
1291 }
1292
1293 // Show control panel too for easy navigation
1294 panel_manager->ShowPanel(session_id, "palette.control_panel");
1295}
1296
1297// ============================================================================
1298// Category and Search UI Methods
1299// ============================================================================
1300
1302 ImGui::SetNextItemWidth(-1);
1303 if (ImGui::InputTextWithHint("##PaletteSearch",
1304 ICON_MD_SEARCH " Search palettes...",
1305 search_buffer_, sizeof(search_buffer_))) {
1306 // Search text changed - UI will update automatically
1307 }
1308}
1309
1310bool PaletteEditor::PassesSearchFilter(const std::string& group_name) const {
1311 if (search_buffer_[0] == '\0')
1312 return true;
1313
1314 // Check if group name or display name matches
1315 return gui::FuzzyMatch(search_buffer_, group_name) ||
1317}
1318
1319bool* PaletteEditor::GetShowFlagForGroup(const std::string& group_name) {
1320 if (group_name == "ow_main")
1321 return &show_ow_main_panel_;
1322 if (group_name == "ow_animated")
1324 if (group_name == "dungeon_main")
1326 if (group_name == "global_sprites")
1327 return &show_sprite_panel_;
1328 if (group_name == "sprites_aux1")
1330 if (group_name == "sprites_aux2")
1332 if (group_name == "sprites_aux3")
1334 if (group_name == "armors")
1335 return &show_equipment_panel_;
1336 return nullptr;
1337}
1338
1340 // Search bar at top
1341 DrawSearchBar();
1342 ImGui::Separator();
1343
1344 const auto& categories = GetPaletteCategories();
1345
1346 for (size_t cat_idx = 0; cat_idx < categories.size(); cat_idx++) {
1347 const auto& cat = categories[cat_idx];
1348
1349 // Check if any items in category match search
1350 bool has_visible_items = false;
1351 for (const auto& group_name : cat.group_names) {
1352 if (PassesSearchFilter(group_name)) {
1353 has_visible_items = true;
1354 break;
1355 }
1356 }
1357
1358 if (!has_visible_items)
1359 continue;
1360
1361 ImGui::PushID(static_cast<int>(cat_idx));
1362
1363 // Collapsible header for category with icon
1364 std::string header_text =
1365 absl::StrFormat("%s %s", cat.icon, cat.display_name);
1366 bool open = ImGui::CollapsingHeader(header_text.c_str(),
1367 ImGuiTreeNodeFlags_DefaultOpen);
1368
1369 if (open) {
1370 ImGui::Indent(10.0f);
1371 for (const auto& group_name : cat.group_names) {
1372 if (!PassesSearchFilter(group_name))
1373 continue;
1374
1375 bool* show_flag = GetShowFlagForGroup(group_name);
1376 if (show_flag) {
1377 std::string label = GetGroupDisplayName(group_name);
1378
1379 // Show modified indicator
1380 bool is_modified =
1382 if (is_modified) {
1383 label += " *";
1384 }
1385 std::optional<gui::StyleColorGuard> mod_guard;
1386 if (is_modified) {
1387 mod_guard.emplace(ImGuiCol_Text, ImVec4(1.0f, 0.6f, 0.0f, 1.0f));
1388 }
1389
1390 ImGui::Checkbox(label.c_str(), show_flag);
1391 mod_guard.reset();
1392 }
1393 }
1394 ImGui::Unindent(10.0f);
1395 }
1396 ImGui::PopID();
1397 }
1398
1399 ImGui::Separator();
1400
1401 // Utilities section
1402 ImGui::Text("Utilities:");
1403 ImGui::Indent(10.0f);
1404 ImGui::Checkbox("Quick Access", &show_quick_access_);
1405 ImGui::Checkbox("Custom Palette", &show_custom_palette_);
1406 ImGui::Unindent(10.0f);
1407}
1408
1409} // namespace editor
1410} // namespace yaze
#define F32_TO_INT8_SAT(_VAL)
project::ResourceLabelManager * resource_label()
Definition rom.h:150
void set_dirty(bool dirty)
Definition rom.h:134
bool is_loaded() const
Definition rom.h:132
std::function< void()> draw_callback_
float GetPreferredWidth() const override
Get preferred width for this panel (optional)
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:297
EditorDependencies dependencies_
Definition editor.h:306
void SetGameData(zelda3::GameData *data)
std::string GetIcon() const override
Material Design icon for this panel.
std::function< void()> draw_callback_
float GetPreferredWidth() const override
Get preferred width for this panel (optional)
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)
void SetToastManager(ToastManager *toast_manager)
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.
float GetPreferredWidth() const override
Get preferred width for this panel (optional)
void Show(const std::string &message, ToastType type=ToastType::kInfo, float ttl_seconds=3.0f)
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.
bool IsGroupModified(const std::string &group_name) const
Check if a specific palette group has modifications.
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).
#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_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 * 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:22
ImVec4 GetErrorColor()
Definition ui_helpers.cc:58
IMGUI_API bool SnesColorEdit4(absl::string_view label, gfx::SnesColor *color, ImGuiColorEditFlags flags)
Definition color.cc:57
ImVec4 GetWarningColor()
Definition ui_helpers.cc:53
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:35
#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:1966
void SelectableLabelWithNameEdit(bool selected, const std::string &type, const std::string &key, const std::string &defaultValue)
Definition project.cc:1941
gfx::PaletteGroupMap palette_groups
Definition game_data.h:89