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