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