yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
palette_editor.cc
Go to the documentation of this file.
1#include "palette_editor.h"
2#include "util/i18n/tr.h"
3
4#include <algorithm>
5
6#include "absl/status/status.h"
7#include "absl/strings/str_cat.h"
8#include "absl/strings/str_format.h"
16#include "app/gui/core/color.h"
17#include "app/gui/core/icons.h"
19#include "app/gui/core/search.h"
23#include "imgui/imgui.h"
24
25#include <cstring>
26
27namespace yaze {
28namespace editor {
29
30using ImGui::AcceptDragDropPayload;
31using ImGui::BeginChild;
32using ImGui::BeginDragDropTarget;
33using ImGui::BeginGroup;
34using ImGui::BeginPopup;
35using ImGui::BeginPopupContextItem;
36using ImGui::Button;
37using ImGui::ColorButton;
38using ImGui::ColorPicker4;
39using ImGui::EndChild;
40using ImGui::EndDragDropTarget;
41using ImGui::EndGroup;
42using ImGui::EndPopup;
43using ImGui::GetStyle;
44using ImGui::OpenPopup;
45using ImGui::PopID;
46using ImGui::PushID;
47using ImGui::SameLine;
48using ImGui::Selectable;
49using ImGui::Separator;
50using ImGui::SetClipboardText;
51using ImGui::Text;
52
53using namespace gfx;
54
55constexpr ImGuiTableFlags kPaletteTableFlags =
56 ImGuiTableFlags_Reorderable | ImGuiTableFlags_Resizable |
57 ImGuiTableFlags_SizingStretchSame | ImGuiTableFlags_Hideable;
58
59constexpr ImGuiColorEditFlags kPalNoAlpha = ImGuiColorEditFlags_NoAlpha;
60
61constexpr ImGuiColorEditFlags kPalButtonFlags = ImGuiColorEditFlags_NoAlpha |
62 ImGuiColorEditFlags_NoPicker |
63 ImGuiColorEditFlags_NoTooltip;
64
65constexpr ImGuiColorEditFlags kColorPopupFlags =
66 ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoAlpha |
67 ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_DisplayHSV |
68 ImGuiColorEditFlags_DisplayHex;
69
70namespace {
71void DrawColorDropTargetOutline(ImVec2 min, ImVec2 max) {
72 const auto& theme = gui::ThemeManager::Get().GetCurrentTheme();
73 ImGui::GetForegroundDrawList()->AddRect(
74 min, max, ImGui::GetColorU32(gui::ConvertColorToImVec4(theme.accent)),
75 0.0f, 0, 2.0f);
76}
77
78bool AcceptImGuiColorDrop(SnesColor* color, ImVec2 min, ImVec2 max) {
79 if (!color || !BeginDragDropTarget()) {
80 return false;
81 }
82
83 bool changed = false;
84 constexpr ImGuiDragDropFlags kFlags =
85 ImGuiDragDropFlags_AcceptBeforeDelivery |
86 ImGuiDragDropFlags_AcceptNoDrawDefaultRect |
87 ImGuiDragDropFlags_AcceptNoPreviewTooltip;
88 auto accept = [&](const char* type, size_t components) {
89 if (changed) {
90 return;
91 }
92 if (const ImGuiPayload* payload = AcceptDragDropPayload(type, kFlags)) {
94 if (payload->IsDelivery()) {
95 ImVec4 dropped(0, 0, 0, 1);
96 std::memcpy(&dropped.x, payload->Data, sizeof(float) * components);
97 if (components == 3) {
98 dropped.w = 1.0f;
99 }
100 *color = SnesColor(dropped);
101 changed = true;
102 }
103 }
104 };
105
106 accept(IMGUI_PAYLOAD_TYPE_COLOR_3F, 3);
107 accept(IMGUI_PAYLOAD_TYPE_COLOR_4F, 4);
108 EndDragDropTarget();
109 return changed;
110}
111
112int CustomFormatString(char* buf, size_t buf_size, const char* fmt, ...) {
113 va_list args;
114 va_start(args, fmt);
115#ifdef IMGUI_USE_STB_SPRINTF
116 int w = stbsp_vsnprintf(buf, (int)buf_size, fmt, args);
117#else
118 int w = vsnprintf(buf, buf_size, fmt, args);
119#endif
120 va_end(args);
121 if (buf == nullptr)
122 return w;
123 if (w == -1 || w >= (int)buf_size)
124 w = (int)buf_size - 1;
125 buf[w] = 0;
126 return w;
127}
128
129static inline float color_saturate(float f) {
130 return (f < 0.0f) ? 0.0f : (f > 1.0f) ? 1.0f : f;
131}
132
133#define F32_TO_INT8_SAT(_VAL) \
134 ((int)(color_saturate(_VAL) * 255.0f + \
135 0.5f)) // Saturated, always output 0..255
136} // namespace
137
155absl::Status DisplayPalette(gfx::SnesPalette& palette, bool loaded) {
156 static ImVec4 color = ImVec4(0, 0, 0, 1.0f);
157 static ImVec4 current_palette[256] = {};
158 static int current_palette_count = 0;
159 ImGuiColorEditFlags misc_flags = ImGuiColorEditFlags_AlphaPreview |
160 ImGuiColorEditFlags_NoDragDrop |
161 ImGuiColorEditFlags_NoOptions;
162
163 // Reload palette colors whenever the palette data is available.
164 if (loaded) {
165 current_palette_count =
166 std::min<int>(static_cast<int>(palette.size()),
167 static_cast<int>(IM_ARRAYSIZE(current_palette)));
168
169 for (int n = 0; n < current_palette_count; ++n) {
170 const auto palette_color = palette[static_cast<size_t>(n)];
171 current_palette[n].x = palette_color.rgb().x / 255.0f;
172 current_palette[n].y = palette_color.rgb().y / 255.0f;
173 current_palette[n].z = palette_color.rgb().z / 255.0f;
174 current_palette[n].w = 1.0f;
175 }
176 for (int n = current_palette_count;
177 n < static_cast<int>(IM_ARRAYSIZE(current_palette)); ++n) {
178 current_palette[n] = ImVec4(0, 0, 0, 1.0f);
179 }
180 } else {
181 current_palette_count = 0;
182 }
183
184 static ImVec4 backup_color;
185 bool open_popup = ColorButton("MyColor##3b", color, misc_flags);
186 SameLine(0, GetStyle().ItemInnerSpacing.x);
187 open_popup |= Button(tr("Palette"));
188 if (open_popup) {
191 .c_str());
192 backup_color = color;
193 }
194
197 .c_str())) {
198 TEXT_WITH_SEPARATOR("Current Overworld Palette");
199 ColorPicker4("##picker", (float*)&color,
200 misc_flags | ImGuiColorEditFlags_NoSidePreview |
201 ImGuiColorEditFlags_NoSmallPreview);
202 SameLine();
203
204 BeginGroup(); // Lock X position
205 Text(tr("Current ==>"));
206 SameLine();
207 Text(tr("Previous"));
208
209 if (Button(tr("Update Map Palette"))) {}
210
211 ColorButton(
212 "##current", color,
213 ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf,
214 ImVec2(60, 40));
215 SameLine();
216
217 if (ColorButton(
218 "##previous", backup_color,
219 ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf,
220 ImVec2(60, 40)))
221 color = backup_color;
222
223 // List of Colors in Overworld Palette
224 Separator();
225 Text(tr("Palette"));
226 if (current_palette_count <= 0) {
227 ImGui::TextDisabled(tr("No palette entries loaded."));
228 }
229 for (int n = 0; n < current_palette_count; n++) {
230 PushID(n);
231 if ((n % 8) != 0)
232 SameLine(0.0f, GetStyle().ItemSpacing.y);
233
234 if (ColorButton("##palette", current_palette[n], kPalButtonFlags,
235 ImVec2(20, 20)))
236 color = ImVec4(current_palette[n].x, current_palette[n].y,
237 current_palette[n].z, color.w); // Preserve alpha!
238
239 if (BeginDragDropTarget()) {
240 if (const ImGuiPayload* payload =
241 AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))
242 memcpy((float*)&current_palette[n], payload->Data, sizeof(float) * 3);
243 if (const ImGuiPayload* payload =
244 AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))
245 memcpy((float*)&current_palette[n], payload->Data, sizeof(float) * 4);
246 EndDragDropTarget();
247 }
248
249 PopID();
250 }
251 EndGroup();
252 EndPopup();
253 }
254
255 return absl::OkStatus();
256}
257
259 // Register all panels with WorkspaceWindowManager (done once during
260 // initialization)
262 return;
263 auto* window_manager = dependencies_.window_manager;
264 const size_t session_id = dependencies_.session_id;
265
266 window_manager->RegisterPanel(
267 {.card_id = "palette.control_panel",
268 .display_name = "Palette Controls",
269 .window_title = " Palette Controls",
270 .icon = ICON_MD_PALETTE,
271 .category = "Palette",
272 .shortcut_hint = "Ctrl+Shift+P",
273 .visibility_flag = &show_control_panel_,
274 .priority = 10,
275 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
276 .disabled_tooltip = "Load a ROM first"});
277
278 window_manager->RegisterPanel(
279 {.card_id = "palette.ow_main",
280 .display_name = "Overworld Main",
281 .window_title = " Overworld Main",
282 .icon = ICON_MD_LANDSCAPE,
283 .category = "Palette",
284 .shortcut_hint = "Ctrl+Alt+1",
285 .visibility_flag = &show_ow_main_panel_,
286 .priority = 20,
287 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
288 .disabled_tooltip = "Load a ROM first"});
289
290 window_manager->RegisterPanel(
291 {.card_id = "palette.ow_animated",
292 .display_name = "Overworld Animated",
293 .window_title = " Overworld Animated",
294 .icon = ICON_MD_WATER,
295 .category = "Palette",
296 .shortcut_hint = "Ctrl+Alt+2",
297 .visibility_flag = &show_ow_animated_panel_,
298 .priority = 30,
299 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
300 .disabled_tooltip = "Load a ROM first"});
301
302 window_manager->RegisterPanel(
303 {.card_id = "palette.dungeon_main",
304 .display_name = "Dungeon Main",
305 .window_title = " Dungeon Main",
306 .icon = ICON_MD_CASTLE,
307 .category = "Palette",
308 .shortcut_hint = "Ctrl+Alt+3",
309 .visibility_flag = &show_dungeon_main_panel_,
310 .priority = 40,
311 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
312 .disabled_tooltip = "Load a ROM first"});
313
314 window_manager->RegisterPanel(
315 {.card_id = "palette.sprites",
316 .display_name = "Global Sprite Palettes",
317 .window_title = " SNES Palette",
318 .icon = ICON_MD_PETS,
319 .category = "Palette",
320 .shortcut_hint = "Ctrl+Alt+4",
321 .visibility_flag = &show_sprite_panel_,
322 .priority = 50,
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.sprites_aux1",
328 .display_name = "Sprites Aux 1",
329 .window_title = " Sprites Aux 1",
330 .icon = ICON_MD_FILTER_1,
331 .category = "Palette",
332 .shortcut_hint = "Ctrl+Alt+7",
333 .visibility_flag = &show_sprites_aux1_panel_,
334 .priority = 51,
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.sprites_aux2",
340 .display_name = "Sprites Aux 2",
341 .window_title = " Sprites Aux 2",
342 .icon = ICON_MD_FILTER_2,
343 .category = "Palette",
344 .shortcut_hint = "Ctrl+Alt+8",
345 .visibility_flag = &show_sprites_aux2_panel_,
346 .priority = 52,
347 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
348 .disabled_tooltip = "Load a ROM first"});
349
350 window_manager->RegisterPanel(
351 {.card_id = "palette.sprites_aux3",
352 .display_name = "Sprites Aux 3",
353 .window_title = " Sprites Aux 3",
354 .icon = ICON_MD_FILTER_3,
355 .category = "Palette",
356 .shortcut_hint = "Ctrl+Alt+9",
357 .visibility_flag = &show_sprites_aux3_panel_,
358 .priority = 53,
359 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
360 .disabled_tooltip = "Load a ROM first"});
361
362 window_manager->RegisterPanel(
363 {.card_id = "palette.equipment",
364 .display_name = "Equipment Palettes",
365 .window_title = " Equipment Palettes",
366 .icon = ICON_MD_SHIELD,
367 .category = "Palette",
368 .shortcut_hint = "Ctrl+Alt+5",
369 .visibility_flag = &show_equipment_panel_,
370 .priority = 60,
371 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
372 .disabled_tooltip = "Load a ROM first"});
373
374 window_manager->RegisterPanel(
375 {.card_id = "palette.quick_access",
376 .display_name = "Quick Access",
377 .window_title = " Color Harmony",
378 .icon = ICON_MD_COLOR_LENS,
379 .category = "Palette",
380 .shortcut_hint = "Ctrl+Alt+Q",
381 .visibility_flag = &show_quick_access_,
382 .priority = 70,
383 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
384 .disabled_tooltip = "Load a ROM first"});
385
386 window_manager->RegisterPanel(
387 {.card_id = "palette.custom",
388 .display_name = "Custom Palette",
389 .window_title = " Palette Editor",
390 .icon = ICON_MD_BRUSH,
391 .category = "Palette",
392 .shortcut_hint = "Ctrl+Alt+C",
393 .visibility_flag = &show_custom_palette_,
394 .priority = 80,
395 .enabled_condition = [this]() { return rom_ && rom_->is_loaded(); },
396 .disabled_tooltip = "Load a ROM first"});
397
398 // Show control panel by default when Palette Editor is activated
399 window_manager->OpenWindow(session_id, "palette.control_panel");
400}
401
402// ============================================================================
403// Helper Panel Classes
404// ============================================================================
405
407 public:
408 explicit PaletteControlPanel(std::function<void()> draw_callback)
409 : draw_callback_(std::move(draw_callback)) {}
410
411 std::string GetId() const override { return "palette.control_panel"; }
412 std::string GetDisplayName() const override { return "Palette Controls"; }
413 std::string GetIcon() const override { return ICON_MD_PALETTE; }
414 std::string GetEditorCategory() const override { return "Palette"; }
415 int GetPriority() const override { return 10; }
416 float GetPreferredWidth() const override { return 320.0f; }
417
418 void Draw(bool* p_open) override {
419 if (p_open && !*p_open)
420 return;
421 if (draw_callback_)
423 }
424
425 private:
426 std::function<void()> draw_callback_;
427};
428
430 public:
431 explicit QuickAccessPalettePanel(std::function<void()> draw_callback)
432 : draw_callback_(std::move(draw_callback)) {}
433
434 std::string GetId() const override { return "palette.quick_access"; }
435 std::string GetDisplayName() const override { return "Quick Access"; }
436 std::string GetIcon() const override { return ICON_MD_COLOR_LENS; }
437 std::string GetEditorCategory() const override { return "Palette"; }
438 int GetPriority() const override { return 70; }
439 float GetPreferredWidth() const override { return 340.0f; }
440
441 void Draw(bool* p_open) override {
442 if (p_open && !*p_open)
443 return;
444 if (draw_callback_)
446 }
447
448 private:
449 std::function<void()> draw_callback_;
450};
451
453 public:
454 explicit CustomPalettePanel(std::function<void()> draw_callback)
455 : draw_callback_(std::move(draw_callback)) {}
456
457 std::string GetId() const override { return "palette.custom"; }
458 std::string GetDisplayName() const override { return "Custom Palette"; }
459 std::string GetIcon() const override { return ICON_MD_BRUSH; }
460 std::string GetEditorCategory() const override { return "Palette"; }
461 int GetPriority() const override { return 80; }
462 float GetPreferredWidth() const override { return 420.0f; }
463
464 void Draw(bool* p_open) override {
465 if (p_open && !*p_open)
466 return;
467 if (draw_callback_)
469 }
470
471 private:
472 std::function<void()> draw_callback_;
473};
474
475absl::Status PaletteEditor::Load() {
476 gfx::ScopedTimer timer("PaletteEditor::Load");
477
478 if (!rom() || !rom()->is_loaded()) {
479 return absl::NotFoundError("ROM not open, no palettes to display");
480 }
481
482 // Initialize the labels
483 for (int i = 0; i < kNumPalettes; i++) {
485 "Palette Group Name", std::to_string(i),
486 std::string(kPaletteGroupNames[i]));
487 }
488
489 // Initialize the centralized PaletteManager with GameData
490 // This must be done before creating any palette cards
491 if (game_data()) {
493 } else {
494 // Fallback to legacy ROM-only initialization
496 }
497
498 // Register WindowContent instances with WorkspaceWindowManager
500 auto* window_manager = dependencies_.window_manager;
501
502 // Create and register palette panels
503 // Note: WorkspaceWindowManager takes ownership via unique_ptr
504
505 // Overworld Main
506 auto ow_main =
507 std::make_unique<OverworldMainPalettePanel>(rom_, game_data());
508 ow_main_panel_ = ow_main.get();
509 window_manager->RegisterWindowContent(std::move(ow_main));
510
511 // Overworld Animated
512 auto ow_anim =
513 std::make_unique<OverworldAnimatedPalettePanel>(rom_, game_data());
514 ow_anim_panel_ = ow_anim.get();
515 window_manager->RegisterWindowContent(std::move(ow_anim));
516
517 // Dungeon Main
518 auto dungeon_main =
519 std::make_unique<DungeonMainPalettePanel>(rom_, game_data());
520 dungeon_main_panel_ = dungeon_main.get();
521 window_manager->RegisterWindowContent(std::move(dungeon_main));
522
523 // Global Sprites
524 auto sprite_global =
525 std::make_unique<SpritePalettePanel>(rom_, game_data());
526 sprite_global_panel_ = sprite_global.get();
527 window_manager->RegisterWindowContent(std::move(sprite_global));
528
529 // Sprites Aux 1
530 auto sprite_aux1 =
531 std::make_unique<SpritesAux1PalettePanel>(rom_, game_data());
532 sprite_aux1_panel_ = sprite_aux1.get();
533 window_manager->RegisterWindowContent(std::move(sprite_aux1));
534
535 // Sprites Aux 2
536 auto sprite_aux2 =
537 std::make_unique<SpritesAux2PalettePanel>(rom_, game_data());
538 sprite_aux2_panel_ = sprite_aux2.get();
539 window_manager->RegisterWindowContent(std::move(sprite_aux2));
540
541 // Sprites Aux 3
542 auto sprite_aux3 =
543 std::make_unique<SpritesAux3PalettePanel>(rom_, game_data());
544 sprite_aux3_panel_ = sprite_aux3.get();
545 window_manager->RegisterWindowContent(std::move(sprite_aux3));
546
547 // Equipment
548 auto equipment = std::make_unique<EquipmentPalettePanel>(rom_, game_data());
549 equipment_panel_ = equipment.get();
550 window_manager->RegisterWindowContent(std::move(equipment));
551
552 // Wire toast manager to all palette group panels
553 auto* toast = dependencies_.toast_manager;
554 if (toast) {
563 }
564
565 // Register utility panels with callbacks
566 window_manager->RegisterWindowContent(std::make_unique<PaletteControlPanel>(
567 [this]() { DrawControlPanel(); }));
568 window_manager->RegisterWindowContent(
569 std::make_unique<QuickAccessPalettePanel>(
570 [this]() { DrawQuickAccessPanel(); }));
571 window_manager->RegisterWindowContent(std::make_unique<CustomPalettePanel>(
572 [this]() { DrawCustomPalettePanel(); }));
573 }
574
575 return absl::OkStatus();
576}
577
578absl::Status PaletteEditor::Save() {
579 if (!rom_ || !rom_->is_loaded()) {
580 return absl::FailedPreconditionError("ROM not loaded");
581 }
582 if (!game_data() || !gfx::PaletteManager::Get().IsManaging(game_data())) {
583 return absl::FailedPreconditionError(
584 "Cannot save palettes from an inactive ROM session");
585 }
586
587 // Delegate to PaletteManager for centralized save
589
590 // Mark ROM as needing file save
591 rom_->set_dirty(true);
592
593 return absl::OkStatus();
594}
595
596absl::Status PaletteEditor::Undo() {
597 if (!game_data() || !gfx::PaletteManager::Get().IsManaging(game_data())) {
598 return absl::FailedPreconditionError(
599 "Cannot undo palettes from an inactive ROM session");
600 }
601
603 return absl::OkStatus();
604}
605
606absl::Status PaletteEditor::Redo() {
607 if (!game_data() || !gfx::PaletteManager::Get().IsManaging(game_data())) {
608 return absl::FailedPreconditionError(
609 "Cannot redo palettes from an inactive ROM session");
610 }
611
613 return absl::OkStatus();
614}
615
616absl::Status PaletteEditor::Update() {
617 // Panel drawing is handled centrally by WorkspaceWindowManager::DrawAllVisiblePanels()
618 // via the WindowContent implementations registered in Load().
619 // No local drawing needed here - this fixes duplicate panel rendering.
620 return absl::OkStatus();
621}
622
624 BeginChild("QuickAccessPalettes", ImVec2(0, 0), true);
625
626 Text(tr("Custom Palette"));
628
629 Separator();
630
631 // Current color picker with more options
632 BeginGroup();
633 Text(tr("Current Color"));
634 gui::SnesColorEdit4("##CurrentColorPicker", &current_color_,
636
637 char buf[64];
638 auto col = current_color_.rgb();
639 int cr = F32_TO_INT8_SAT(col.x / 255.0f);
640 int cg = F32_TO_INT8_SAT(col.y / 255.0f);
641 int cb = F32_TO_INT8_SAT(col.z / 255.0f);
642
643 CustomFormatString(buf, IM_ARRAYSIZE(buf), "RGB: %d, %d, %d", cr, cg, cb);
644 Text("%s", buf);
645
646 CustomFormatString(buf, IM_ARRAYSIZE(buf), "SNES: $%04X",
648 Text("%s", buf);
649
650 if (Button(tr("Copy to Clipboard"))) {
651 SetClipboardText(buf);
652 }
653 EndGroup();
654
655 Separator();
656
657 // Recently used colors
658 Text(tr("Recently Used Colors"));
659 for (int i = 0; i < recently_used_colors_.size(); i++) {
660 PushID(i);
661 if (i % 8 != 0)
662 SameLine();
663 ImVec4 displayColor =
665 if (ImGui::ColorButton("##recent", displayColor)) {
666 // Set as current color
668 }
669 PopID();
670 }
671
672 EndChild();
673}
674
691 if (BeginChild("ColorPalette", ImVec2(0, 40), ImGuiChildFlags_None,
692 ImGuiWindowFlags_HorizontalScrollbar)) {
693 for (int i = 0; i < custom_palette_.size(); i++) {
694 PushID(i);
695 if (i > 0)
696 SameLine(0.0f, GetStyle().ItemSpacing.y);
697
698 // Enhanced color button with context menu and drag-drop support
699 ImVec4 displayColor = gui::ConvertSnesColorToImVec4(custom_palette_[i]);
700 bool open_color_picker = ImGui::ColorButton(
701 absl::StrFormat("##customPal%d", i).c_str(), displayColor);
702 const ImVec2 swatch_min = ImGui::GetItemRectMin();
703 const ImVec2 swatch_max = ImGui::GetItemRectMax();
704 if (ImGui::IsItemHovered() &&
705 ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
706 open_color_picker = true;
707 }
708
709 if (open_color_picker) {
713 "CustomPaletteColorEdit")
714 .c_str());
715 }
716
717 if (BeginPopupContextItem()) {
718 // Edit color directly in the popup
719 SnesColor original_color = custom_palette_[i];
720 if (gui::SnesColorEdit4("Edit Color", &custom_palette_[i],
722 // Color was changed, add to recently used
724 }
725
726 if (Button(tr("Delete"), ImVec2(-1, 0))) {
727 custom_palette_.erase(custom_palette_.begin() + i);
728 }
729 }
730
731 if (AcceptImGuiColorDrop(&custom_palette_[i], swatch_min, swatch_max)) {
733 }
734
735 PopID();
736 }
737
738 SameLine();
739 if (ImGui::Button("+")) {
740 custom_palette_.push_back(SnesColor(0x7FFF));
741 }
742
743 SameLine();
744 if (ImGui::Button(tr("Clear"))) {
745 custom_palette_.clear();
746 }
747
748 SameLine();
749 if (ImGui::Button(tr("Export"))) {
750 std::string clipboard;
751 for (const auto& color : custom_palette_) {
752 clipboard += absl::StrFormat("$%04X,", color.snes());
753 }
754 SetClipboardText(clipboard.c_str());
755 }
756 }
757 EndChild();
758
759 // Color picker popup for custom palette editing
760 if (ImGui::BeginPopup(
761 gui::MakePopupId(gui::EditorNames::kPalette, "CustomPaletteColorEdit")
762 .c_str())) {
763 if (edit_palette_index_ >= 0 &&
767 "Edit Color", &custom_palette_[edit_palette_index_],
768 kColorPopupFlags | ImGuiColorEditFlags_PickerHueWheel)) {
769 // Color was changed, add to recently used
771 }
772 }
773 ImGui::EndPopup();
774 }
775}
776
777absl::Status PaletteEditor::DrawPaletteGroup(int category,
778 bool /*right_side*/) {
779 if (!rom()->is_loaded() || !game_data()) {
780 return absl::NotFoundError("ROM not open, no palettes to display");
781 }
782
783 auto palette_group_name = kPaletteGroupNames[category];
784 gfx::PaletteGroup* palette_group =
785 game_data()->palette_groups.get_group(palette_group_name.data());
786 const auto size = palette_group->size();
787
788 for (int j = 0; j < size; j++) {
789 gfx::SnesPalette* palette = palette_group->mutable_palette(j);
790 auto pal_size = palette->size();
791
792 BeginGroup();
793
794 PushID(j);
795 BeginGroup();
797 false, palette_group_name.data(), /*key=*/std::to_string(j),
798 "Unnamed Palette");
799 EndGroup();
800
801 for (int n = 0; n < pal_size; n++) {
802 PushID(n);
803 if (n > 0 && n % 8 != 0)
804 SameLine(0.0f, 2.0f);
805
806 auto popup_id =
807 absl::StrCat(kPaletteCategoryNames[category].data(), j, "_", n);
808
809 ImVec4 displayColor = gui::ConvertSnesColorToImVec4((*palette)[n]);
810 if (ImGui::ColorButton(popup_id.c_str(), displayColor)) {
811 current_color_ = (*palette)[n];
813 }
814 const ImVec2 swatch_min = ImGui::GetItemRectMin();
815 const ImVec2 swatch_max = ImGui::GetItemRectMax();
816 if (ImGui::IsItemHovered() &&
817 ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
818 current_color_ = (*palette)[n];
820 OpenPopup(popup_id.c_str());
821 }
822 {
823 const SnesColor original_color = (*palette)[n];
824 if (AcceptImGuiColorDrop(&(*palette)[n], swatch_min, swatch_max)) {
826 /*group_name=*/std::string(kPaletteGroupNames[category]),
827 /*palette_index=*/j, /*color_index=*/n, original_color,
828 (*palette)[n]);
829 (*palette)[n].set_modified(true);
830 AddRecentlyUsedColor((*palette)[n]);
831 }
832 }
833
834 if (BeginPopupContextItem(popup_id.c_str())) {
835 RETURN_IF_ERROR(HandleColorPopup(*palette, category, j, n))
836 }
837 PopID();
838 }
839 PopID();
840 EndGroup();
841
842 if (j < size - 1) {
843 Separator();
844 }
845 }
846 return absl::OkStatus();
847}
848
850 // Check if color already exists in recently used
851 auto it = std::find_if(
853 [&color](const SnesColor& c) { return c.snes() == color.snes(); });
854
855 // If found, remove it to re-add at front
856 if (it != recently_used_colors_.end()) {
857 recently_used_colors_.erase(it);
858 }
859
860 // Add at front
861 recently_used_colors_.insert(recently_used_colors_.begin(), color);
862
863 // Limit size
864 if (recently_used_colors_.size() > 16) {
865 recently_used_colors_.pop_back();
866 }
867}
868
870 int j, int n) {
871 auto col = gfx::ToFloatArray(palette[n]);
872 auto original_color = palette[n];
873
874 if (gui::SnesColorEdit4("Edit Color", &palette[n], kColorPopupFlags)) {
875 history_.RecordChange(/*group_name=*/std::string(kPaletteGroupNames[i]),
876 /*palette_index=*/j, /*color_index=*/n,
877 original_color, palette[n]);
878 palette[n].set_modified(true);
879
880 // Add to recently used colors
881 AddRecentlyUsedColor(palette[n]);
882 }
883
884 // Color information display
885 char buf[64];
886 int cr = F32_TO_INT8_SAT(col[0]);
887 int cg = F32_TO_INT8_SAT(col[1]);
888 int cb = F32_TO_INT8_SAT(col[2]);
889
890 Text(tr("RGB: %d, %d, %d"), cr, cg, cb);
891 Text(tr("SNES: $%04X"), palette[n].snes());
892
893 Separator();
894
895 if (Button(tr("Copy as.."), ImVec2(-1, 0)))
898 .c_str());
901 .c_str())) {
902 CustomFormatString(buf, IM_ARRAYSIZE(buf), "(%.3ff, %.3ff, %.3ff)", col[0],
903 col[1], col[2]);
904 if (Selectable(buf))
905 SetClipboardText(buf);
906
907 CustomFormatString(buf, IM_ARRAYSIZE(buf), "(%d,%d,%d)", cr, cg, cb);
908 if (Selectable(buf))
909 SetClipboardText(buf);
910
911 CustomFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", cr, cg, cb);
912 if (Selectable(buf))
913 SetClipboardText(buf);
914
915 // SNES Format
916 CustomFormatString(buf, IM_ARRAYSIZE(buf), "$%04X",
917 ConvertRgbToSnes(ImVec4(col[0], col[1], col[2], 1.0f)));
918 if (Selectable(buf))
919 SetClipboardText(buf);
920
921 EndPopup();
922 }
923
924 // Add a button to add this color to custom palette
925 if (Button(tr("Add to Custom Palette"), ImVec2(-1, 0))) {
926 custom_palette_.push_back(palette[n]);
927 }
928
929 EndPopup();
930 return absl::OkStatus();
931}
932
934 int index) {
935 if (index >= palette.size()) {
936 return absl::InvalidArgumentError("Index out of bounds");
937 }
938
939 // Get the current color
940 auto color = palette[index];
941 auto currentColor = color.rgb();
942 if (ColorPicker4("Color Picker", (float*)&palette[index])) {
943 // The color was modified, update it in the palette
944 palette[index] = gui::ConvertImVec4ToSnesColor(currentColor);
945
946 // Add to recently used colors
947 AddRecentlyUsedColor(palette[index]);
948 }
949 return absl::OkStatus();
950}
951
953 gfx::SnesPalette& palette, int index,
954 const gfx::SnesPalette& originalPalette) {
955 if (index >= palette.size() || index >= originalPalette.size()) {
956 return absl::InvalidArgumentError("Index out of bounds");
957 }
958 auto color = originalPalette[index];
959 auto originalColor = color.rgb();
960 palette[index] = gui::ConvertImVec4ToSnesColor(originalColor);
961 return absl::OkStatus();
962}
963
964// ============================================================================
965// Panel-Based UI Methods
966// ============================================================================
967
969 // Sidebar is drawn by WorkspaceWindowManager in EditorManager
970 // Panels registered in Initialize() appear in the sidebar automatically
971}
972
974 if (!game_data() || !gfx::PaletteManager::Get().IsManaging(game_data())) {
975 ImGui::TextDisabled(
976 tr("Palette controls are unavailable for an inactive ROM session."));
977 return;
978 }
979
980 // Toolbar with quick toggles
981 DrawToolset();
982
983 ImGui::Separator();
984
985 // Categorized palette list with search
987
988 ImGui::Separator();
989
990 // Modified status indicator
991 ImGui::TextColored(gui::GetWarningColor(), tr("Modified Panels:"));
992 bool any_modified = false;
993
995 ImGui::BulletText(tr("Overworld Main"));
996 any_modified = true;
997 }
999 ImGui::BulletText(tr("Overworld Animated"));
1000 any_modified = true;
1001 }
1003 ImGui::BulletText(tr("Dungeon Main"));
1004 any_modified = true;
1005 }
1007 ImGui::BulletText(tr("Global Sprite Palettes"));
1008 any_modified = true;
1009 }
1011 ImGui::BulletText(tr("Sprites Aux 1"));
1012 any_modified = true;
1013 }
1015 ImGui::BulletText(tr("Sprites Aux 2"));
1016 any_modified = true;
1017 }
1019 ImGui::BulletText(tr("Sprites Aux 3"));
1020 any_modified = true;
1021 }
1023 ImGui::BulletText(tr("Equipment Palettes"));
1024 any_modified = true;
1025 }
1026
1027 if (!any_modified) {
1028 ImGui::TextDisabled(tr("No unsaved changes"));
1029 }
1030
1031 ImGui::Separator();
1032
1033 // Quick actions
1034 ImGui::Text(tr("Quick Actions:"));
1035
1036 // Use centralized PaletteManager for global operations
1037 bool has_unsaved = gfx::PaletteManager::Get().HasUnsavedChanges();
1038 size_t modified_count = gfx::PaletteManager::Get().GetModifiedColorCount();
1039
1040 ImGui::BeginDisabled(!has_unsaved);
1041 if (ImGui::Button(
1042 absl::StrFormat(ICON_MD_SAVE " Save All (%zu colors)", modified_count)
1043 .c_str(),
1044 ImVec2(-1, 0))) {
1045 auto status = gfx::PaletteManager::Get().SaveAllToRom();
1046 if (!status.ok()) {
1049 absl::StrFormat("Failed to save palettes: %s", status.message()),
1051 }
1052 }
1053 }
1054 ImGui::EndDisabled();
1055
1056 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
1057 if (has_unsaved) {
1058 ImGui::SetTooltip(tr("Save all modified colors to ROM"));
1059 } else {
1060 ImGui::SetTooltip(tr("No unsaved changes"));
1061 }
1062 }
1063
1064 // Apply to Editors button - preview changes without saving to ROM
1065 ImGui::BeginDisabled(!has_unsaved);
1066 if (ImGui::Button(ICON_MD_VISIBILITY " Apply to Editors", ImVec2(-1, 0))) {
1068 if (!status.ok()) {
1071 .c_str());
1072 }
1073 }
1074 ImGui::EndDisabled();
1075
1076 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
1077 if (has_unsaved) {
1078 ImGui::SetTooltip(
1079 tr("Preview palette changes in other editors without saving to ROM"));
1080 } else {
1081 ImGui::SetTooltip(tr("No changes to preview"));
1082 }
1083 }
1084
1085 ImGui::BeginDisabled(!has_unsaved);
1086 if (ImGui::Button(ICON_MD_UNDO " Discard All Changes", ImVec2(-1, 0))) {
1089 .c_str());
1090 }
1091 ImGui::EndDisabled();
1092
1093 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
1094 if (has_unsaved) {
1095 ImGui::SetTooltip(tr("Discard all unsaved changes"));
1096 } else {
1097 ImGui::SetTooltip(tr("No changes to discard"));
1098 }
1099 }
1100
1101 // Confirmation popup for discard
1102 if (ImGui::BeginPopupModal(
1105 .c_str(),
1106 nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
1107 ImGui::Text(tr("Discard all unsaved changes?"));
1108 ImGui::TextColored(gui::GetWarningColor(),
1109 tr("This will revert %zu modified colors."),
1110 modified_count);
1111 ImGui::Separator();
1112
1113 if (ImGui::Button(tr("Discard"), ImVec2(120, 0))) {
1115 ImGui::CloseCurrentPopup();
1116 }
1117 ImGui::SameLine();
1118 if (ImGui::Button(tr("Cancel"), ImVec2(120, 0))) {
1119 ImGui::CloseCurrentPopup();
1120 }
1121 ImGui::EndPopup();
1122 }
1123
1124 // Error popup for save failures
1125 if (ImGui::BeginPopupModal(gui::MakePopupId(gui::EditorNames::kPalette,
1127 .c_str(),
1128 nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
1129 ImGui::TextColored(gui::GetErrorColor(), tr("Failed to save changes"));
1130 ImGui::Text(tr("An error occurred while saving to ROM."));
1131 ImGui::Separator();
1132
1133 if (ImGui::Button(tr("OK"), ImVec2(120, 0))) {
1134 ImGui::CloseCurrentPopup();
1135 }
1136 ImGui::EndPopup();
1137 }
1138
1139 ImGui::Separator();
1140
1141 // Panel management handled globally in the sidebar/menu system.
1142}
1143
1145 // Current color picker with more options
1146 ImGui::BeginGroup();
1147 ImGui::Text(tr("Current Color"));
1148 gui::SnesColorEdit4("##CurrentColorPicker", &current_color_,
1150
1151 char buf[64];
1152 auto col = current_color_.rgb();
1153 int cr = F32_TO_INT8_SAT(col.x / 255.0f);
1154 int cg = F32_TO_INT8_SAT(col.y / 255.0f);
1155 int cb = F32_TO_INT8_SAT(col.z / 255.0f);
1156
1157 CustomFormatString(buf, IM_ARRAYSIZE(buf), "RGB: %d, %d, %d", cr, cg, cb);
1158 ImGui::Text("%s", buf);
1159
1160 CustomFormatString(buf, IM_ARRAYSIZE(buf), "SNES: $%04X",
1162 ImGui::Text("%s", buf);
1163
1164 if (ImGui::Button(tr("Copy to Clipboard"), ImVec2(-1, 0))) {
1165 SetClipboardText(buf);
1166 }
1167 ImGui::EndGroup();
1168
1169 ImGui::Separator();
1170
1171 // Recently used colors
1172 ImGui::Text(tr("Recently Used Colors"));
1173 if (recently_used_colors_.empty()) {
1174 ImGui::TextDisabled(tr("No recently used colors yet"));
1175 } else {
1176 for (int i = 0; i < recently_used_colors_.size(); i++) {
1177 PushID(i);
1178 if (i % 8 != 0)
1179 SameLine();
1180 ImVec4 displayColor =
1182 if (ImGui::ColorButton("##recent", displayColor, kPalButtonFlags,
1183 ImVec2(28, 28))) {
1184 // Set as current color
1186 }
1187 if (ImGui::IsItemHovered()) {
1188 ImGui::SetTooltip(tr("SNES: $%04X"), recently_used_colors_[i].snes());
1189 }
1190 PopID();
1191 }
1192 }
1193}
1194
1196 ImGui::TextWrapped(tr(
1197 "Create your own custom color palette for reference. "
1198 "Colors can be added from any palette group or created from scratch."));
1199
1200 ImGui::Separator();
1201
1202 // Custom palette color grid
1203 if (custom_palette_.empty()) {
1204 ImGui::TextDisabled(tr("Your custom palette is empty."));
1205 ImGui::Text(tr("Click + to add colors or drag colors from any palette."));
1206 } else {
1207 for (int i = 0; i < custom_palette_.size(); i++) {
1208 PushID(i);
1209 if (i > 0 && i % 16 != 0)
1210 SameLine(0.0f, 2.0f);
1211
1212 // Enhanced color button with context menu and drag-drop support
1213 ImVec4 displayColor = gui::ConvertSnesColorToImVec4(custom_palette_[i]);
1214 bool open_color_picker =
1215 ImGui::ColorButton(absl::StrFormat("##customPal%d", i).c_str(),
1216 displayColor, kPalButtonFlags, ImVec2(28, 28));
1217 const ImVec2 swatch_min = ImGui::GetItemRectMin();
1218 const ImVec2 swatch_max = ImGui::GetItemRectMax();
1219 if (ImGui::IsItemHovered() &&
1220 ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
1221 open_color_picker = true;
1222 }
1223
1224 if (open_color_picker) {
1228 "PanelCustomPaletteColorEdit")
1229 .c_str());
1230 }
1231
1232 if (BeginPopupContextItem()) {
1233 // Edit color directly in the popup
1234 SnesColor original_color = custom_palette_[i];
1235 if (gui::SnesColorEdit4("Edit Color", &custom_palette_[i],
1237 // Color was changed, add to recently used
1239 }
1240
1241 if (ImGui::Button(tr("Delete"), ImVec2(-1, 0))) {
1242 custom_palette_.erase(custom_palette_.begin() + i);
1243 ImGui::CloseCurrentPopup();
1244 }
1245 ImGui::EndPopup();
1246 }
1247
1248 if (AcceptImGuiColorDrop(&custom_palette_[i], swatch_min, swatch_max)) {
1250 }
1251
1252 PopID();
1253 }
1254 }
1255
1256 ImGui::Separator();
1257
1258 // Buttons for palette management
1259 if (ImGui::Button(ICON_MD_ADD " Add Color")) {
1260 custom_palette_.push_back(SnesColor(0x7FFF));
1261 }
1262
1263 ImGui::SameLine();
1264 if (ImGui::Button(ICON_MD_DELETE " Clear All")) {
1265 custom_palette_.clear();
1266 }
1267
1268 ImGui::SameLine();
1269 if (ImGui::Button(ICON_MD_CONTENT_COPY " Export")) {
1270 std::string clipboard;
1271 for (const auto& color : custom_palette_) {
1272 clipboard += absl::StrFormat("$%04X,", color.snes());
1273 }
1274 if (!clipboard.empty()) {
1275 clipboard.pop_back(); // Remove trailing comma
1276 }
1277 SetClipboardText(clipboard.c_str());
1278 }
1279 if (ImGui::IsItemHovered()) {
1280 ImGui::SetTooltip(tr("Copy palette as comma-separated SNES values"));
1281 }
1282
1283 // Color picker popup for custom palette editing
1284 if (ImGui::BeginPopup(gui::MakePopupId(gui::EditorNames::kPalette,
1285 "PanelCustomPaletteColorEdit")
1286 .c_str())) {
1287 if (edit_palette_index_ >= 0 &&
1291 "Edit Color", &custom_palette_[edit_palette_index_],
1292 kColorPopupFlags | ImGuiColorEditFlags_PickerHueWheel)) {
1293 // Color was changed, add to recently used
1295 }
1296 }
1297 ImGui::EndPopup();
1298 }
1299}
1300
1301void PaletteEditor::JumpToPalette(const std::string& group_name,
1302 int palette_index) {
1304 return;
1305 }
1306 auto* window_manager = dependencies_.window_manager;
1307 const size_t session_id = dependencies_.session_id;
1308
1309 // Show and focus the appropriate card
1310 if (group_name == "ow_main") {
1311 window_manager->OpenWindow(session_id, "palette.ow_main");
1312 if (ow_main_panel_) {
1315 }
1316 } else if (group_name == "ow_animated") {
1317 window_manager->OpenWindow(session_id, "palette.ow_animated");
1318 if (ow_anim_panel_) {
1321 }
1322 } else if (group_name == "dungeon_main") {
1323 window_manager->OpenWindow(session_id, "palette.dungeon_main");
1324 if (dungeon_main_panel_) {
1327 }
1328 } else if (group_name == "global_sprites") {
1329 window_manager->OpenWindow(session_id, "palette.sprites");
1333 }
1334 } else if (group_name == "sprites_aux1") {
1335 window_manager->OpenWindow(session_id, "palette.sprites_aux1");
1336 if (sprite_aux1_panel_) {
1339 }
1340 } else if (group_name == "sprites_aux2") {
1341 window_manager->OpenWindow(session_id, "palette.sprites_aux2");
1342 if (sprite_aux2_panel_) {
1345 }
1346 } else if (group_name == "sprites_aux3") {
1347 window_manager->OpenWindow(session_id, "palette.sprites_aux3");
1348 if (sprite_aux3_panel_) {
1351 }
1352 } else if (group_name == "armors") {
1353 window_manager->OpenWindow(session_id, "palette.equipment");
1354 if (equipment_panel_) {
1357 }
1358 }
1359
1360 // Show control panel too for easy navigation
1361 window_manager->OpenWindow(session_id, "palette.control_panel");
1362}
1363
1364// ============================================================================
1365// Category and Search UI Methods
1366// ============================================================================
1367
1369 ImGui::SetNextItemWidth(-1);
1370 if (ImGui::InputTextWithHint("##PaletteSearch",
1371 ICON_MD_SEARCH " Search palettes...",
1372 search_buffer_, sizeof(search_buffer_))) {
1373 // Search text changed - UI will update automatically
1374 }
1375}
1376
1377bool PaletteEditor::PassesSearchFilter(const std::string& group_name) const {
1378 if (search_buffer_[0] == '\0')
1379 return true;
1380
1381 // Check if group name or display name matches
1382 return gui::FuzzyMatch(search_buffer_, group_name) ||
1384}
1385
1386bool* PaletteEditor::GetShowFlagForGroup(const std::string& group_name) {
1387 if (group_name == "ow_main")
1388 return &show_ow_main_panel_;
1389 if (group_name == "ow_animated")
1391 if (group_name == "dungeon_main")
1393 if (group_name == "global_sprites")
1394 return &show_sprite_panel_;
1395 if (group_name == "sprites_aux1")
1397 if (group_name == "sprites_aux2")
1399 if (group_name == "sprites_aux3")
1401 if (group_name == "armors")
1402 return &show_equipment_panel_;
1403 return nullptr;
1404}
1405
1407 // Search bar at top
1408 DrawSearchBar();
1409 ImGui::Separator();
1410
1411 const auto& categories = GetPaletteCategories();
1412
1413 for (size_t cat_idx = 0; cat_idx < categories.size(); cat_idx++) {
1414 const auto& cat = categories[cat_idx];
1415
1416 // Check if any items in category match search
1417 bool has_visible_items = false;
1418 for (const auto& group_name : cat.group_names) {
1419 if (PassesSearchFilter(group_name)) {
1420 has_visible_items = true;
1421 break;
1422 }
1423 }
1424
1425 if (!has_visible_items)
1426 continue;
1427
1428 ImGui::PushID(static_cast<int>(cat_idx));
1429
1430 // Collapsible header for category with icon
1431 std::string header_text =
1432 absl::StrFormat("%s %s", cat.icon, cat.display_name);
1433 bool open = ImGui::CollapsingHeader(header_text.c_str(),
1434 ImGuiTreeNodeFlags_DefaultOpen);
1435
1436 if (open) {
1437 ImGui::Indent(10.0f);
1438 for (const auto& group_name : cat.group_names) {
1439 if (!PassesSearchFilter(group_name))
1440 continue;
1441
1442 bool* show_flag = GetShowFlagForGroup(group_name);
1443 if (show_flag) {
1444 std::string label = GetGroupDisplayName(group_name);
1445
1446 // Show modified indicator
1447 bool is_modified =
1449 if (is_modified) {
1450 label += " *";
1451 }
1452 std::optional<gui::StyleColorGuard> mod_guard;
1453 if (is_modified) {
1454 mod_guard.emplace(ImGuiCol_Text, gui::GetWarningColor());
1455 }
1456
1457 ImGui::Checkbox(label.c_str(), show_flag);
1458 mod_guard.reset();
1459 }
1460 }
1461 ImGui::Unindent(10.0f);
1462 }
1463 ImGui::PopID();
1464 }
1465
1466 ImGui::Separator();
1467
1468 // Utilities section
1469 ImGui::Text(tr("Utilities:"));
1470 ImGui::Indent(10.0f);
1471 ImGui::Checkbox(tr("Quick Access"), &show_quick_access_);
1472 ImGui::Checkbox(tr("Custom Palette"), &show_custom_palette_);
1473 ImGui::Unindent(10.0f);
1474}
1475
1476} // namespace editor
1477} // namespace yaze
#define F32_TO_INT8_SAT(_VAL)
project::ResourceLabelManager * resource_label()
Definition rom.h:162
void set_dirty(bool dirty)
Definition rom.h:146
bool is_loaded() const
Definition rom.h:144
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.
zelda3::GameData * game_data() const
Definition editor.h:320
EditorDependencies dependencies_
Definition editor.h:333
std::string GetIcon() const override
Material Design icon for this panel.
std::function< void()> draw_callback_
float GetPreferredWidth() const override
Get preferred width for this panel (optional)
std::string GetId() const override
Unique identifier for this panel.
void Draw(bool *p_open) override
Draw the panel content.
int GetPriority() const override
Get display priority for menu ordering.
std::string GetEditorCategory() const override
Editor category this panel belongs to.
PaletteControlPanel(std::function< void()> draw_callback)
std::string GetDisplayName() const override
Human-readable name shown in menus and title bars.
absl::Status DrawPaletteGroup(int category, bool right_side=false)
bool * GetShowFlagForGroup(const std::string &group_name)
OverworldAnimatedPalettePanel * ow_anim_panel_
absl::Status ResetColorToOriginal(gfx::SnesPalette &palette, int index, const gfx::SnesPalette &originalPalette)
std::vector< gfx::SnesColor > custom_palette_
SpritesAux1PalettePanel * sprite_aux1_panel_
void DrawCustomPalette()
Draw custom palette editor with enhanced ROM hacking features.
void AddRecentlyUsedColor(const gfx::SnesColor &color)
absl::Status Update() override
bool PassesSearchFilter(const std::string &group_name) const
absl::Status Undo() override
EquipmentPalettePanel * equipment_panel_
SpritePalettePanel * sprite_global_panel_
absl::Status HandleColorPopup(gfx::SnesPalette &palette, int i, int j, int n)
SpritesAux2PalettePanel * sprite_aux2_panel_
std::vector< gfx::SnesColor > recently_used_colors_
palette_internal::PaletteEditorHistory history_
absl::Status Save() override
absl::Status Load() override
void JumpToPalette(const std::string &group_name, int palette_index)
Jump to a specific palette by group and index.
DungeonMainPalettePanel * dungeon_main_panel_
SpritesAux3PalettePanel * sprite_aux3_panel_
OverworldMainPalettePanel * ow_main_panel_
absl::Status Redo() override
absl::Status EditColorInPalette(gfx::SnesPalette &palette, int index)
void SetToastManager(ToastManager *toast_manager)
QuickAccessPalettePanel(std::function< void()> draw_callback)
std::string GetId() const override
Unique identifier for this panel.
void Draw(bool *p_open) override
Draw the panel content.
std::string GetIcon() const override
Material Design icon for this panel.
std::string GetEditorCategory() const override
Editor category this panel belongs to.
int GetPriority() const override
Get display priority for menu ordering.
std::string GetDisplayName() const override
Human-readable name shown in menus and title bars.
float GetPreferredWidth() const override
Get preferred width for this panel (optional)
void Show(const std::string &message, ToastType type=ToastType::kInfo, float ttl_seconds=3.0f)
Base interface for all logical window content components.
void RecordChange(const std::string &group_name, size_t palette_index, size_t color_index, const gfx::SnesColor &original_color, const gfx::SnesColor &new_color)
bool HasUnsavedChanges() const
Check if there are ANY unsaved changes.
bool IsGroupModified(const std::string &group_name) const
Check if a specific palette group has modifications.
void Undo()
Undo the most recent change.
void Initialize(zelda3::GameData *game_data)
Initialize the palette manager with GameData.
void DiscardAllChanges()
Discard ALL unsaved changes.
size_t GetModifiedColorCount() const
Get count of modified colors across all groups.
static PaletteManager & Get()
Get the singleton instance.
absl::Status SaveAllToRom()
Save ALL modified palettes to ROM.
absl::Status ApplyPreviewChanges()
Apply preview changes to other editors without saving to ROM.
void Redo()
Redo the most recently undone change.
RAII timer for automatic timing management.
SNES Color container.
Definition snes_color.h:110
constexpr ImVec4 rgb() const
Get RGB values (WARNING: stored as 0-255 in ImVec4)
Definition snes_color.h:183
constexpr uint16_t snes() const
Get SNES 15-bit color.
Definition snes_color.h:193
Represents a palette of colors for the Super Nintendo Entertainment System (SNES).
const Theme & GetCurrentTheme() const
static ThemeManager & Get()
#define ICON_MD_LANDSCAPE
Definition icons.h:1059
#define ICON_MD_PETS
Definition icons.h:1431
#define ICON_MD_SHIELD
Definition icons.h:1724
#define ICON_MD_SEARCH
Definition icons.h:1673
#define ICON_MD_BRUSH
Definition icons.h:325
#define ICON_MD_FILTER_2
Definition icons.h:752
#define ICON_MD_VISIBILITY
Definition icons.h:2101
#define ICON_MD_CASTLE
Definition icons.h:380
#define ICON_MD_FILTER_3
Definition icons.h:753
#define ICON_MD_ADD
Definition icons.h:86
#define ICON_MD_SAVE
Definition icons.h:1644
#define ICON_MD_FILTER_1
Definition icons.h:751
#define ICON_MD_DELETE
Definition icons.h:530
#define ICON_MD_PALETTE
Definition icons.h:1370
#define ICON_MD_CONTENT_COPY
Definition icons.h:465
#define ICON_MD_COLOR_LENS
Definition icons.h:440
#define ICON_MD_WATER
Definition icons.h:2129
#define ICON_MD_UNDO
Definition icons.h:2039
#define TEXT_WITH_SEPARATOR(text)
Definition macro.h:90
bool AcceptImGuiColorDrop(SnesColor *color, ImVec2 min, ImVec2 max)
int CustomFormatString(char *buf, size_t buf_size, const char *fmt,...)
constexpr ImGuiTableFlags kPaletteTableFlags
constexpr ImGuiColorEditFlags kPalNoAlpha
const std::vector< PaletteCategoryInfo > & GetPaletteCategories()
Get all palette categories with their associated groups.
absl::Status DisplayPalette(gfx::SnesPalette &palette, bool loaded)
Display SNES palette with enhanced ROM hacking features.
constexpr ImGuiColorEditFlags kColorPopupFlags
constexpr ImGuiColorEditFlags kPalButtonFlags
constexpr int kNumPalettes
std::string GetGroupDisplayName(const std::string &group_name)
Get display name for a palette group.
uint16_t ConvertRgbToSnes(const snes_color &color)
Convert RGB (0-255) to SNES 15-bit color.
Definition snes_color.cc:33
std::array< float, 4 > ToFloatArray(const SnesColor &color)
constexpr const char * kPalette
Definition popup_id.h:56
constexpr const char * kSaveError
Definition popup_id.h:85
constexpr const char * kConfirmDiscardAll
Definition popup_id.h:86
constexpr const char * kCopyPopup
Definition popup_id.h:84
constexpr const char * kColorPicker
Definition popup_id.h:83
ImVec4 ConvertColorToImVec4(const Color &color)
Definition color.h:134
bool FuzzyMatch(const std::string &pattern, const std::string &str)
Simple fuzzy match - returns true if all chars in pattern appear in str in order.
Definition search.h:23
ImVec4 ConvertSnesColorToImVec4(const gfx::SnesColor &color)
Convert SnesColor to standard ImVec4 for display.
Definition color.cc:23
ImVec4 GetErrorColor()
Definition ui_helpers.cc:59
IMGUI_API bool SnesColorEdit4(absl::string_view label, gfx::SnesColor *color, ImGuiColorEditFlags flags)
Definition color.cc:58
ImVec4 GetWarningColor()
Definition ui_helpers.cc:54
std::string MakePopupId(size_t session_id, const std::string &editor_name, const std::string &popup_name)
Generate session-aware popup IDs to prevent conflicts in multi-editor layouts.
Definition popup_id.h:23
gfx::SnesColor ConvertImVec4ToSnesColor(const ImVec4 &color)
Convert standard ImVec4 to SnesColor.
Definition color.cc:36
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
WorkspaceWindowManager * window_manager
Definition editor.h:181
PaletteGroup * get_group(const std::string &group_name)
Represents a group of palettes.
std::string CreateOrGetLabel(const std::string &type, const std::string &key, const std::string &defaultValue)
Definition project.cc:2366
void SelectableLabelWithNameEdit(bool selected, const std::string &type, const std::string &key, const std::string &defaultValue)
Definition project.cc:2341
gfx::PaletteGroupMap palette_groups
Definition game_data.h:92