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#include <functional>
6
7#include "absl/status/status.h"
8#include "absl/strings/str_cat.h"
9#include "absl/strings/str_format.h"
20#include "app/gui/core/color.h"
21#include "app/gui/core/icons.h"
23#include "app/gui/core/search.h"
28#include "core/project.h"
29#include "imgui/imgui.h"
30
31#include <cstring>
32
33namespace yaze {
34namespace editor {
35
36using ImGui::AcceptDragDropPayload;
37using ImGui::BeginChild;
38using ImGui::BeginDragDropTarget;
39using ImGui::BeginGroup;
40using ImGui::BeginPopup;
41using ImGui::BeginPopupContextItem;
42using ImGui::Button;
43using ImGui::ColorButton;
44using ImGui::ColorPicker4;
45using ImGui::EndChild;
46using ImGui::EndDragDropTarget;
47using ImGui::EndGroup;
48using ImGui::EndPopup;
49using ImGui::GetStyle;
50using ImGui::OpenPopup;
51using ImGui::PopID;
52using ImGui::PushID;
53using ImGui::SameLine;
54using ImGui::Selectable;
55using ImGui::Separator;
56using ImGui::SetClipboardText;
57using ImGui::Text;
58
59using namespace gfx;
60
61constexpr ImGuiTableFlags kPaletteTableFlags =
62 ImGuiTableFlags_Reorderable | ImGuiTableFlags_Resizable |
63 ImGuiTableFlags_SizingStretchSame | ImGuiTableFlags_Hideable;
64
65constexpr ImGuiColorEditFlags kPalNoAlpha = ImGuiColorEditFlags_NoAlpha;
66
67constexpr ImGuiColorEditFlags kPalButtonFlags = ImGuiColorEditFlags_NoAlpha |
68 ImGuiColorEditFlags_NoPicker |
69 ImGuiColorEditFlags_NoTooltip;
70
71constexpr ImGuiColorEditFlags kColorPopupFlags =
72 ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoAlpha |
73 ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_DisplayHSV |
74 ImGuiColorEditFlags_DisplayHex;
75
76namespace {
77void DrawColorDropTargetOutline(ImVec2 min, ImVec2 max) {
78 const auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
79 ImGui::GetForegroundDrawList()->AddRect(
80 min, max, ImGui::GetColorU32(gui::ConvertColorToImVec4(theme.accent)),
81 0.0f, 0, 2.0f);
82}
83
84bool AcceptImGuiColorDrop(SnesColor* color, ImVec2 min, ImVec2 max) {
85 if (!color || !BeginDragDropTarget()) {
86 return false;
87 }
88
89 bool changed = false;
90 constexpr ImGuiDragDropFlags kFlags =
91 ImGuiDragDropFlags_AcceptBeforeDelivery |
92 ImGuiDragDropFlags_AcceptNoDrawDefaultRect |
93 ImGuiDragDropFlags_AcceptNoPreviewTooltip;
94 auto accept = [&](const char* type, size_t components) {
95 if (changed) {
96 return;
97 }
98 if (const ImGuiPayload* payload = AcceptDragDropPayload(type, kFlags)) {
100 if (payload->IsDelivery()) {
101 ImVec4 dropped(0, 0, 0, 1);
102 std::memcpy(&dropped.x, payload->Data, sizeof(float) * components);
103 if (components == 3) {
104 dropped.w = 1.0f;
105 }
106 *color = SnesColor(dropped);
107 changed = true;
108 }
109 }
110 };
111
112 accept(IMGUI_PAYLOAD_TYPE_COLOR_3F, 3);
113 accept(IMGUI_PAYLOAD_TYPE_COLOR_4F, 4);
114 EndDragDropTarget();
115 return changed;
116}
117
118int CustomFormatString(char* buf, size_t buf_size, const char* fmt, ...) {
119 va_list args;
120 va_start(args, fmt);
121#ifdef IMGUI_USE_STB_SPRINTF
122 int w = stbsp_vsnprintf(buf, (int)buf_size, fmt, args);
123#else
124 int w = vsnprintf(buf, buf_size, fmt, args);
125#endif
126 va_end(args);
127 if (buf == nullptr)
128 return w;
129 if (w == -1 || w >= (int)buf_size)
130 w = (int)buf_size - 1;
131 buf[w] = 0;
132 return w;
133}
134
135static inline float color_saturate(float f) {
136 return (f < 0.0f) ? 0.0f : (f > 1.0f) ? 1.0f : f;
137}
138
139#define F32_TO_INT8_SAT(_VAL) \
140 ((int)(color_saturate(_VAL) * 255.0f + \
141 0.5f)) // Saturated, always output 0..255
142
143// Right-click menu for a custom palette swatch. Returns true when the color
144// was removed; the caller must stop using `index` for this frame.
146 std::vector<gfx::SnesColor>& colors, int index,
147 const std::function<void(const gfx::SnesColor&)>& on_edited) {
148 if (!BeginPopupContextItem()) {
149 return false;
150 }
151 // Edit color directly in the popup
152 if (gui::SnesColorEdit4("Edit Color", &colors[index], kColorPopupFlags)) {
153 on_edited(colors[index]);
154 }
155 Separator();
156 gui::SnesColorCopyMenuItems(colors[index]);
157 Separator();
158 bool removed = false;
160 "Remove from Custom Palette", ICON_MD_DELETE,
161 [&removed]() { removed = true; },
162 /*confirm=*/false));
163 EndPopup();
164 if (removed) {
165 colors.erase(colors.begin() + index);
166 }
167 return removed;
168}
169} // namespace
170
188absl::Status DisplayPalette(gfx::SnesPalette& palette, bool loaded) {
189 static ImVec4 color = ImVec4(0, 0, 0, 1.0f);
190 static ImVec4 current_palette[256] = {};
191 static int current_palette_count = 0;
192 ImGuiColorEditFlags misc_flags = ImGuiColorEditFlags_AlphaPreview |
193 ImGuiColorEditFlags_NoDragDrop |
194 ImGuiColorEditFlags_NoOptions;
195
196 // Reload palette colors whenever the palette data is available.
197 if (loaded) {
198 current_palette_count =
199 std::min<int>(static_cast<int>(palette.size()),
200 static_cast<int>(IM_ARRAYSIZE(current_palette)));
201
202 for (int n = 0; n < current_palette_count; ++n) {
203 const auto palette_color = palette[static_cast<size_t>(n)];
204 current_palette[n].x = palette_color.rgb().x / 255.0f;
205 current_palette[n].y = palette_color.rgb().y / 255.0f;
206 current_palette[n].z = palette_color.rgb().z / 255.0f;
207 current_palette[n].w = 1.0f;
208 }
209 for (int n = current_palette_count;
210 n < static_cast<int>(IM_ARRAYSIZE(current_palette)); ++n) {
211 current_palette[n] = ImVec4(0, 0, 0, 1.0f);
212 }
213 } else {
214 current_palette_count = 0;
215 }
216
217 static ImVec4 backup_color;
218 bool open_popup = ColorButton("MyColor##3b", color, misc_flags);
219 SameLine(0, GetStyle().ItemInnerSpacing.x);
220 open_popup |= Button(tr("Palette"));
221 if (open_popup) {
224 .c_str());
225 backup_color = color;
226 }
227
230 .c_str())) {
231 TEXT_WITH_SEPARATOR("Current Overworld Palette");
232 ColorPicker4("##picker", (float*)&color,
233 misc_flags | ImGuiColorEditFlags_NoSidePreview |
234 ImGuiColorEditFlags_NoSmallPreview);
235 SameLine();
236
237 BeginGroup(); // Lock X position
238 Text(tr("Current ==>"));
239 SameLine();
240 Text(tr("Previous"));
241
242 if (Button(tr("Update Map Palette"))) {}
243
244 ColorButton(
245 "##current", color,
246 ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf,
247 ImVec2(60, 40));
248 SameLine();
249
250 if (ColorButton(
251 "##previous", backup_color,
252 ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf,
253 ImVec2(60, 40)))
254 color = backup_color;
255
256 // List of Colors in Overworld Palette
257 Separator();
258 Text(tr("Palette"));
259 if (current_palette_count <= 0) {
260 ImGui::TextDisabled(tr("No palette entries loaded."));
261 }
262 for (int n = 0; n < current_palette_count; n++) {
263 PushID(n);
264 if ((n % 8) != 0)
265 SameLine(0.0f, GetStyle().ItemSpacing.y);
266
267 if (ColorButton("##palette", current_palette[n], kPalButtonFlags,
268 ImVec2(20, 20)))
269 color = ImVec4(current_palette[n].x, current_palette[n].y,
270 current_palette[n].z, color.w); // Preserve alpha!
271
272 if (BeginDragDropTarget()) {
273 if (const ImGuiPayload* payload =
274 AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))
275 memcpy((float*)&current_palette[n], payload->Data, sizeof(float) * 3);
276 if (const ImGuiPayload* payload =
277 AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))
278 memcpy((float*)&current_palette[n], payload->Data, sizeof(float) * 4);
279 EndDragDropTarget();
280 }
281
282 PopID();
283 }
284 EndGroup();
285 EndPopup();
286 }
287
288 return absl::OkStatus();
289}
290
292 // Register all panels with WorkspaceWindowManager (done once during
293 // initialization)
295 return;
296 auto* window_manager = dependencies_.window_manager;
297 const size_t session_id = dependencies_.session_id;
298
299 window_manager->RegisterPanel(
300 {.card_id = "palette.control_panel",
301 .display_name = "Palette",
302 .window_title = " Palette",
303 .icon = ICON_MD_PALETTE,
304 .category = "Palette",
306 .shortcut_hint = "Ctrl+Shift+P",
307 .visibility_flag = &show_control_panel_,
308 .priority = 10,
309 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
310 .disabled_tooltip = "Load a ROM first"});
311
312 window_manager->RegisterPanel(
313 {.card_id = "palette.ow_main",
314 .display_name = "Overworld Main",
315 .window_title = " Overworld Main",
316 .icon = ICON_MD_LANDSCAPE,
317 .category = "Palette",
319 .shortcut_hint = "Ctrl+Alt+1",
320 .visibility_flag = &show_ow_main_panel_,
321 .priority = 20,
322 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
323 .disabled_tooltip = "Load a ROM first"});
324
325 window_manager->RegisterPanel(
326 {.card_id = "palette.ow_animated",
327 .display_name = "Overworld Animated",
328 .window_title = " Overworld Animated",
329 .icon = ICON_MD_WATER,
330 .category = "Palette",
332 .shortcut_hint = "Ctrl+Alt+2",
333 .visibility_flag = &show_ow_animated_panel_,
334 .priority = 30,
335 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
336 .disabled_tooltip = "Load a ROM first"});
337
338 window_manager->RegisterPanel(
339 {.card_id = "palette.dungeon_main",
340 .display_name = "Dungeon Main",
341 .window_title = " Dungeon Main",
342 .icon = ICON_MD_CASTLE,
343 .category = "Palette",
345 .shortcut_hint = "Ctrl+Alt+3",
346 .visibility_flag = &show_dungeon_main_panel_,
347 .priority = 40,
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",
353 .display_name = "Global Sprite Palettes",
354 .window_title = " SNES Palette",
355 .icon = ICON_MD_PETS,
356 .category = "Palette",
358 .shortcut_hint = "Ctrl+Alt+4",
359 .visibility_flag = &show_sprite_panel_,
360 .priority = 50,
361 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
362 .disabled_tooltip = "Load a ROM first"});
363
364 window_manager->RegisterPanel(
365 {.card_id = "palette.sprites_aux1",
366 .display_name = "Sprites Aux 1",
367 .window_title = " Sprites Aux 1",
368 .icon = ICON_MD_FILTER_1,
369 .category = "Palette",
371 .shortcut_hint = "Ctrl+Alt+7",
372 .visibility_flag = &show_sprites_aux1_panel_,
373 .priority = 51,
374 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
375 .disabled_tooltip = "Load a ROM first"});
376
377 window_manager->RegisterPanel(
378 {.card_id = "palette.sprites_aux2",
379 .display_name = "Sprites Aux 2",
380 .window_title = " Sprites Aux 2",
381 .icon = ICON_MD_FILTER_2,
382 .category = "Palette",
384 .shortcut_hint = "Ctrl+Alt+8",
385 .visibility_flag = &show_sprites_aux2_panel_,
386 .priority = 52,
387 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
388 .disabled_tooltip = "Load a ROM first"});
389
390 window_manager->RegisterPanel(
391 {.card_id = "palette.sprites_aux3",
392 .display_name = "Sprites Aux 3",
393 .window_title = " Sprites Aux 3",
394 .icon = ICON_MD_FILTER_3,
395 .category = "Palette",
397 .shortcut_hint = "Ctrl+Alt+9",
398 .visibility_flag = &show_sprites_aux3_panel_,
399 .priority = 53,
400 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
401 .disabled_tooltip = "Load a ROM first"});
402
403 window_manager->RegisterPanel(
404 {.card_id = "palette.equipment",
405 .display_name = "Equipment Palettes",
406 .window_title = " Equipment Palettes",
407 .icon = ICON_MD_SHIELD,
408 .category = "Palette",
410 .shortcut_hint = "Ctrl+Alt+5",
411 .visibility_flag = &show_equipment_panel_,
412 .priority = 60,
413 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
414 .disabled_tooltip = "Load a ROM first"});
415
416 window_manager->RegisterPanel(
417 {.card_id = "palette.quick_access",
418 .display_name = "Quick Access",
419 .window_title = " Color Harmony",
420 .icon = ICON_MD_COLOR_LENS,
421 .category = "Palette",
423 .shortcut_hint = "Ctrl+Alt+Q",
424 .visibility_flag = &show_quick_access_,
425 .priority = 70,
426 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
427 .disabled_tooltip = "Load a ROM first"});
428
429 window_manager->RegisterPanel(
430 {.card_id = "palette.custom",
431 .display_name = "Custom Palette",
432 .window_title = " Palette Editor",
433 .icon = ICON_MD_BRUSH,
434 .category = "Palette",
436 .shortcut_hint = "Ctrl+Alt+C",
437 .visibility_flag = &show_custom_palette_,
438 .priority = 80,
439 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
440 .disabled_tooltip = "Load a ROM first"});
441
442 // Show control panel by default when Palette Editor is activated
443 window_manager->OpenWindow(session_id, "palette.control_panel");
444}
445
446// ============================================================================
447// Helper Panel Classes
448// ============================================================================
449
451 public:
452 explicit PaletteControlPanel(std::function<void()> draw_callback)
453 : draw_callback_(std::move(draw_callback)) {}
454
455 std::string GetId() const override { return "palette.control_panel"; }
456 std::string GetDisplayName() const override { return "Palette"; }
457 std::string GetIcon() const override { return ICON_MD_PALETTE; }
458 std::string GetEditorCategory() const override { return "Palette"; }
462 int GetPriority() const override { return 10; }
463 float GetPreferredWidth() const override { return 900.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
477 public:
478 explicit QuickAccessPalettePanel(std::function<void()> draw_callback)
479 : draw_callback_(std::move(draw_callback)) {}
480
481 std::string GetId() const override { return "palette.quick_access"; }
482 std::string GetDisplayName() const override { return "Quick Access"; }
483 std::string GetIcon() const override { return ICON_MD_COLOR_LENS; }
484 std::string GetEditorCategory() const override { return "Palette"; }
488 int GetPriority() const override { return 70; }
489 float GetPreferredWidth() const override { return 340.0f; }
490
491 void Draw(bool* p_open) override {
492 if (p_open && !*p_open)
493 return;
494 if (draw_callback_)
496 }
497
498 private:
499 std::function<void()> draw_callback_;
500};
501
503 public:
504 explicit CustomPalettePanel(std::function<void()> draw_callback)
505 : draw_callback_(std::move(draw_callback)) {}
506
507 std::string GetId() const override { return "palette.custom"; }
508 std::string GetDisplayName() const override { return "Custom Palette"; }
509 std::string GetIcon() const override { return ICON_MD_BRUSH; }
510 std::string GetEditorCategory() const override { return "Palette"; }
514 int GetPriority() const override { return 80; }
515 float GetPreferredWidth() const override { return 420.0f; }
516
517 void Draw(bool* p_open) override {
518 if (p_open && !*p_open)
519 return;
520 if (draw_callback_)
522 }
523
524 private:
525 std::function<void()> draw_callback_;
526};
527
533
534namespace {
535template <typename Panel>
537 size_t session_id, const char* panel_id,
538 Rom* rom) {
539 auto* panel = dynamic_cast<Panel*>(
540 window_manager->GetWindowContent(session_id, panel_id));
541 return panel != nullptr && panel->IsOwnedByRom(rom) ? panel : nullptr;
542}
543} // namespace
544
546 auto* window_manager = dependencies_.window_manager;
547 if (window_manager == nullptr) {
548 ow_main_panel_ = nullptr;
549 ow_anim_panel_ = nullptr;
550 dungeon_main_panel_ = nullptr;
551 sprite_global_panel_ = nullptr;
552 sprite_aux1_panel_ = nullptr;
553 sprite_aux2_panel_ = nullptr;
554 sprite_aux3_panel_ = nullptr;
555 equipment_panel_ = nullptr;
556 return;
557 }
558
559 const size_t session_id = dependencies_.session_id;
560 ow_main_panel_ = ResolveSessionPalettePanel<OverworldMainPalettePanel>(
561 window_manager, session_id, "palette.ow_main", rom_);
562 ow_anim_panel_ = ResolveSessionPalettePanel<OverworldAnimatedPalettePanel>(
563 window_manager, session_id, "palette.ow_animated", rom_);
564 dungeon_main_panel_ = ResolveSessionPalettePanel<DungeonMainPalettePanel>(
565 window_manager, session_id, "palette.dungeon_main", rom_);
566 sprite_global_panel_ = ResolveSessionPalettePanel<SpritePalettePanel>(
567 window_manager, session_id, "palette.global_sprites", rom_);
568 sprite_aux1_panel_ = ResolveSessionPalettePanel<SpritesAux1PalettePanel>(
569 window_manager, session_id, "palette.sprites_aux1", rom_);
570 sprite_aux2_panel_ = ResolveSessionPalettePanel<SpritesAux2PalettePanel>(
571 window_manager, session_id, "palette.sprites_aux2", rom_);
572 sprite_aux3_panel_ = ResolveSessionPalettePanel<SpritesAux3PalettePanel>(
573 window_manager, session_id, "palette.sprites_aux3", rom_);
574 equipment_panel_ = ResolveSessionPalettePanel<EquipmentPalettePanel>(
575 window_manager, session_id, "palette.armors", rom_);
576}
577
579 // WorkspaceWindowManager owns these panels and may have already destroyed
580 // them while the session-close event is still rebinding editor dependencies.
582 const auto apply = [this](PaletteGroupPanel* panel) {
583 if (panel == nullptr) {
584 return;
585 }
586 panel->SetToastManager(dependencies_.toast_manager);
587 panel->SetProject(dependencies_.project);
588 };
589 apply(ow_main_panel_);
590 apply(ow_anim_panel_);
591 apply(dungeon_main_panel_);
593 apply(sprite_aux1_panel_);
594 apply(sprite_aux2_panel_);
595 apply(sprite_aux3_panel_);
596 apply(equipment_panel_);
597}
598
599absl::Status PaletteEditor::Load() {
600 gfx::ScopedTimer timer("PaletteEditor::Load");
601
602 if (!rom() || !rom()->is_loaded()) {
603 return absl::NotFoundError("ROM not open, no palettes to display");
604 }
605
606 // Initialize the labels
607 for (int i = 0; i < kNumPalettes; i++) {
609 "Palette Group Name", std::to_string(i),
610 std::string(kPaletteGroupNames[i]));
611 }
612
613 // Initialize the centralized PaletteManager with GameData
614 // This must be done before creating any palette cards
615 if (game_data()) {
617 } else {
618 // Fallback to legacy ROM-only initialization
620 }
621
622 // Register WindowContent instances with WorkspaceWindowManager
624 auto* window_manager = dependencies_.window_manager;
625
626 // Create and register palette panels
627 // Note: WorkspaceWindowManager takes ownership via unique_ptr
628
629 // Overworld Main
630 auto ow_main =
631 std::make_unique<OverworldMainPalettePanel>(rom_, game_data());
632 ow_main_panel_ = ow_main.get();
633 window_manager->RegisterWindowContent(std::move(ow_main));
634
635 // Overworld Animated
636 auto ow_anim =
637 std::make_unique<OverworldAnimatedPalettePanel>(rom_, game_data());
638 ow_anim_panel_ = ow_anim.get();
639 window_manager->RegisterWindowContent(std::move(ow_anim));
640
641 // Dungeon Main
642 auto dungeon_main =
643 std::make_unique<DungeonMainPalettePanel>(rom_, game_data());
644 dungeon_main_panel_ = dungeon_main.get();
645 window_manager->RegisterWindowContent(std::move(dungeon_main));
646
647 // Global Sprites
648 auto sprite_global =
649 std::make_unique<SpritePalettePanel>(rom_, game_data());
650 sprite_global_panel_ = sprite_global.get();
651 window_manager->RegisterWindowContent(std::move(sprite_global));
652
653 // Sprites Aux 1
654 auto sprite_aux1 =
655 std::make_unique<SpritesAux1PalettePanel>(rom_, game_data());
656 sprite_aux1_panel_ = sprite_aux1.get();
657 window_manager->RegisterWindowContent(std::move(sprite_aux1));
658
659 // Sprites Aux 2
660 auto sprite_aux2 =
661 std::make_unique<SpritesAux2PalettePanel>(rom_, game_data());
662 sprite_aux2_panel_ = sprite_aux2.get();
663 window_manager->RegisterWindowContent(std::move(sprite_aux2));
664
665 // Sprites Aux 3
666 auto sprite_aux3 =
667 std::make_unique<SpritesAux3PalettePanel>(rom_, game_data());
668 sprite_aux3_panel_ = sprite_aux3.get();
669 window_manager->RegisterWindowContent(std::move(sprite_aux3));
670
671 // Equipment
672 auto equipment = std::make_unique<EquipmentPalettePanel>(rom_, game_data());
673 equipment_panel_ = equipment.get();
674 window_manager->RegisterWindowContent(std::move(equipment));
675
677
678 // Register utility panels with callbacks
679 window_manager->RegisterWindowContent(std::make_unique<PaletteControlPanel>(
680 [this]() { DrawControlPanel(); }));
681 window_manager->RegisterWindowContent(
682 std::make_unique<QuickAccessPalettePanel>(
683 [this]() { DrawQuickAccessPanel(); }));
684 window_manager->RegisterWindowContent(std::make_unique<CustomPalettePanel>(
685 [this]() { DrawCustomPalettePanel(); }));
686 }
687
688 return absl::OkStatus();
689}
690
692 auto& manager = gfx::PaletteManager::Get();
693 if (!game_data() || !manager.IsManaging(game_data())) {
694 return absl::FailedPreconditionError(
695 "Cannot save palettes from an inactive ROM session");
696 }
697 return manager.BeginSaveTransaction();
698}
699
703
707
708absl::Status PaletteEditor::Save() {
709 if (!rom_ || !rom_->is_loaded()) {
710 return absl::FailedPreconditionError("ROM not loaded");
711 }
712 if (!game_data() || !gfx::PaletteManager::Get().IsManaging(game_data())) {
713 return absl::FailedPreconditionError(
714 "Cannot save palettes from an inactive ROM session");
715 }
716
717 auto& palette_manager = gfx::PaletteManager::Get();
718 if (dependencies_.project != nullptr &&
720 const auto ranges =
721 palette_manager.GetModifiedColorWriteRanges(game_data());
722 if (!ranges.empty()) {
725 dependencies_.project->rom_metadata.write_policy, ranges, "palettes",
726 "PaletteEditor", dependencies_.toast_manager));
727 }
728 }
729
730 // Delegate to PaletteManager for centralized save.
731 RETURN_IF_ERROR(palette_manager.SaveAllToRom());
732
733 // Mark ROM as needing file save
734 rom_->set_dirty(true);
735
736 return absl::OkStatus();
737}
738
740 const auto& manager = gfx::PaletteManager::Get();
741 return game_data() && manager.IsManaging(game_data()) && manager.CanUndo();
742}
743
745 const auto& manager = gfx::PaletteManager::Get();
746 return game_data() && manager.IsManaging(game_data()) && manager.CanRedo();
747}
748
749absl::Status PaletteEditor::Undo() {
750 if (!game_data() || !gfx::PaletteManager::Get().IsManaging(game_data())) {
751 return absl::FailedPreconditionError(
752 "Cannot undo palettes from an inactive ROM session");
753 }
754
756 return absl::OkStatus();
757}
758
759absl::Status PaletteEditor::Redo() {
760 if (!game_data() || !gfx::PaletteManager::Get().IsManaging(game_data())) {
761 return absl::FailedPreconditionError(
762 "Cannot redo palettes from an inactive ROM session");
763 }
764
766 return absl::OkStatus();
767}
768
769absl::Status PaletteEditor::Update() {
770 // Panel drawing is handled centrally by WorkspaceWindowManager::DrawAllVisiblePanels()
771 // via the WindowContent implementations registered in Load().
772 // No local drawing needed here - this fixes duplicate panel rendering.
773 return absl::OkStatus();
774}
775
777 BeginChild("QuickAccessPalettes", ImVec2(0, 0), true);
778
779 Text(tr("Custom Palette"));
781
782 Separator();
783
784 // Current color picker with more options
785 BeginGroup();
786 Text(tr("Current Color"));
787 gui::SnesColorEdit4("##CurrentColorPicker", &current_color_,
789
790 char buf[64];
791 auto col = current_color_.rgb();
792 int cr = F32_TO_INT8_SAT(col.x / 255.0f);
793 int cg = F32_TO_INT8_SAT(col.y / 255.0f);
794 int cb = F32_TO_INT8_SAT(col.z / 255.0f);
795
796 CustomFormatString(buf, IM_ARRAYSIZE(buf), "RGB: %d, %d, %d", cr, cg, cb);
797 Text("%s", buf);
798
799 CustomFormatString(buf, IM_ARRAYSIZE(buf), "SNES: $%04X",
801 Text("%s", buf);
802
803 if (Button(tr("Copy to Clipboard"))) {
804 SetClipboardText(buf);
805 }
806 EndGroup();
807
808 Separator();
809
810 // Recently used colors
811 Text(tr("Recently Used Colors"));
812 for (int i = 0; i < recently_used_colors_.size(); i++) {
813 PushID(i);
814 if (i % 8 != 0)
815 SameLine();
816 ImVec4 displayColor =
818 if (ImGui::ColorButton("##recent", displayColor)) {
819 // Set as current color
821 }
822 PopID();
823 }
824
825 EndChild();
826}
827
844 if (BeginChild("ColorPalette", ImVec2(0, 40), ImGuiChildFlags_None,
845 ImGuiWindowFlags_HorizontalScrollbar)) {
846 for (int i = 0; i < custom_palette_.size(); i++) {
847 PushID(i);
848 if (i > 0)
849 SameLine(0.0f, GetStyle().ItemSpacing.y);
850
851 // Enhanced color button with context menu and drag-drop support
852 ImVec4 displayColor = gui::ConvertSnesColorToImVec4(custom_palette_[i]);
853 bool open_color_picker = ImGui::ColorButton(
854 absl::StrFormat("##customPal%d", i).c_str(), displayColor);
855 const ImVec2 swatch_min = ImGui::GetItemRectMin();
856 const ImVec2 swatch_max = ImGui::GetItemRectMax();
857 if (ImGui::IsItemHovered() &&
858 ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
859 open_color_picker = true;
860 }
861
862 if (open_color_picker) {
866 "CustomPaletteColorEdit")
867 .c_str());
868 }
869
870 if (DrawCustomColorContextMenu(
872 [this](const SnesColor& c) { AddRecentlyUsedColor(c); })) {
873 PopID();
874 break;
875 }
876
877 if (AcceptImGuiColorDrop(&custom_palette_[i], swatch_min, swatch_max)) {
879 }
880
881 PopID();
882 }
883
884 SameLine();
885 if (ImGui::Button("+")) {
886 custom_palette_.push_back(SnesColor(0x7FFF));
887 }
888
889 SameLine();
890 if (ImGui::Button(tr("Clear"))) {
891 custom_palette_.clear();
892 }
893
894 SameLine();
895 if (ImGui::Button(tr("Export"))) {
896 std::string clipboard;
897 for (const auto& color : custom_palette_) {
898 clipboard += absl::StrFormat("$%04X,", color.snes());
899 }
900 SetClipboardText(clipboard.c_str());
901 }
902 }
903 EndChild();
904
905 // Color picker popup for custom palette editing
906 if (ImGui::BeginPopup(
907 gui::MakePopupId(gui::EditorNames::kPalette, "CustomPaletteColorEdit")
908 .c_str())) {
909 if (edit_palette_index_ >= 0 &&
913 "Edit Color", &custom_palette_[edit_palette_index_],
914 kColorPopupFlags | ImGuiColorEditFlags_PickerHueWheel)) {
915 // Color was changed, add to recently used
917 }
918 }
919 ImGui::EndPopup();
920 }
921}
922
923absl::Status PaletteEditor::DrawPaletteGroup(int category,
924 bool /*right_side*/) {
925 if (!rom()->is_loaded() || !game_data()) {
926 return absl::NotFoundError("ROM not open, no palettes to display");
927 }
928
929 auto palette_group_name = kPaletteGroupNames[category];
930 gfx::PaletteGroup* palette_group =
931 game_data()->palette_groups.get_group(palette_group_name.data());
932 const auto size = palette_group->size();
933
934 for (int j = 0; j < size; j++) {
935 gfx::SnesPalette* palette = palette_group->mutable_palette(j);
936 auto pal_size = palette->size();
937
938 BeginGroup();
939
940 PushID(j);
941 BeginGroup();
943 false, palette_group_name.data(), /*key=*/std::to_string(j),
944 "Unnamed Palette");
945 EndGroup();
946
947 for (int n = 0; n < pal_size; n++) {
948 PushID(n);
949 if (n > 0 && n % 8 != 0)
950 SameLine(0.0f, 2.0f);
951
952 auto popup_id =
953 absl::StrCat(kPaletteCategoryNames[category].data(), j, "_", n);
954
955 ImVec4 displayColor = gui::ConvertSnesColorToImVec4((*palette)[n]);
956 if (ImGui::ColorButton(popup_id.c_str(), displayColor)) {
957 current_color_ = (*palette)[n];
959 }
960 const ImVec2 swatch_min = ImGui::GetItemRectMin();
961 const ImVec2 swatch_max = ImGui::GetItemRectMax();
962 if (ImGui::IsItemHovered() &&
963 ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
964 current_color_ = (*palette)[n];
966 OpenPopup(popup_id.c_str());
967 }
968 {
969 const SnesColor original_color = (*palette)[n];
970 if (AcceptImGuiColorDrop(&(*palette)[n], swatch_min, swatch_max)) {
972 /*group_name=*/std::string(kPaletteGroupNames[category]),
973 /*palette_index=*/j, /*color_index=*/n, original_color,
974 (*palette)[n]);
975 (*palette)[n].set_modified(true);
976 AddRecentlyUsedColor((*palette)[n]);
977 }
978 }
979
980 if (BeginPopupContextItem(popup_id.c_str())) {
981 RETURN_IF_ERROR(HandleColorPopup(*palette, category, j, n))
982 }
983 PopID();
984 }
985 PopID();
986 EndGroup();
987
988 if (j < size - 1) {
989 Separator();
990 }
991 }
992 return absl::OkStatus();
993}
994
996 // Check if color already exists in recently used
997 auto it = std::find_if(
999 [&color](const SnesColor& c) { return c.snes() == color.snes(); });
1000
1001 // If found, remove it to re-add at front
1002 if (it != recently_used_colors_.end()) {
1003 recently_used_colors_.erase(it);
1004 }
1005
1006 // Add at front
1007 recently_used_colors_.insert(recently_used_colors_.begin(), color);
1008
1009 // Limit size
1010 if (recently_used_colors_.size() > 16) {
1011 recently_used_colors_.pop_back();
1012 }
1013}
1014
1016 int j, int n) {
1017 auto col = gfx::ToFloatArray(palette[n]);
1018 auto original_color = palette[n];
1019
1020 if (gui::SnesColorEdit4("Edit Color", &palette[n], kColorPopupFlags)) {
1021 history_.RecordChange(/*group_name=*/std::string(kPaletteGroupNames[i]),
1022 /*palette_index=*/j, /*color_index=*/n,
1023 original_color, palette[n]);
1024 palette[n].set_modified(true);
1025
1026 // Add to recently used colors
1027 AddRecentlyUsedColor(palette[n]);
1028 }
1029
1030 // Color information display
1031 char buf[64];
1032 int cr = F32_TO_INT8_SAT(col[0]);
1033 int cg = F32_TO_INT8_SAT(col[1]);
1034 int cb = F32_TO_INT8_SAT(col[2]);
1035
1036 Text(tr("RGB: %d, %d, %d"), cr, cg, cb);
1037 Text(tr("SNES: $%04X"), palette[n].snes());
1038
1039 Separator();
1040
1041 if (ImGui::BeginMenu(ICON_MD_CONTENT_COPY " Copy as")) {
1042 gui::SnesColorCopyMenuItems(palette[n]);
1043 CustomFormatString(buf, IM_ARRAYSIZE(buf), "(%.3ff, %.3ff, %.3ff)", col[0],
1044 col[1], col[2]);
1045 if (ImGui::MenuItem(ICON_MD_CONTENT_COPY " Copy as Float", buf)) {
1046 SetClipboardText(buf);
1047 }
1048 ImGui::EndMenu();
1049 }
1050
1051 // Add a button to add this color to custom palette
1052 if (ImGui::MenuItem(ICON_MD_ADD " Add to Custom Palette")) {
1053 custom_palette_.push_back(palette[n]);
1054 }
1055
1056 EndPopup();
1057 return absl::OkStatus();
1058}
1059
1061 int index) {
1062 if (index >= palette.size()) {
1063 return absl::InvalidArgumentError("Index out of bounds");
1064 }
1065
1066 // Get the current color
1067 auto color = palette[index];
1068 auto currentColor = color.rgb();
1069 if (ColorPicker4("Color Picker", (float*)&palette[index])) {
1070 // The color was modified, update it in the palette
1071 palette[index] = gui::ConvertImVec4ToSnesColor(currentColor);
1072
1073 // Add to recently used colors
1074 AddRecentlyUsedColor(palette[index]);
1075 }
1076 return absl::OkStatus();
1077}
1078
1080 gfx::SnesPalette& palette, int index,
1081 const gfx::SnesPalette& originalPalette) {
1082 if (index >= palette.size() || index >= originalPalette.size()) {
1083 return absl::InvalidArgumentError("Index out of bounds");
1084 }
1085 auto color = originalPalette[index];
1086 auto originalColor = color.rgb();
1087 palette[index] = gui::ConvertImVec4ToSnesColor(originalColor);
1088 return absl::OkStatus();
1089}
1090
1091// ============================================================================
1092// Panel-Based UI Methods
1093// ============================================================================
1094
1096 auto& manager = gfx::PaletteManager::Get();
1097 if (!game_data() || !manager.IsManaging(game_data())) {
1098 ImGui::TextDisabled(
1099 tr("Palette controls are unavailable for an inactive ROM session."));
1100 return;
1101 }
1104 const size_t modified_count = manager.GetModifiedColorCount();
1105 const bool pending = manager.HasUnsavedChanges() || (rom_ && rom_->dirty());
1106 ImGui::BeginDisabled(!pending || !dependencies_.window_manager);
1107 const bool save = ImGui::Button(ICON_MD_SAVE " Save ROM");
1108 gui::AutoRegisterLastItem("button", "Save ROM");
1109 if (save)
1111 ImGui::EndDisabled();
1112 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled))
1113 ImGui::SetTooltip(
1114 tr("Save through the application, including pending editor changes."));
1115 ImGui::SameLine();
1116 ImGui::BeginDisabled(!CanUndo());
1117 if (gui::ThemedIconButton(ICON_MD_UNDO, "Undo palette edit"))
1118 Undo();
1119 gui::AutoRegisterLastItem("button", "Undo palette edit");
1120 ImGui::EndDisabled();
1121 ImGui::SameLine();
1122 ImGui::BeginDisabled(!CanRedo());
1123 if (gui::ThemedIconButton(ICON_MD_REDO, "Redo palette edit"))
1124 Redo();
1125 gui::AutoRegisterLastItem("button", "Redo palette edit");
1126 ImGui::EndDisabled();
1127 ImGui::SameLine();
1128 if (gui::ThemedIconButton(ICON_MD_MORE_HORIZ, "Palette tools"))
1129 ImGui::OpenPopup("PaletteTools");
1130 gui::AutoRegisterLastItem("button", "Palette tools");
1131 bool discard_requested = false;
1132 if (ImGui::BeginPopup("PaletteTools")) {
1133 if (ImGui::MenuItem(tr("Refresh editor previews"))) {
1134 const auto result = manager.ApplyPreviewChanges();
1135 if (!result.ok() && dependencies_.toast_manager)
1136 dependencies_.toast_manager->Show(std::string(result.message()),
1138 }
1139 if (ImGui::MenuItem(tr("Discard palette changes..."), nullptr, false,
1140 manager.HasUnsavedChanges()))
1141 discard_requested = true;
1142 gui::AutoRegisterLastItem("menu", "Discard palette changes");
1143 ImGui::Separator();
1144 if (ImGui::MenuItem(tr("Color reference")))
1146 "palette.quick_access");
1147 if (ImGui::MenuItem(tr("Custom swatches")))
1149 "palette.custom");
1150 ImGui::EndPopup();
1151 }
1152 if (discard_requested)
1153 ImGui::OpenPopup("Discard palette changes?###ConfirmPaletteDiscard");
1154 if (ImGui::BeginPopupModal("Discard palette changes?###ConfirmPaletteDiscard",
1155 nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
1156 ImGui::Text(tr("Discard changes to %zu colors?"), modified_count);
1157 if (ImGui::Button(tr("Discard"))) {
1158 manager.DiscardAllChanges();
1159 ImGui::CloseCurrentPopup();
1160 }
1161 ImGui::SameLine();
1162 if (ImGui::Button(tr("Cancel")))
1163 ImGui::CloseCurrentPopup();
1164 gui::AutoRegisterLastItem("button", "Cancel discard");
1165 ImGui::EndPopup();
1166 }
1167 ImGui::SameLine();
1168 if (modified_count)
1169 ImGui::TextDisabled(tr("%zu edited colors"), modified_count);
1170 else
1171 ImGui::TextDisabled(pending ? tr("ROM has unsaved changes")
1172 : tr("No pending palette edits"));
1173 ImGui::Separator();
1174
1175 if (ImGui::RadioButton(tr("Palettes"), !show_catalog_))
1176 show_catalog_ = false;
1177 ImGui::SameLine();
1178 if (ImGui::RadioButton(tr("Catalog"), show_catalog_))
1179 show_catalog_ = true;
1180 ImGui::Separator();
1181 if (show_catalog_) {
1183 return;
1184 }
1185
1186 const float nav_width =
1187 std::clamp(ImGui::GetContentRegionAvail().x * 0.22f,
1188 ImGui::GetFontSize() * 11, ImGui::GetFontSize() * 16);
1189 ImGui::BeginChild("PaletteGroups", ImVec2(nav_width, 0));
1191 ImGui::EndChild();
1192 ImGui::SameLine();
1193 ImGui::BeginChild("PaletteSelection", ImVec2(0, 0));
1194 if (auto* panel = GetPanelForGroup(selected_group_)) {
1195 ImGui::TextUnformatted(GetGroupDisplayName(selected_group_).c_str());
1196 ImGui::SameLine();
1197 if (gui::ThemedIconButton(ICON_MD_OPEN_IN_NEW, "Open comparison window"))
1199 panel->GetId());
1200 gui::AutoRegisterLastItem("button", "Open comparison window");
1201 ImGui::PushID(selected_group_.c_str());
1202 panel->DrawEmbedded();
1203 ImGui::PopID();
1204 }
1205 ImGui::EndChild();
1206}
1207
1209 // Current color picker with more options
1210 ImGui::BeginGroup();
1211 ImGui::Text(tr("Current Color"));
1212 gui::SnesColorEdit4("##CurrentColorPicker", &current_color_,
1214
1215 char buf[64];
1216 auto col = current_color_.rgb();
1217 int cr = F32_TO_INT8_SAT(col.x / 255.0f);
1218 int cg = F32_TO_INT8_SAT(col.y / 255.0f);
1219 int cb = F32_TO_INT8_SAT(col.z / 255.0f);
1220
1221 CustomFormatString(buf, IM_ARRAYSIZE(buf), "RGB: %d, %d, %d", cr, cg, cb);
1222 ImGui::Text("%s", buf);
1223
1224 CustomFormatString(buf, IM_ARRAYSIZE(buf), "SNES: $%04X",
1226 ImGui::Text("%s", buf);
1227
1228 if (ImGui::Button(tr("Copy to Clipboard"), ImVec2(-1, 0))) {
1229 SetClipboardText(buf);
1230 }
1231 ImGui::EndGroup();
1232
1233 ImGui::Separator();
1234
1235 // Recently used colors
1236 ImGui::Text(tr("Recently Used Colors"));
1237 if (recently_used_colors_.empty()) {
1238 ImGui::TextDisabled(tr("No recently used colors yet"));
1239 } else {
1240 for (int i = 0; i < recently_used_colors_.size(); i++) {
1241 PushID(i);
1242 if (i % 8 != 0)
1243 SameLine();
1244 ImVec4 displayColor =
1246 if (ImGui::ColorButton("##recent", displayColor, kPalButtonFlags,
1247 ImVec2(28, 28))) {
1248 // Set as current color
1250 }
1251 if (ImGui::IsItemHovered()) {
1252 ImGui::SetTooltip(tr("SNES: $%04X"), recently_used_colors_[i].snes());
1253 }
1254 PopID();
1255 }
1256 }
1257}
1258
1260 ImGui::TextWrapped(tr(
1261 "Create your own custom color palette for reference. "
1262 "Colors can be added from any palette group or created from scratch."));
1263
1264 ImGui::Separator();
1265
1266 // Custom palette color grid
1267 if (custom_palette_.empty()) {
1268 ImGui::TextDisabled(tr("Your custom palette is empty."));
1269 ImGui::Text(tr("Click + to add colors or drag colors from any palette."));
1270 } else {
1271 for (int i = 0; i < custom_palette_.size(); i++) {
1272 PushID(i);
1273 if (i > 0 && i % 16 != 0)
1274 SameLine(0.0f, 2.0f);
1275
1276 // Enhanced color button with context menu and drag-drop support
1277 ImVec4 displayColor = gui::ConvertSnesColorToImVec4(custom_palette_[i]);
1278 bool open_color_picker =
1279 ImGui::ColorButton(absl::StrFormat("##customPal%d", i).c_str(),
1280 displayColor, kPalButtonFlags, ImVec2(28, 28));
1281 const ImVec2 swatch_min = ImGui::GetItemRectMin();
1282 const ImVec2 swatch_max = ImGui::GetItemRectMax();
1283 if (ImGui::IsItemHovered() &&
1284 ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
1285 open_color_picker = true;
1286 }
1287
1288 if (open_color_picker) {
1292 "PanelCustomPaletteColorEdit")
1293 .c_str());
1294 }
1295
1296 if (DrawCustomColorContextMenu(
1297 custom_palette_, i,
1298 [this](const SnesColor& c) { AddRecentlyUsedColor(c); })) {
1299 PopID();
1300 break;
1301 }
1302
1303 if (AcceptImGuiColorDrop(&custom_palette_[i], swatch_min, swatch_max)) {
1305 }
1306
1307 PopID();
1308 }
1309 }
1310
1311 ImGui::Separator();
1312
1313 // Buttons for palette management
1314 if (ImGui::Button(ICON_MD_ADD " Add Color")) {
1315 custom_palette_.push_back(SnesColor(0x7FFF));
1316 }
1317
1318 ImGui::SameLine();
1319 if (ImGui::Button(ICON_MD_DELETE " Clear All")) {
1320 custom_palette_.clear();
1321 }
1322
1323 ImGui::SameLine();
1324 if (ImGui::Button(ICON_MD_CONTENT_COPY " Export")) {
1325 std::string clipboard;
1326 for (const auto& color : custom_palette_) {
1327 clipboard += absl::StrFormat("$%04X,", color.snes());
1328 }
1329 if (!clipboard.empty()) {
1330 clipboard.pop_back(); // Remove trailing comma
1331 }
1332 SetClipboardText(clipboard.c_str());
1333 }
1334 if (ImGui::IsItemHovered()) {
1335 ImGui::SetTooltip(tr("Copy palette as comma-separated SNES values"));
1336 }
1337
1338 // Color picker popup for custom palette editing
1339 if (ImGui::BeginPopup(gui::MakePopupId(gui::EditorNames::kPalette,
1340 "PanelCustomPaletteColorEdit")
1341 .c_str())) {
1342 if (edit_palette_index_ >= 0 &&
1346 "Edit Color", &custom_palette_[edit_palette_index_],
1347 kColorPopupFlags | ImGuiColorEditFlags_PickerHueWheel)) {
1348 // Color was changed, add to recently used
1350 }
1351 }
1352 ImGui::EndPopup();
1353 }
1354}
1355
1357 if (group == "ow_main")
1358 return ow_main_panel_;
1359 if (group == "ow_animated")
1360 return ow_anim_panel_;
1361 if (group == "dungeon_main")
1362 return dungeon_main_panel_;
1363 if (group == "global_sprites")
1364 return sprite_global_panel_;
1365 if (group == "sprites_aux1")
1366 return sprite_aux1_panel_;
1367 if (group == "sprites_aux2")
1368 return sprite_aux2_panel_;
1369 if (group == "sprites_aux3")
1370 return sprite_aux3_panel_;
1371 if (group == "armors")
1372 return equipment_panel_;
1373 return nullptr;
1374}
1375
1376void PaletteEditor::JumpToPalette(const std::string& group_name,
1377 int palette_index) {
1379 return;
1381 if (auto* panel = GetPanelForGroup(group_name)) {
1382 selected_group_ = group_name;
1383 panel->SetSelectedPaletteIndex(palette_index);
1385 "palette.control_panel");
1386 }
1387}
1388
1389// ============================================================================
1390// Category and Search UI Methods
1391// ============================================================================
1392
1394 ImGui::SetNextItemWidth(-1);
1395 if (ImGui::InputTextWithHint("##PaletteSearch",
1396 ICON_MD_SEARCH " Search palettes...",
1397 search_buffer_, sizeof(search_buffer_))) {
1398 // Search text changed - UI will update automatically
1399 }
1400}
1401
1402bool PaletteEditor::PassesSearchFilter(const std::string& group_name) const {
1403 if (search_buffer_[0] == '\0')
1404 return true;
1405
1406 // Check if group name or display name matches
1407 return gui::FuzzyMatch(search_buffer_, group_name) ||
1409}
1410
1412 DrawSearchBar();
1413 for (const auto& category : GetPaletteCategories()) {
1414 bool heading_drawn = false;
1415 for (const auto& group : category.group_names) {
1416 if (!GetPanelForGroup(group) || !PassesSearchFilter(group))
1417 continue;
1418 if (!heading_drawn) {
1419 ImGui::Spacing();
1420 ImGui::TextDisabled("%s", category.display_name.c_str());
1421 heading_drawn = true;
1422 }
1423 auto label = GetGroupDisplayName(group);
1424 if (gfx::PaletteManager::Get().IsGroupModified(group))
1425 label += " *";
1426 if (ImGui::Selectable(label.c_str(), selected_group_ == group))
1427 selected_group_ = group;
1428 gui::AutoRegisterLastItem("selectable", GetGroupDisplayName(group));
1429 }
1430 }
1431}
1432
1433} // namespace editor
1434} // namespace yaze
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:49
project::ResourceLabelManager * resource_label()
Definition rom.h:225
void set_dirty(bool dirty)
Definition rom.h:189
bool dirty() const
Definition rom.h:188
bool is_loaded() const
Definition rom.h:187
bool loaded() const
Check if the manifest has been loaded.
WindowPresentationPolicy GetPresentationPolicy() const override
How this content is hosted and advertised by workspace chrome.
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:254
zelda3::GameData * game_data() const
Definition editor.h:357
EditorDependencies dependencies_
Definition editor.h:370
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.
WindowPresentationPolicy GetPresentationPolicy() const override
How this content is hosted and advertised by workspace chrome.
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)
OverworldAnimatedPalettePanel * ow_anim_panel_
bool CanUndo() const override
absl::Status ResetColorToOriginal(gfx::SnesPalette &palette, int index, const gfx::SnesPalette &originalPalette)
std::vector< gfx::SnesColor > custom_palette_
SpritesAux1PalettePanel * sprite_aux1_panel_
void CommitSaveTransaction() override
void RollbackSaveTransaction() override
void DrawCustomPalette()
Draw custom palette editor with enhanced ROM hacking features.
absl::Status BeginSaveTransaction() override
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_
bool CanRedo() const override
std::vector< gfx::SnesColor > recently_used_colors_
palette_internal::PaletteEditorHistory history_
PaletteGroupPanel * GetPanelForGroup(const std::string &group_name)
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)
WindowPresentationPolicy GetPresentationPolicy() const override
How this content is hosted and advertised by workspace chrome.
void Show(const std::string &message, ToastType type=ToastType::kInfo, float ttl_seconds=3.0f, std::string action_label={}, std::function< void()> on_action=nullptr)
Base interface for all logical window content components.
Central registry for all editor cards with session awareness and dependency injection.
bool OpenWindow(size_t session_id, const std::string &base_window_id)
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)
void Undo()
Undo the most recent change.
void Initialize(zelda3::GameData *game_data)
Initialize the palette manager with GameData.
static PaletteManager & Get()
Get the singleton instance.
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_REDO
Definition icons.h:1570
#define ICON_MD_FILTER_2
Definition icons.h:752
#define ICON_MD_MORE_HORIZ
Definition icons.h:1241
#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_OPEN_IN_NEW
Definition icons.h:1354
#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 DrawCustomColorContextMenu(std::vector< gfx::SnesColor > &colors, int index, const std::function< void(const gfx::SnesColor &)> &on_edited)
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
std::string GetGroupDisplayName(const std::string &group_name)
Get display name for a palette group.
std::array< float, 4 > ToFloatArray(const SnesColor &color)
constexpr int kNumPalettes
constexpr const char * kPalette
Definition popup_id.h:56
constexpr const char * kColorPicker
Definition popup_id.h:83
bool ThemedIconButton(const char *icon, const char *tooltip, const ImVec2 &size, bool is_active, bool is_disabled, const char *panel_id, const char *anim_id)
Draw a standard icon button with theme-aware colors.
ImVec4 ConvertColorToImVec4(const Color &color)
Definition color.h:140
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
void AutoRegisterLastItem(const std::string &widget_type, const std::string &explicit_label, const std::string &description)
Automatically register the last ImGui item.
void SnesColorCopyMenuItems(const gfx::SnesColor &color)
Definition color.cc:59
ImVec4 ConvertSnesColorToImVec4(const gfx::SnesColor &color)
Convert SnesColor to standard ImVec4 for display.
Definition color.cc:24
IMGUI_API bool SnesColorEdit4(absl::string_view label, gfx::SnesColor *color, ImGuiColorEditFlags flags)
Definition color.cc:97
void RenderMenuItem(const CanvasMenuItem &item, std::function< void(const std::string &, std::function< void()>)> popup_opened_callback)
Render a single menu item.
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:37
#define F32_TO_INT8_SAT(_VAL)
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
Unified dependency container for all editor types.
Definition editor.h:170
project::YazeProject * project
Definition editor.h:174
WorkspaceWindowManager * window_manager
Definition editor.h:182
static WindowPresentationPolicy CoreWorkspace()
static WindowPresentationPolicy OptionalPopOut()
PaletteGroup * get_group(const std::string &group_name)
Represents a group of palettes.
static CanvasMenuItem Destructive(const std::string &lbl, const std::string &ico, std::function< void()> cb, bool confirm=true, const std::string &sc="")
std::string CreateOrGetLabel(const std::string &type, const std::string &key, const std::string &defaultValue)
Definition project.cc:2940
void SelectableLabelWithNameEdit(bool selected, const std::string &type, const std::string &key, const std::string &defaultValue)
Definition project.cc:2915
RomWritePolicy write_policy
Definition project.h:225
core::HackManifest hack_manifest
Definition project.h:360
gfx::PaletteGroupMap palette_groups
Definition game_data.h:95
Automatic widget registration helpers for ImGui Test Engine integration.