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#include "util/i18n/tr.h"
3
4#include <algorithm>
5
6#include "absl/status/status.h"
7#include "absl/strings/str_cat.h"
8#include "absl/strings/str_format.h"
17#include "app/gui/core/color.h"
18#include "app/gui/core/icons.h"
20#include "app/gui/core/search.h"
24#include "imgui/imgui.h"
25
26#include <cstring>
27
28namespace yaze {
29namespace editor {
30
31using ImGui::AcceptDragDropPayload;
32using ImGui::BeginChild;
33using ImGui::BeginDragDropTarget;
34using ImGui::BeginGroup;
35using ImGui::BeginPopup;
36using ImGui::BeginPopupContextItem;
37using ImGui::Button;
38using ImGui::ColorButton;
39using ImGui::ColorPicker4;
40using ImGui::EndChild;
41using ImGui::EndDragDropTarget;
42using ImGui::EndGroup;
43using ImGui::EndPopup;
44using ImGui::GetStyle;
45using ImGui::OpenPopup;
46using ImGui::PopID;
47using ImGui::PushID;
48using ImGui::SameLine;
49using ImGui::Selectable;
50using ImGui::Separator;
51using ImGui::SetClipboardText;
52using ImGui::Text;
53
54using namespace gfx;
55
56constexpr ImGuiTableFlags kPaletteTableFlags =
57 ImGuiTableFlags_Reorderable | ImGuiTableFlags_Resizable |
58 ImGuiTableFlags_SizingStretchSame | ImGuiTableFlags_Hideable;
59
60constexpr ImGuiColorEditFlags kPalNoAlpha = ImGuiColorEditFlags_NoAlpha;
61
62constexpr ImGuiColorEditFlags kPalButtonFlags = ImGuiColorEditFlags_NoAlpha |
63 ImGuiColorEditFlags_NoPicker |
64 ImGuiColorEditFlags_NoTooltip;
65
66constexpr ImGuiColorEditFlags kColorPopupFlags =
67 ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoAlpha |
68 ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_DisplayHSV |
69 ImGuiColorEditFlags_DisplayHex;
70
71namespace {
72void DrawColorDropTargetOutline(ImVec2 min, ImVec2 max) {
73 const auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
74 ImGui::GetForegroundDrawList()->AddRect(
75 min, max, ImGui::GetColorU32(gui::ConvertColorToImVec4(theme.accent)),
76 0.0f, 0, 2.0f);
77}
78
79bool AcceptImGuiColorDrop(SnesColor* color, ImVec2 min, ImVec2 max) {
80 if (!color || !BeginDragDropTarget()) {
81 return false;
82 }
83
84 bool changed = false;
85 constexpr ImGuiDragDropFlags kFlags =
86 ImGuiDragDropFlags_AcceptBeforeDelivery |
87 ImGuiDragDropFlags_AcceptNoDrawDefaultRect |
88 ImGuiDragDropFlags_AcceptNoPreviewTooltip;
89 auto accept = [&](const char* type, size_t components) {
90 if (changed) {
91 return;
92 }
93 if (const ImGuiPayload* payload = AcceptDragDropPayload(type, kFlags)) {
95 if (payload->IsDelivery()) {
96 ImVec4 dropped(0, 0, 0, 1);
97 std::memcpy(&dropped.x, payload->Data, sizeof(float) * components);
98 if (components == 3) {
99 dropped.w = 1.0f;
100 }
101 *color = SnesColor(dropped);
102 changed = true;
103 }
104 }
105 };
106
107 accept(IMGUI_PAYLOAD_TYPE_COLOR_3F, 3);
108 accept(IMGUI_PAYLOAD_TYPE_COLOR_4F, 4);
109 EndDragDropTarget();
110 return changed;
111}
112
113int CustomFormatString(char* buf, size_t buf_size, const char* fmt, ...) {
114 va_list args;
115 va_start(args, fmt);
116#ifdef IMGUI_USE_STB_SPRINTF
117 int w = stbsp_vsnprintf(buf, (int)buf_size, fmt, args);
118#else
119 int w = vsnprintf(buf, buf_size, fmt, args);
120#endif
121 va_end(args);
122 if (buf == nullptr)
123 return w;
124 if (w == -1 || w >= (int)buf_size)
125 w = (int)buf_size - 1;
126 buf[w] = 0;
127 return w;
128}
129
130static inline float color_saturate(float f) {
131 return (f < 0.0f) ? 0.0f : (f > 1.0f) ? 1.0f : f;
132}
133
134#define F32_TO_INT8_SAT(_VAL) \
135 ((int)(color_saturate(_VAL) * 255.0f + \
136 0.5f)) // Saturated, always output 0..255
137} // namespace
138
156absl::Status DisplayPalette(gfx::SnesPalette& palette, bool loaded) {
157 static ImVec4 color = ImVec4(0, 0, 0, 1.0f);
158 static ImVec4 current_palette[256] = {};
159 static int current_palette_count = 0;
160 ImGuiColorEditFlags misc_flags = ImGuiColorEditFlags_AlphaPreview |
161 ImGuiColorEditFlags_NoDragDrop |
162 ImGuiColorEditFlags_NoOptions;
163
164 // Reload palette colors whenever the palette data is available.
165 if (loaded) {
166 current_palette_count =
167 std::min<int>(static_cast<int>(palette.size()),
168 static_cast<int>(IM_ARRAYSIZE(current_palette)));
169
170 for (int n = 0; n < current_palette_count; ++n) {
171 const auto palette_color = palette[static_cast<size_t>(n)];
172 current_palette[n].x = palette_color.rgb().x / 255.0f;
173 current_palette[n].y = palette_color.rgb().y / 255.0f;
174 current_palette[n].z = palette_color.rgb().z / 255.0f;
175 current_palette[n].w = 1.0f;
176 }
177 for (int n = current_palette_count;
178 n < static_cast<int>(IM_ARRAYSIZE(current_palette)); ++n) {
179 current_palette[n] = ImVec4(0, 0, 0, 1.0f);
180 }
181 } else {
182 current_palette_count = 0;
183 }
184
185 static ImVec4 backup_color;
186 bool open_popup = ColorButton("MyColor##3b", color, misc_flags);
187 SameLine(0, GetStyle().ItemInnerSpacing.x);
188 open_popup |= Button(tr("Palette"));
189 if (open_popup) {
192 .c_str());
193 backup_color = color;
194 }
195
198 .c_str())) {
199 TEXT_WITH_SEPARATOR("Current Overworld Palette");
200 ColorPicker4("##picker", (float*)&color,
201 misc_flags | ImGuiColorEditFlags_NoSidePreview |
202 ImGuiColorEditFlags_NoSmallPreview);
203 SameLine();
204
205 BeginGroup(); // Lock X position
206 Text(tr("Current ==>"));
207 SameLine();
208 Text(tr("Previous"));
209
210 if (Button(tr("Update Map Palette"))) {}
211
212 ColorButton(
213 "##current", color,
214 ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf,
215 ImVec2(60, 40));
216 SameLine();
217
218 if (ColorButton(
219 "##previous", backup_color,
220 ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf,
221 ImVec2(60, 40)))
222 color = backup_color;
223
224 // List of Colors in Overworld Palette
225 Separator();
226 Text(tr("Palette"));
227 if (current_palette_count <= 0) {
228 ImGui::TextDisabled(tr("No palette entries loaded."));
229 }
230 for (int n = 0; n < current_palette_count; n++) {
231 PushID(n);
232 if ((n % 8) != 0)
233 SameLine(0.0f, GetStyle().ItemSpacing.y);
234
235 if (ColorButton("##palette", current_palette[n], kPalButtonFlags,
236 ImVec2(20, 20)))
237 color = ImVec4(current_palette[n].x, current_palette[n].y,
238 current_palette[n].z, color.w); // Preserve alpha!
239
240 if (BeginDragDropTarget()) {
241 if (const ImGuiPayload* payload =
242 AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))
243 memcpy((float*)&current_palette[n], payload->Data, sizeof(float) * 3);
244 if (const ImGuiPayload* payload =
245 AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))
246 memcpy((float*)&current_palette[n], payload->Data, sizeof(float) * 4);
247 EndDragDropTarget();
248 }
249
250 PopID();
251 }
252 EndGroup();
253 EndPopup();
254 }
255
256 return absl::OkStatus();
257}
258
260 // Register all panels with WorkspaceWindowManager (done once during
261 // initialization)
263 return;
264 auto* window_manager = dependencies_.window_manager;
265 const size_t session_id = dependencies_.session_id;
266
267 window_manager->RegisterPanel(
268 {.card_id = "palette.control_panel",
269 .display_name = "Palette Controls",
270 .window_title = " Palette Controls",
271 .icon = ICON_MD_PALETTE,
272 .category = "Palette",
273 .shortcut_hint = "Ctrl+Shift+P",
274 .visibility_flag = &show_control_panel_,
275 .priority = 10,
276 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
277 .disabled_tooltip = "Load a ROM first"});
278
279 window_manager->RegisterPanel(
280 {.card_id = "palette.ow_main",
281 .display_name = "Overworld Main",
282 .window_title = " Overworld Main",
283 .icon = ICON_MD_LANDSCAPE,
284 .category = "Palette",
285 .shortcut_hint = "Ctrl+Alt+1",
286 .visibility_flag = &show_ow_main_panel_,
287 .priority = 20,
288 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
289 .disabled_tooltip = "Load a ROM first"});
290
291 window_manager->RegisterPanel(
292 {.card_id = "palette.ow_animated",
293 .display_name = "Overworld Animated",
294 .window_title = " Overworld Animated",
295 .icon = ICON_MD_WATER,
296 .category = "Palette",
297 .shortcut_hint = "Ctrl+Alt+2",
298 .visibility_flag = &show_ow_animated_panel_,
299 .priority = 30,
300 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
301 .disabled_tooltip = "Load a ROM first"});
302
303 window_manager->RegisterPanel(
304 {.card_id = "palette.dungeon_main",
305 .display_name = "Dungeon Main",
306 .window_title = " Dungeon Main",
307 .icon = ICON_MD_CASTLE,
308 .category = "Palette",
309 .shortcut_hint = "Ctrl+Alt+3",
310 .visibility_flag = &show_dungeon_main_panel_,
311 .priority = 40,
312 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
313 .disabled_tooltip = "Load a ROM first"});
314
315 window_manager->RegisterPanel(
316 {.card_id = "palette.sprites",
317 .display_name = "Global Sprite Palettes",
318 .window_title = " SNES Palette",
319 .icon = ICON_MD_PETS,
320 .category = "Palette",
321 .shortcut_hint = "Ctrl+Alt+4",
322 .visibility_flag = &show_sprite_panel_,
323 .priority = 50,
324 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
325 .disabled_tooltip = "Load a ROM first"});
326
327 window_manager->RegisterPanel(
328 {.card_id = "palette.sprites_aux1",
329 .display_name = "Sprites Aux 1",
330 .window_title = " Sprites Aux 1",
331 .icon = ICON_MD_FILTER_1,
332 .category = "Palette",
333 .shortcut_hint = "Ctrl+Alt+7",
334 .visibility_flag = &show_sprites_aux1_panel_,
335 .priority = 51,
336 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
337 .disabled_tooltip = "Load a ROM first"});
338
339 window_manager->RegisterPanel(
340 {.card_id = "palette.sprites_aux2",
341 .display_name = "Sprites Aux 2",
342 .window_title = " Sprites Aux 2",
343 .icon = ICON_MD_FILTER_2,
344 .category = "Palette",
345 .shortcut_hint = "Ctrl+Alt+8",
346 .visibility_flag = &show_sprites_aux2_panel_,
347 .priority = 52,
348 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
349 .disabled_tooltip = "Load a ROM first"});
350
351 window_manager->RegisterPanel(
352 {.card_id = "palette.sprites_aux3",
353 .display_name = "Sprites Aux 3",
354 .window_title = " Sprites Aux 3",
355 .icon = ICON_MD_FILTER_3,
356 .category = "Palette",
357 .shortcut_hint = "Ctrl+Alt+9",
358 .visibility_flag = &show_sprites_aux3_panel_,
359 .priority = 53,
360 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
361 .disabled_tooltip = "Load a ROM first"});
362
363 window_manager->RegisterPanel(
364 {.card_id = "palette.equipment",
365 .display_name = "Equipment Palettes",
366 .window_title = " Equipment Palettes",
367 .icon = ICON_MD_SHIELD,
368 .category = "Palette",
369 .shortcut_hint = "Ctrl+Alt+5",
370 .visibility_flag = &show_equipment_panel_,
371 .priority = 60,
372 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
373 .disabled_tooltip = "Load a ROM first"});
374
375 window_manager->RegisterPanel(
376 {.card_id = "palette.quick_access",
377 .display_name = "Quick Access",
378 .window_title = " Color Harmony",
379 .icon = ICON_MD_COLOR_LENS,
380 .category = "Palette",
381 .shortcut_hint = "Ctrl+Alt+Q",
382 .visibility_flag = &show_quick_access_,
383 .priority = 70,
384 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
385 .disabled_tooltip = "Load a ROM first"});
386
387 window_manager->RegisterPanel(
388 {.card_id = "palette.custom",
389 .display_name = "Custom Palette",
390 .window_title = " Palette Editor",
391 .icon = ICON_MD_BRUSH,
392 .category = "Palette",
393 .shortcut_hint = "Ctrl+Alt+C",
394 .visibility_flag = &show_custom_palette_,
395 .priority = 80,
396 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
397 .disabled_tooltip = "Load a ROM first"});
398
399 // Show control panel by default when Palette Editor is activated
400 window_manager->OpenWindow(session_id, "palette.control_panel");
401}
402
403// ============================================================================
404// Helper Panel Classes
405// ============================================================================
406
408 public:
409 explicit PaletteControlPanel(std::function<void()> draw_callback)
410 : draw_callback_(std::move(draw_callback)) {}
411
412 std::string GetId() const override { return "palette.control_panel"; }
413 std::string GetDisplayName() const override { return "Palette Controls"; }
414 std::string GetIcon() const override { return ICON_MD_PALETTE; }
415 std::string GetEditorCategory() const override { return "Palette"; }
416 int GetPriority() const override { return 10; }
417 float GetPreferredWidth() const override { return 320.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
431 public:
432 explicit QuickAccessPalettePanel(std::function<void()> draw_callback)
433 : draw_callback_(std::move(draw_callback)) {}
434
435 std::string GetId() const override { return "palette.quick_access"; }
436 std::string GetDisplayName() const override { return "Quick Access"; }
437 std::string GetIcon() const override { return ICON_MD_COLOR_LENS; }
438 std::string GetEditorCategory() const override { return "Palette"; }
439 int GetPriority() const override { return 70; }
440 float GetPreferredWidth() const override { return 340.0f; }
441
442 void Draw(bool* p_open) override {
443 if (p_open && !*p_open)
444 return;
445 if (draw_callback_)
447 }
448
449 private:
450 std::function<void()> draw_callback_;
451};
452
454 public:
455 explicit CustomPalettePanel(std::function<void()> draw_callback)
456 : draw_callback_(std::move(draw_callback)) {}
457
458 std::string GetId() const override { return "palette.custom"; }
459 std::string GetDisplayName() const override { return "Custom Palette"; }
460 std::string GetIcon() const override { return ICON_MD_BRUSH; }
461 std::string GetEditorCategory() const override { return "Palette"; }
462 int GetPriority() const override { return 80; }
463 float GetPreferredWidth() const override { return 420.0f; }
464
465 void Draw(bool* p_open) override {
466 if (p_open && !*p_open)
467 return;
468 if (draw_callback_)
470 }
471
472 private:
473 std::function<void()> draw_callback_;
474};
475
480
481namespace {
482template <typename Panel>
484 size_t session_id, const char* panel_id,
485 Rom* rom) {
486 auto* panel = dynamic_cast<Panel*>(
487 window_manager->GetWindowContent(session_id, panel_id));
488 return panel != nullptr && panel->IsOwnedByRom(rom) ? panel : nullptr;
489}
490} // namespace
491
493 auto* window_manager = dependencies_.window_manager;
494 if (window_manager == nullptr) {
495 ow_main_panel_ = nullptr;
496 ow_anim_panel_ = nullptr;
497 dungeon_main_panel_ = nullptr;
498 sprite_global_panel_ = nullptr;
499 sprite_aux1_panel_ = nullptr;
500 sprite_aux2_panel_ = nullptr;
501 sprite_aux3_panel_ = nullptr;
502 equipment_panel_ = nullptr;
503 return;
504 }
505
506 const size_t session_id = dependencies_.session_id;
507 ow_main_panel_ = ResolveSessionPalettePanel<OverworldMainPalettePanel>(
508 window_manager, session_id, "palette.ow_main", rom_);
509 ow_anim_panel_ = ResolveSessionPalettePanel<OverworldAnimatedPalettePanel>(
510 window_manager, session_id, "palette.ow_animated", rom_);
511 dungeon_main_panel_ = ResolveSessionPalettePanel<DungeonMainPalettePanel>(
512 window_manager, session_id, "palette.dungeon_main", rom_);
513 sprite_global_panel_ = ResolveSessionPalettePanel<SpritePalettePanel>(
514 window_manager, session_id, "palette.global_sprites", rom_);
515 sprite_aux1_panel_ = ResolveSessionPalettePanel<SpritesAux1PalettePanel>(
516 window_manager, session_id, "palette.sprites_aux1", rom_);
517 sprite_aux2_panel_ = ResolveSessionPalettePanel<SpritesAux2PalettePanel>(
518 window_manager, session_id, "palette.sprites_aux2", rom_);
519 sprite_aux3_panel_ = ResolveSessionPalettePanel<SpritesAux3PalettePanel>(
520 window_manager, session_id, "palette.sprites_aux3", rom_);
521 equipment_panel_ = ResolveSessionPalettePanel<EquipmentPalettePanel>(
522 window_manager, session_id, "palette.armors", rom_);
523}
524
526 // WorkspaceWindowManager owns these panels and may have already destroyed
527 // them while the session-close event is still rebinding editor dependencies.
529 const auto apply = [this](PaletteGroupPanel* panel) {
530 if (panel == nullptr) {
531 return;
532 }
533 panel->SetToastManager(dependencies_.toast_manager);
534 panel->SetProject(dependencies_.project);
535 };
536 apply(ow_main_panel_);
537 apply(ow_anim_panel_);
538 apply(dungeon_main_panel_);
540 apply(sprite_aux1_panel_);
541 apply(sprite_aux2_panel_);
542 apply(sprite_aux3_panel_);
543 apply(equipment_panel_);
544}
545
546absl::Status PaletteEditor::Load() {
547 gfx::ScopedTimer timer("PaletteEditor::Load");
548
549 if (!rom() || !rom()->is_loaded()) {
550 return absl::NotFoundError("ROM not open, no palettes to display");
551 }
552
553 // Initialize the labels
554 for (int i = 0; i < kNumPalettes; i++) {
556 "Palette Group Name", std::to_string(i),
557 std::string(kPaletteGroupNames[i]));
558 }
559
560 // Initialize the centralized PaletteManager with GameData
561 // This must be done before creating any palette cards
562 if (game_data()) {
564 } else {
565 // Fallback to legacy ROM-only initialization
567 }
568
569 // Register WindowContent instances with WorkspaceWindowManager
571 auto* window_manager = dependencies_.window_manager;
572
573 // Create and register palette panels
574 // Note: WorkspaceWindowManager takes ownership via unique_ptr
575
576 // Overworld Main
577 auto ow_main =
578 std::make_unique<OverworldMainPalettePanel>(rom_, game_data());
579 ow_main_panel_ = ow_main.get();
580 window_manager->RegisterWindowContent(std::move(ow_main));
581
582 // Overworld Animated
583 auto ow_anim =
584 std::make_unique<OverworldAnimatedPalettePanel>(rom_, game_data());
585 ow_anim_panel_ = ow_anim.get();
586 window_manager->RegisterWindowContent(std::move(ow_anim));
587
588 // Dungeon Main
589 auto dungeon_main =
590 std::make_unique<DungeonMainPalettePanel>(rom_, game_data());
591 dungeon_main_panel_ = dungeon_main.get();
592 window_manager->RegisterWindowContent(std::move(dungeon_main));
593
594 // Global Sprites
595 auto sprite_global =
596 std::make_unique<SpritePalettePanel>(rom_, game_data());
597 sprite_global_panel_ = sprite_global.get();
598 window_manager->RegisterWindowContent(std::move(sprite_global));
599
600 // Sprites Aux 1
601 auto sprite_aux1 =
602 std::make_unique<SpritesAux1PalettePanel>(rom_, game_data());
603 sprite_aux1_panel_ = sprite_aux1.get();
604 window_manager->RegisterWindowContent(std::move(sprite_aux1));
605
606 // Sprites Aux 2
607 auto sprite_aux2 =
608 std::make_unique<SpritesAux2PalettePanel>(rom_, game_data());
609 sprite_aux2_panel_ = sprite_aux2.get();
610 window_manager->RegisterWindowContent(std::move(sprite_aux2));
611
612 // Sprites Aux 3
613 auto sprite_aux3 =
614 std::make_unique<SpritesAux3PalettePanel>(rom_, game_data());
615 sprite_aux3_panel_ = sprite_aux3.get();
616 window_manager->RegisterWindowContent(std::move(sprite_aux3));
617
618 // Equipment
619 auto equipment = std::make_unique<EquipmentPalettePanel>(rom_, game_data());
620 equipment_panel_ = equipment.get();
621 window_manager->RegisterWindowContent(std::move(equipment));
622
624
625 // Register utility panels with callbacks
626 window_manager->RegisterWindowContent(std::make_unique<PaletteControlPanel>(
627 [this]() { DrawControlPanel(); }));
628 window_manager->RegisterWindowContent(
629 std::make_unique<QuickAccessPalettePanel>(
630 [this]() { DrawQuickAccessPanel(); }));
631 window_manager->RegisterWindowContent(std::make_unique<CustomPalettePanel>(
632 [this]() { DrawCustomPalettePanel(); }));
633 }
634
635 return absl::OkStatus();
636}
637
638absl::Status PaletteEditor::Save() {
639 if (!rom_ || !rom_->is_loaded()) {
640 return absl::FailedPreconditionError("ROM not loaded");
641 }
642 if (!game_data() || !gfx::PaletteManager::Get().IsManaging(game_data())) {
643 return absl::FailedPreconditionError(
644 "Cannot save palettes from an inactive ROM session");
645 }
646
647 auto& palette_manager = gfx::PaletteManager::Get();
648 if (dependencies_.project != nullptr &&
650 const auto ranges =
651 palette_manager.GetModifiedColorWriteRanges(game_data());
652 if (!ranges.empty()) {
655 dependencies_.project->rom_metadata.write_policy, ranges, "palettes",
656 "PaletteEditor", dependencies_.toast_manager));
657 }
658 }
659
660 // Delegate to PaletteManager for centralized save.
661 RETURN_IF_ERROR(palette_manager.SaveAllToRom());
662
663 // Mark ROM as needing file save
664 rom_->set_dirty(true);
665
666 return absl::OkStatus();
667}
668
669absl::Status PaletteEditor::Undo() {
670 if (!game_data() || !gfx::PaletteManager::Get().IsManaging(game_data())) {
671 return absl::FailedPreconditionError(
672 "Cannot undo palettes from an inactive ROM session");
673 }
674
676 return absl::OkStatus();
677}
678
679absl::Status PaletteEditor::Redo() {
680 if (!game_data() || !gfx::PaletteManager::Get().IsManaging(game_data())) {
681 return absl::FailedPreconditionError(
682 "Cannot redo palettes from an inactive ROM session");
683 }
684
686 return absl::OkStatus();
687}
688
689absl::Status PaletteEditor::Update() {
690 // Panel drawing is handled centrally by WorkspaceWindowManager::DrawAllVisiblePanels()
691 // via the WindowContent implementations registered in Load().
692 // No local drawing needed here - this fixes duplicate panel rendering.
693 return absl::OkStatus();
694}
695
697 BeginChild("QuickAccessPalettes", ImVec2(0, 0), true);
698
699 Text(tr("Custom Palette"));
701
702 Separator();
703
704 // Current color picker with more options
705 BeginGroup();
706 Text(tr("Current Color"));
707 gui::SnesColorEdit4("##CurrentColorPicker", &current_color_,
709
710 char buf[64];
711 auto col = current_color_.rgb();
712 int cr = F32_TO_INT8_SAT(col.x / 255.0f);
713 int cg = F32_TO_INT8_SAT(col.y / 255.0f);
714 int cb = F32_TO_INT8_SAT(col.z / 255.0f);
715
716 CustomFormatString(buf, IM_ARRAYSIZE(buf), "RGB: %d, %d, %d", cr, cg, cb);
717 Text("%s", buf);
718
719 CustomFormatString(buf, IM_ARRAYSIZE(buf), "SNES: $%04X",
721 Text("%s", buf);
722
723 if (Button(tr("Copy to Clipboard"))) {
724 SetClipboardText(buf);
725 }
726 EndGroup();
727
728 Separator();
729
730 // Recently used colors
731 Text(tr("Recently Used Colors"));
732 for (int i = 0; i < recently_used_colors_.size(); i++) {
733 PushID(i);
734 if (i % 8 != 0)
735 SameLine();
736 ImVec4 displayColor =
738 if (ImGui::ColorButton("##recent", displayColor)) {
739 // Set as current color
741 }
742 PopID();
743 }
744
745 EndChild();
746}
747
764 if (BeginChild("ColorPalette", ImVec2(0, 40), ImGuiChildFlags_None,
765 ImGuiWindowFlags_HorizontalScrollbar)) {
766 for (int i = 0; i < custom_palette_.size(); i++) {
767 PushID(i);
768 if (i > 0)
769 SameLine(0.0f, GetStyle().ItemSpacing.y);
770
771 // Enhanced color button with context menu and drag-drop support
772 ImVec4 displayColor = gui::ConvertSnesColorToImVec4(custom_palette_[i]);
773 bool open_color_picker = ImGui::ColorButton(
774 absl::StrFormat("##customPal%d", i).c_str(), displayColor);
775 const ImVec2 swatch_min = ImGui::GetItemRectMin();
776 const ImVec2 swatch_max = ImGui::GetItemRectMax();
777 if (ImGui::IsItemHovered() &&
778 ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
779 open_color_picker = true;
780 }
781
782 if (open_color_picker) {
786 "CustomPaletteColorEdit")
787 .c_str());
788 }
789
790 if (BeginPopupContextItem()) {
791 // Edit color directly in the popup
792 SnesColor original_color = custom_palette_[i];
793 if (gui::SnesColorEdit4("Edit Color", &custom_palette_[i],
795 // Color was changed, add to recently used
797 }
798
799 if (Button(tr("Delete"), ImVec2(-1, 0))) {
800 custom_palette_.erase(custom_palette_.begin() + i);
801 }
802 }
803
804 if (AcceptImGuiColorDrop(&custom_palette_[i], swatch_min, swatch_max)) {
806 }
807
808 PopID();
809 }
810
811 SameLine();
812 if (ImGui::Button("+")) {
813 custom_palette_.push_back(SnesColor(0x7FFF));
814 }
815
816 SameLine();
817 if (ImGui::Button(tr("Clear"))) {
818 custom_palette_.clear();
819 }
820
821 SameLine();
822 if (ImGui::Button(tr("Export"))) {
823 std::string clipboard;
824 for (const auto& color : custom_palette_) {
825 clipboard += absl::StrFormat("$%04X,", color.snes());
826 }
827 SetClipboardText(clipboard.c_str());
828 }
829 }
830 EndChild();
831
832 // Color picker popup for custom palette editing
833 if (ImGui::BeginPopup(
834 gui::MakePopupId(gui::EditorNames::kPalette, "CustomPaletteColorEdit")
835 .c_str())) {
836 if (edit_palette_index_ >= 0 &&
840 "Edit Color", &custom_palette_[edit_palette_index_],
841 kColorPopupFlags | ImGuiColorEditFlags_PickerHueWheel)) {
842 // Color was changed, add to recently used
844 }
845 }
846 ImGui::EndPopup();
847 }
848}
849
850absl::Status PaletteEditor::DrawPaletteGroup(int category,
851 bool /*right_side*/) {
852 if (!rom()->is_loaded() || !game_data()) {
853 return absl::NotFoundError("ROM not open, no palettes to display");
854 }
855
856 auto palette_group_name = kPaletteGroupNames[category];
857 gfx::PaletteGroup* palette_group =
858 game_data()->palette_groups.get_group(palette_group_name.data());
859 const auto size = palette_group->size();
860
861 for (int j = 0; j < size; j++) {
862 gfx::SnesPalette* palette = palette_group->mutable_palette(j);
863 auto pal_size = palette->size();
864
865 BeginGroup();
866
867 PushID(j);
868 BeginGroup();
870 false, palette_group_name.data(), /*key=*/std::to_string(j),
871 "Unnamed Palette");
872 EndGroup();
873
874 for (int n = 0; n < pal_size; n++) {
875 PushID(n);
876 if (n > 0 && n % 8 != 0)
877 SameLine(0.0f, 2.0f);
878
879 auto popup_id =
880 absl::StrCat(kPaletteCategoryNames[category].data(), j, "_", n);
881
882 ImVec4 displayColor = gui::ConvertSnesColorToImVec4((*palette)[n]);
883 if (ImGui::ColorButton(popup_id.c_str(), displayColor)) {
884 current_color_ = (*palette)[n];
886 }
887 const ImVec2 swatch_min = ImGui::GetItemRectMin();
888 const ImVec2 swatch_max = ImGui::GetItemRectMax();
889 if (ImGui::IsItemHovered() &&
890 ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
891 current_color_ = (*palette)[n];
893 OpenPopup(popup_id.c_str());
894 }
895 {
896 const SnesColor original_color = (*palette)[n];
897 if (AcceptImGuiColorDrop(&(*palette)[n], swatch_min, swatch_max)) {
899 /*group_name=*/std::string(kPaletteGroupNames[category]),
900 /*palette_index=*/j, /*color_index=*/n, original_color,
901 (*palette)[n]);
902 (*palette)[n].set_modified(true);
903 AddRecentlyUsedColor((*palette)[n]);
904 }
905 }
906
907 if (BeginPopupContextItem(popup_id.c_str())) {
908 RETURN_IF_ERROR(HandleColorPopup(*palette, category, j, n))
909 }
910 PopID();
911 }
912 PopID();
913 EndGroup();
914
915 if (j < size - 1) {
916 Separator();
917 }
918 }
919 return absl::OkStatus();
920}
921
923 // Check if color already exists in recently used
924 auto it = std::find_if(
926 [&color](const SnesColor& c) { return c.snes() == color.snes(); });
927
928 // If found, remove it to re-add at front
929 if (it != recently_used_colors_.end()) {
930 recently_used_colors_.erase(it);
931 }
932
933 // Add at front
934 recently_used_colors_.insert(recently_used_colors_.begin(), color);
935
936 // Limit size
937 if (recently_used_colors_.size() > 16) {
938 recently_used_colors_.pop_back();
939 }
940}
941
943 int j, int n) {
944 auto col = gfx::ToFloatArray(palette[n]);
945 auto original_color = palette[n];
946
947 if (gui::SnesColorEdit4("Edit Color", &palette[n], kColorPopupFlags)) {
948 history_.RecordChange(/*group_name=*/std::string(kPaletteGroupNames[i]),
949 /*palette_index=*/j, /*color_index=*/n,
950 original_color, palette[n]);
951 palette[n].set_modified(true);
952
953 // Add to recently used colors
954 AddRecentlyUsedColor(palette[n]);
955 }
956
957 // Color information display
958 char buf[64];
959 int cr = F32_TO_INT8_SAT(col[0]);
960 int cg = F32_TO_INT8_SAT(col[1]);
961 int cb = F32_TO_INT8_SAT(col[2]);
962
963 Text(tr("RGB: %d, %d, %d"), cr, cg, cb);
964 Text(tr("SNES: $%04X"), palette[n].snes());
965
966 Separator();
967
968 if (Button(tr("Copy as.."), ImVec2(-1, 0)))
971 .c_str());
974 .c_str())) {
975 CustomFormatString(buf, IM_ARRAYSIZE(buf), "(%.3ff, %.3ff, %.3ff)", col[0],
976 col[1], col[2]);
977 if (Selectable(buf))
978 SetClipboardText(buf);
979
980 CustomFormatString(buf, IM_ARRAYSIZE(buf), "(%d,%d,%d)", cr, cg, cb);
981 if (Selectable(buf))
982 SetClipboardText(buf);
983
984 CustomFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", cr, cg, cb);
985 if (Selectable(buf))
986 SetClipboardText(buf);
987
988 // SNES Format
989 CustomFormatString(buf, IM_ARRAYSIZE(buf), "$%04X",
990 ConvertRgbToSnes(ImVec4(col[0], col[1], col[2], 1.0f)));
991 if (Selectable(buf))
992 SetClipboardText(buf);
993
994 EndPopup();
995 }
996
997 // Add a button to add this color to custom palette
998 if (Button(tr("Add to Custom Palette"), ImVec2(-1, 0))) {
999 custom_palette_.push_back(palette[n]);
1000 }
1001
1002 EndPopup();
1003 return absl::OkStatus();
1004}
1005
1007 int index) {
1008 if (index >= palette.size()) {
1009 return absl::InvalidArgumentError("Index out of bounds");
1010 }
1011
1012 // Get the current color
1013 auto color = palette[index];
1014 auto currentColor = color.rgb();
1015 if (ColorPicker4("Color Picker", (float*)&palette[index])) {
1016 // The color was modified, update it in the palette
1017 palette[index] = gui::ConvertImVec4ToSnesColor(currentColor);
1018
1019 // Add to recently used colors
1020 AddRecentlyUsedColor(palette[index]);
1021 }
1022 return absl::OkStatus();
1023}
1024
1026 gfx::SnesPalette& palette, int index,
1027 const gfx::SnesPalette& originalPalette) {
1028 if (index >= palette.size() || index >= originalPalette.size()) {
1029 return absl::InvalidArgumentError("Index out of bounds");
1030 }
1031 auto color = originalPalette[index];
1032 auto originalColor = color.rgb();
1033 palette[index] = gui::ConvertImVec4ToSnesColor(originalColor);
1034 return absl::OkStatus();
1035}
1036
1037// ============================================================================
1038// Panel-Based UI Methods
1039// ============================================================================
1040
1042 // Sidebar is drawn by WorkspaceWindowManager in EditorManager
1043 // Panels registered in Initialize() appear in the sidebar automatically
1044}
1045
1047 if (!game_data() || !gfx::PaletteManager::Get().IsManaging(game_data())) {
1048 ImGui::TextDisabled(
1049 tr("Palette controls are unavailable for an inactive ROM session."));
1050 return;
1051 }
1052
1054
1055 // Toolbar with quick toggles
1056 DrawToolset();
1057
1058 ImGui::Separator();
1059
1060 // Categorized palette list with search
1062
1063 ImGui::Separator();
1064
1065 // Modified status indicator
1066 ImGui::TextColored(gui::GetWarningColor(), tr("Modified Panels:"));
1067 bool any_modified = false;
1068
1070 ImGui::BulletText(tr("Overworld Main"));
1071 any_modified = true;
1072 }
1074 ImGui::BulletText(tr("Overworld Animated"));
1075 any_modified = true;
1076 }
1078 ImGui::BulletText(tr("Dungeon Main"));
1079 any_modified = true;
1080 }
1082 ImGui::BulletText(tr("Global Sprite Palettes"));
1083 any_modified = true;
1084 }
1086 ImGui::BulletText(tr("Sprites Aux 1"));
1087 any_modified = true;
1088 }
1090 ImGui::BulletText(tr("Sprites Aux 2"));
1091 any_modified = true;
1092 }
1094 ImGui::BulletText(tr("Sprites Aux 3"));
1095 any_modified = true;
1096 }
1098 ImGui::BulletText(tr("Equipment Palettes"));
1099 any_modified = true;
1100 }
1101
1102 if (!any_modified) {
1103 ImGui::TextDisabled(tr("No unsaved changes"));
1104 }
1105
1106 ImGui::Separator();
1107
1108 // Quick actions
1109 ImGui::Text(tr("Quick Actions:"));
1110
1111 // Use centralized PaletteManager for global operations
1112 bool has_unsaved = gfx::PaletteManager::Get().HasUnsavedChanges();
1113 size_t modified_count = gfx::PaletteManager::Get().GetModifiedColorCount();
1114
1115 ImGui::BeginDisabled(!has_unsaved);
1116 if (ImGui::Button(
1117 absl::StrFormat(ICON_MD_SAVE " Save All (%zu colors)", modified_count)
1118 .c_str(),
1119 ImVec2(-1, 0))) {
1120 auto status = Save();
1121 if (!status.ok()) {
1124 absl::StrFormat("Failed to save palettes: %s", status.message()),
1126 }
1127 }
1128 }
1129 ImGui::EndDisabled();
1130
1131 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
1132 if (has_unsaved) {
1133 ImGui::SetTooltip(tr("Save all modified colors to ROM"));
1134 } else {
1135 ImGui::SetTooltip(tr("No unsaved changes"));
1136 }
1137 }
1138
1139 // Apply to Editors button - preview changes without saving to ROM
1140 ImGui::BeginDisabled(!has_unsaved);
1141 if (ImGui::Button(ICON_MD_VISIBILITY " Apply to Editors", ImVec2(-1, 0))) {
1143 if (!status.ok()) {
1146 .c_str());
1147 }
1148 }
1149 ImGui::EndDisabled();
1150
1151 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
1152 if (has_unsaved) {
1153 ImGui::SetTooltip(
1154 tr("Preview palette changes in other editors without saving to ROM"));
1155 } else {
1156 ImGui::SetTooltip(tr("No changes to preview"));
1157 }
1158 }
1159
1160 ImGui::BeginDisabled(!has_unsaved);
1161 if (ImGui::Button(ICON_MD_UNDO " Discard All Changes", ImVec2(-1, 0))) {
1164 .c_str());
1165 }
1166 ImGui::EndDisabled();
1167
1168 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
1169 if (has_unsaved) {
1170 ImGui::SetTooltip(tr("Discard all unsaved changes"));
1171 } else {
1172 ImGui::SetTooltip(tr("No changes to discard"));
1173 }
1174 }
1175
1176 // Confirmation popup for discard
1177 if (ImGui::BeginPopupModal(
1180 .c_str(),
1181 nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
1182 ImGui::Text(tr("Discard all unsaved changes?"));
1183 ImGui::TextColored(gui::GetWarningColor(),
1184 tr("This will revert %zu modified colors."),
1185 modified_count);
1186 ImGui::Separator();
1187
1188 if (ImGui::Button(tr("Discard"), ImVec2(120, 0))) {
1190 ImGui::CloseCurrentPopup();
1191 }
1192 ImGui::SameLine();
1193 if (ImGui::Button(tr("Cancel"), ImVec2(120, 0))) {
1194 ImGui::CloseCurrentPopup();
1195 }
1196 ImGui::EndPopup();
1197 }
1198
1199 // Error popup for save failures
1200 if (ImGui::BeginPopupModal(gui::MakePopupId(gui::EditorNames::kPalette,
1202 .c_str(),
1203 nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
1204 ImGui::TextColored(gui::GetErrorColor(), tr("Failed to save changes"));
1205 ImGui::Text(tr("An error occurred while saving to ROM."));
1206 ImGui::Separator();
1207
1208 if (ImGui::Button(tr("OK"), ImVec2(120, 0))) {
1209 ImGui::CloseCurrentPopup();
1210 }
1211 ImGui::EndPopup();
1212 }
1213
1214 ImGui::Separator();
1215
1216 // Panel management handled globally in the sidebar/menu system.
1217}
1218
1220 // Current color picker with more options
1221 ImGui::BeginGroup();
1222 ImGui::Text(tr("Current Color"));
1223 gui::SnesColorEdit4("##CurrentColorPicker", &current_color_,
1225
1226 char buf[64];
1227 auto col = current_color_.rgb();
1228 int cr = F32_TO_INT8_SAT(col.x / 255.0f);
1229 int cg = F32_TO_INT8_SAT(col.y / 255.0f);
1230 int cb = F32_TO_INT8_SAT(col.z / 255.0f);
1231
1232 CustomFormatString(buf, IM_ARRAYSIZE(buf), "RGB: %d, %d, %d", cr, cg, cb);
1233 ImGui::Text("%s", buf);
1234
1235 CustomFormatString(buf, IM_ARRAYSIZE(buf), "SNES: $%04X",
1237 ImGui::Text("%s", buf);
1238
1239 if (ImGui::Button(tr("Copy to Clipboard"), ImVec2(-1, 0))) {
1240 SetClipboardText(buf);
1241 }
1242 ImGui::EndGroup();
1243
1244 ImGui::Separator();
1245
1246 // Recently used colors
1247 ImGui::Text(tr("Recently Used Colors"));
1248 if (recently_used_colors_.empty()) {
1249 ImGui::TextDisabled(tr("No recently used colors yet"));
1250 } else {
1251 for (int i = 0; i < recently_used_colors_.size(); i++) {
1252 PushID(i);
1253 if (i % 8 != 0)
1254 SameLine();
1255 ImVec4 displayColor =
1257 if (ImGui::ColorButton("##recent", displayColor, kPalButtonFlags,
1258 ImVec2(28, 28))) {
1259 // Set as current color
1261 }
1262 if (ImGui::IsItemHovered()) {
1263 ImGui::SetTooltip(tr("SNES: $%04X"), recently_used_colors_[i].snes());
1264 }
1265 PopID();
1266 }
1267 }
1268}
1269
1271 ImGui::TextWrapped(tr(
1272 "Create your own custom color palette for reference. "
1273 "Colors can be added from any palette group or created from scratch."));
1274
1275 ImGui::Separator();
1276
1277 // Custom palette color grid
1278 if (custom_palette_.empty()) {
1279 ImGui::TextDisabled(tr("Your custom palette is empty."));
1280 ImGui::Text(tr("Click + to add colors or drag colors from any palette."));
1281 } else {
1282 for (int i = 0; i < custom_palette_.size(); i++) {
1283 PushID(i);
1284 if (i > 0 && i % 16 != 0)
1285 SameLine(0.0f, 2.0f);
1286
1287 // Enhanced color button with context menu and drag-drop support
1288 ImVec4 displayColor = gui::ConvertSnesColorToImVec4(custom_palette_[i]);
1289 bool open_color_picker =
1290 ImGui::ColorButton(absl::StrFormat("##customPal%d", i).c_str(),
1291 displayColor, kPalButtonFlags, ImVec2(28, 28));
1292 const ImVec2 swatch_min = ImGui::GetItemRectMin();
1293 const ImVec2 swatch_max = ImGui::GetItemRectMax();
1294 if (ImGui::IsItemHovered() &&
1295 ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
1296 open_color_picker = true;
1297 }
1298
1299 if (open_color_picker) {
1303 "PanelCustomPaletteColorEdit")
1304 .c_str());
1305 }
1306
1307 if (BeginPopupContextItem()) {
1308 // Edit color directly in the popup
1309 SnesColor original_color = custom_palette_[i];
1310 if (gui::SnesColorEdit4("Edit Color", &custom_palette_[i],
1312 // Color was changed, add to recently used
1314 }
1315
1316 if (ImGui::Button(tr("Delete"), ImVec2(-1, 0))) {
1317 custom_palette_.erase(custom_palette_.begin() + i);
1318 ImGui::CloseCurrentPopup();
1319 }
1320 ImGui::EndPopup();
1321 }
1322
1323 if (AcceptImGuiColorDrop(&custom_palette_[i], swatch_min, swatch_max)) {
1325 }
1326
1327 PopID();
1328 }
1329 }
1330
1331 ImGui::Separator();
1332
1333 // Buttons for palette management
1334 if (ImGui::Button(ICON_MD_ADD " Add Color")) {
1335 custom_palette_.push_back(SnesColor(0x7FFF));
1336 }
1337
1338 ImGui::SameLine();
1339 if (ImGui::Button(ICON_MD_DELETE " Clear All")) {
1340 custom_palette_.clear();
1341 }
1342
1343 ImGui::SameLine();
1344 if (ImGui::Button(ICON_MD_CONTENT_COPY " Export")) {
1345 std::string clipboard;
1346 for (const auto& color : custom_palette_) {
1347 clipboard += absl::StrFormat("$%04X,", color.snes());
1348 }
1349 if (!clipboard.empty()) {
1350 clipboard.pop_back(); // Remove trailing comma
1351 }
1352 SetClipboardText(clipboard.c_str());
1353 }
1354 if (ImGui::IsItemHovered()) {
1355 ImGui::SetTooltip(tr("Copy palette as comma-separated SNES values"));
1356 }
1357
1358 // Color picker popup for custom palette editing
1359 if (ImGui::BeginPopup(gui::MakePopupId(gui::EditorNames::kPalette,
1360 "PanelCustomPaletteColorEdit")
1361 .c_str())) {
1362 if (edit_palette_index_ >= 0 &&
1366 "Edit Color", &custom_palette_[edit_palette_index_],
1367 kColorPopupFlags | ImGuiColorEditFlags_PickerHueWheel)) {
1368 // Color was changed, add to recently used
1370 }
1371 }
1372 ImGui::EndPopup();
1373 }
1374}
1375
1376void PaletteEditor::JumpToPalette(const std::string& group_name,
1377 int palette_index) {
1379 return;
1380 }
1382 auto* window_manager = dependencies_.window_manager;
1383 const size_t session_id = dependencies_.session_id;
1384
1385 // Show and focus the appropriate card
1386 if (group_name == "ow_main") {
1387 window_manager->OpenWindow(session_id, "palette.ow_main");
1388 if (ow_main_panel_) {
1391 }
1392 } else if (group_name == "ow_animated") {
1393 window_manager->OpenWindow(session_id, "palette.ow_animated");
1394 if (ow_anim_panel_) {
1397 }
1398 } else if (group_name == "dungeon_main") {
1399 window_manager->OpenWindow(session_id, "palette.dungeon_main");
1400 if (dungeon_main_panel_) {
1403 }
1404 } else if (group_name == "global_sprites") {
1405 window_manager->OpenWindow(session_id, "palette.sprites");
1409 }
1410 } else if (group_name == "sprites_aux1") {
1411 window_manager->OpenWindow(session_id, "palette.sprites_aux1");
1412 if (sprite_aux1_panel_) {
1415 }
1416 } else if (group_name == "sprites_aux2") {
1417 window_manager->OpenWindow(session_id, "palette.sprites_aux2");
1418 if (sprite_aux2_panel_) {
1421 }
1422 } else if (group_name == "sprites_aux3") {
1423 window_manager->OpenWindow(session_id, "palette.sprites_aux3");
1424 if (sprite_aux3_panel_) {
1427 }
1428 } else if (group_name == "armors") {
1429 window_manager->OpenWindow(session_id, "palette.equipment");
1430 if (equipment_panel_) {
1433 }
1434 }
1435
1436 // Show control panel too for easy navigation
1437 window_manager->OpenWindow(session_id, "palette.control_panel");
1438}
1439
1440// ============================================================================
1441// Category and Search UI Methods
1442// ============================================================================
1443
1445 ImGui::SetNextItemWidth(-1);
1446 if (ImGui::InputTextWithHint("##PaletteSearch",
1447 ICON_MD_SEARCH " Search palettes...",
1448 search_buffer_, sizeof(search_buffer_))) {
1449 // Search text changed - UI will update automatically
1450 }
1451}
1452
1453bool PaletteEditor::PassesSearchFilter(const std::string& group_name) const {
1454 if (search_buffer_[0] == '\0')
1455 return true;
1456
1457 // Check if group name or display name matches
1458 return gui::FuzzyMatch(search_buffer_, group_name) ||
1460}
1461
1462bool* PaletteEditor::GetShowFlagForGroup(const std::string& group_name) {
1463 if (group_name == "ow_main")
1464 return &show_ow_main_panel_;
1465 if (group_name == "ow_animated")
1467 if (group_name == "dungeon_main")
1469 if (group_name == "global_sprites")
1470 return &show_sprite_panel_;
1471 if (group_name == "sprites_aux1")
1473 if (group_name == "sprites_aux2")
1475 if (group_name == "sprites_aux3")
1477 if (group_name == "armors")
1478 return &show_equipment_panel_;
1479 return nullptr;
1480}
1481
1483 // Search bar at top
1484 DrawSearchBar();
1485 ImGui::Separator();
1486
1487 const auto& categories = GetPaletteCategories();
1488
1489 for (size_t cat_idx = 0; cat_idx < categories.size(); cat_idx++) {
1490 const auto& cat = categories[cat_idx];
1491
1492 // Check if any items in category match search
1493 bool has_visible_items = false;
1494 for (const auto& group_name : cat.group_names) {
1495 if (PassesSearchFilter(group_name)) {
1496 has_visible_items = true;
1497 break;
1498 }
1499 }
1500
1501 if (!has_visible_items)
1502 continue;
1503
1504 ImGui::PushID(static_cast<int>(cat_idx));
1505
1506 // Collapsible header for category with icon
1507 std::string header_text =
1508 absl::StrFormat("%s %s", cat.icon, cat.display_name);
1509 bool open = ImGui::CollapsingHeader(header_text.c_str(),
1510 ImGuiTreeNodeFlags_DefaultOpen);
1511
1512 if (open) {
1513 ImGui::Indent(10.0f);
1514 for (const auto& group_name : cat.group_names) {
1515 if (!PassesSearchFilter(group_name))
1516 continue;
1517
1518 bool* show_flag = GetShowFlagForGroup(group_name);
1519 if (show_flag) {
1520 std::string label = GetGroupDisplayName(group_name);
1521
1522 // Show modified indicator
1523 bool is_modified =
1525 if (is_modified) {
1526 label += " *";
1527 }
1528 std::optional<gui::StyleColorGuard> mod_guard;
1529 if (is_modified) {
1530 mod_guard.emplace(ImGuiCol_Text, gui::GetWarningColor());
1531 }
1532
1533 ImGui::Checkbox(label.c_str(), show_flag);
1534 mod_guard.reset();
1535 }
1536 }
1537 ImGui::Unindent(10.0f);
1538 }
1539 ImGui::PopID();
1540 }
1541
1542 ImGui::Separator();
1543
1544 // Utilities section
1545 ImGui::Text(tr("Utilities:"));
1546 ImGui::Indent(10.0f);
1547 ImGui::Checkbox(tr("Quick Access"), &show_quick_access_);
1548 ImGui::Checkbox(tr("Custom Palette"), &show_custom_palette_);
1549 ImGui::Unindent(10.0f);
1550}
1551
1552} // namespace editor
1553} // namespace yaze
#define F32_TO_INT8_SAT(_VAL)
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:28
project::ResourceLabelManager * resource_label()
Definition rom.h:180
void set_dirty(bool dirty)
Definition rom.h:157
bool is_loaded() const
Definition rom.h:155
bool loaded() const
Check if the manifest has been loaded.
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.
virtual void SetDependencies(const EditorDependencies &deps)
Definition editor.h:250
zelda3::GameData * game_data() const
Definition editor.h:320
EditorDependencies dependencies_
Definition editor.h:333
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 SetDependencies(const EditorDependencies &deps) override
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)
Base class for palette group editing cards.
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)
Base interface for all logical window content components.
Central registry for all editor cards with session awareness and dependency injection.
WindowContent * GetWindowContent(const std::string &window_id)
Get a WindowContent instance by ID.
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 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).
const Theme & GetCurrentTheme() const
static ThemeManager & Get()
#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
bool AcceptImGuiColorDrop(SnesColor *color, ImVec2 min, ImVec2 max)
Panel * ResolveSessionPalettePanel(WorkspaceWindowManager *window_manager, size_t session_id, const char *panel_id, Rom *rom)
int CustomFormatString(char *buf, size_t buf_size, const char *fmt,...)
absl::Status ValidateHackManifestSaveConflicts(const core::HackManifest &manifest, project::RomWritePolicy write_policy, const std::vector< std::pair< uint32_t, uint32_t > > &ranges, absl::string_view save_scope, const char *log_tag, ToastManager *toast_manager)
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
ImVec4 ConvertColorToImVec4(const Color &color)
Definition color.h:134
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:23
ImVec4 GetErrorColor()
Definition ui_helpers.cc:59
IMGUI_API bool SnesColorEdit4(absl::string_view label, gfx::SnesColor *color, ImGuiColorEditFlags flags)
Definition color.cc:58
ImVec4 GetWarningColor()
Definition ui_helpers.cc:54
std::string MakePopupId(size_t session_id, const std::string &editor_name, const std::string &popup_name)
Generate session-aware popup IDs to prevent conflicts in multi-editor layouts.
Definition popup_id.h:23
gfx::SnesColor ConvertImVec4ToSnesColor(const ImVec4 &color)
Convert standard ImVec4 to SnesColor.
Definition color.cc:36
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
Unified dependency container for all editor types.
Definition editor.h:169
project::YazeProject * project
Definition editor.h:173
WorkspaceWindowManager * window_manager
Definition editor.h:181
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:2371
void SelectableLabelWithNameEdit(bool selected, const std::string &type, const std::string &key, const std::string &defaultValue)
Definition project.cc:2346
RomWritePolicy write_policy
Definition project.h:110
core::HackManifest hack_manifest
Definition project.h:212
gfx::PaletteGroupMap palette_groups
Definition game_data.h:92