yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
palette_controls_panel.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <string>
5#include <string_view>
6
7#include "absl/strings/str_format.h"
10#include "app/gui/core/icons.h"
12#include "app/gui/core/style.h"
15#include "imgui/imgui.h"
16
17namespace yaze {
18namespace editor {
19
21
22namespace {
27
28PaletteRowLayout GetPaletteRowLayout(std::string_view group_name,
29 size_t palette_size) {
30 if (group_name == "ow_main" || group_name == "ow_aux" ||
31 group_name == "ow_animated" || group_name == "sprites_aux1" ||
32 group_name == "sprites_aux2" || group_name == "sprites_aux3") {
33 return {7, false};
34 }
35 if (group_name == "global_sprites" || group_name == "armors" ||
36 group_name == "dungeon_main") {
37 return {15, false};
38 }
39 if (group_name == "hud" || group_name == "ow_mini_map") {
40 return {16, true};
41 }
42 if (group_name == "swords") {
43 return {3, false};
44 }
45 if (group_name == "shields") {
46 return {4, false};
47 }
48 if (group_name == "grass") {
49 return {3, false};
50 }
51 if (group_name == "3d_object") {
52 return {8, false};
53 }
54
55 if (palette_size % 16 == 0) {
56 return {16, true};
57 }
58 if (palette_size % 15 == 0) {
59 return {15, false};
60 }
61 if (palette_size % 7 == 0) {
62 return {7, false};
63 }
64
65 int fallback = palette_size > 0 ? static_cast<int>(palette_size) : 1;
66 return {fallback, false};
67}
68
69int GetPaletteRowCount(size_t palette_size, int colors_per_row) {
70 if (colors_per_row <= 0) {
71 return 1;
72 }
73 return static_cast<int>((palette_size + colors_per_row - 1) / colors_per_row);
74}
75
76bool ComputePaletteSlice(std::string_view group_name,
77 const gfx::SnesPalette& palette, int row_index,
78 size_t& out_offset, int& out_length) {
79 if (palette.empty()) {
80 return false;
81 }
82
83 const auto layout = GetPaletteRowLayout(group_name, palette.size());
84 const int max_rows =
85 GetPaletteRowCount(palette.size(), layout.colors_per_row);
86 const int clamped_row = std::clamp(row_index, 0, std::max(0, max_rows - 1));
87 const int row_offset = clamped_row * layout.colors_per_row;
88 const size_t offset = static_cast<size_t>(
89 row_offset + (layout.has_explicit_transparent ? 1 : 0));
90 int length =
91 layout.colors_per_row - (layout.has_explicit_transparent ? 1 : 0);
92 length = std::clamp(length, 1, 15);
93
94 if (offset >= palette.size()) {
95 return false;
96 }
97
98 if (offset + length > palette.size()) {
99 length = static_cast<int>(palette.size() - offset);
100 }
101
102 out_offset = offset;
103 out_length = length;
104 return out_length > 0;
105}
106} // namespace
107
109 // Initialize with default palette group
113}
114
115void PaletteControlsPanel::Draw(bool* p_open) {
116 // EditorPanel interface - delegate to existing Update() logic
117 if (!rom_ || !rom_->is_loaded()) {
118 ImGui::TextDisabled("Load a ROM to manage palettes");
119 return;
120 }
121
122 DrawPresets();
123 ImGui::Separator();
125 ImGui::Separator();
127 ImGui::Separator();
129}
130
132 if (!rom_ || !rom_->is_loaded()) {
133 ImGui::TextDisabled("Load a ROM to manage palettes");
134 return absl::OkStatus();
135 }
136
137 DrawPresets();
138 ImGui::Separator();
140 ImGui::Separator();
142 ImGui::Separator();
144
145 return absl::OkStatus();
146}
147
149 gui::TextWithSeparators("Quick Presets");
150
151 if (ImGui::Button(ICON_MD_LANDSCAPE " Overworld")) {
152 state_->palette_group_index = 0; // Dungeon Main (used for overworld too)
154 state_->refresh_graphics = true;
155 }
156 HOVER_HINT("Standard overworld palette");
157
158 ImGui::SameLine();
159
160 if (ImGui::Button(ICON_MD_CASTLE " Dungeon")) {
161 state_->palette_group_index = 0; // Dungeon Main
163 state_->refresh_graphics = true;
164 }
165 HOVER_HINT("Standard dungeon palette");
166
167 ImGui::SameLine();
168
169 if (ImGui::Button(ICON_MD_PERSON " Sprites")) {
170 state_->palette_group_index = 4; // Sprites Aux1
172 state_->refresh_graphics = true;
173 }
174 HOVER_HINT("Sprite/enemy palette");
175
176 if (ImGui::Button(ICON_MD_ACCOUNT_BOX " Link")) {
177 state_->palette_group_index = 3; // Sprite Aux3 (Link's palettes)
179 state_->refresh_graphics = true;
180 }
181 HOVER_HINT("Link's palette");
182
183 ImGui::SameLine();
184
185 if (ImGui::Button(ICON_MD_MENU " HUD")) {
186 state_->palette_group_index = 6; // HUD palettes
188 state_->refresh_graphics = true;
189 }
190 HOVER_HINT("HUD/menu palette");
191}
192
194 gui::TextWithSeparators("Palette Selection");
195
196 // Palette group combo
197 ImGui::SetNextItemWidth(gui::LayoutHelpers::GetComboWidth());
198 if (ImGui::Combo("Group",
199 reinterpret_cast<int*>(&state_->palette_group_index),
200 kPaletteGroupAddressesKeys,
201 IM_ARRAYSIZE(kPaletteGroupAddressesKeys))) {
202 state_->refresh_graphics = true;
203 }
204
205 // Palette index within group
206 ImGui::SetNextItemWidth(
208 int palette_idx = static_cast<int>(state_->palette_index);
209 if (ImGui::InputInt("Palette", &palette_idx)) {
210 state_->palette_index = static_cast<uint64_t>(std::max(0, palette_idx));
211 state_->refresh_graphics = true;
212 }
213 HOVER_HINT("Palette index within the group");
214
215 // Sub-palette index (for multi-row palettes)
216 ImGui::SetNextItemWidth(
218 int sub_idx = static_cast<int>(state_->sub_palette_index);
219 if (ImGui::InputInt("Sub-Palette", &sub_idx)) {
220 state_->sub_palette_index = static_cast<uint64_t>(std::max(0, sub_idx));
221 state_->refresh_graphics = true;
222 }
223 HOVER_HINT("Sub-palette row (0-7 for SNES 128-color palettes)");
224}
225
227 gui::TextWithSeparators("Current Palette");
228
229 // Get the current palette from GameData
230 if (!game_data_)
231 return;
232 auto palette_group_result = game_data_->palette_groups.get_group(
233 kPaletteGroupAddressesKeys[state_->palette_group_index]);
234 if (!palette_group_result) {
235 ImGui::TextDisabled("Invalid palette group");
236 return;
237 }
238
239 auto palette_group = *palette_group_result;
240 if (state_->palette_index >= palette_group.size()) {
241 ImGui::TextDisabled("Invalid palette index");
242 return;
243 }
244
245 auto palette = palette_group.palette(state_->palette_index);
246
247 auto palette_group_name =
248 std::string_view(kPaletteGroupAddressesKeys[state_->palette_group_index]);
249 auto layout = GetPaletteRowLayout(palette_group_name, palette.size());
250
251 int colors_per_row = layout.colors_per_row;
252 int total_colors = static_cast<int>(palette.size());
253 int num_rows = GetPaletteRowCount(palette.size(), colors_per_row);
254 if (state_->sub_palette_index >= static_cast<uint64_t>(num_rows)) {
256 }
257
258 for (int row = 0; row < num_rows; row++) {
259 for (int col = 0; col < colors_per_row; col++) {
260 int idx = row * colors_per_row + col;
261 if (idx >= total_colors)
262 break;
263
264 if (col > 0)
265 ImGui::SameLine();
266
267 auto& color = palette[idx];
268 ImVec4 im_color(color.rgb().x / 255.0f, color.rgb().y / 255.0f,
269 color.rgb().z / 255.0f, 1.0f);
270
271 // Highlight current sub-palette row
272 bool in_sub_palette =
273 (row == static_cast<int>(state_->sub_palette_index));
274 std::optional<gui::StyleVarGuard> pal_border_var;
275 std::optional<gui::StyleColorGuard> pal_border_color;
276 if (in_sub_palette) {
277 pal_border_var.emplace(ImGuiStyleVar_FrameBorderSize, 2.0f);
278 pal_border_color.emplace(ImGuiCol_Border, gui::GetWarningColor());
279 }
280
281 std::string id = absl::StrFormat("##PalColor%d", idx);
282 if (ImGui::ColorButton(id.c_str(), im_color,
283 ImGuiColorEditFlags_NoTooltip, ImVec2(18, 18))) {
284 // Clicking a color in a row selects that sub-palette
285 state_->sub_palette_index = static_cast<uint64_t>(row);
286 state_->refresh_graphics = true;
287 }
288
289 pal_border_color.reset();
290 pal_border_var.reset();
291
292 if (ImGui::IsItemHovered()) {
293 ImGui::BeginTooltip();
294 ImGui::Text("Index: %d (Row %d, Col %d)", idx, row, col);
295 ImGui::Text("SNES: $%04X", color.snes());
296 ImGui::Text("RGB: %d, %d, %d", static_cast<int>(color.rgb().x),
297 static_cast<int>(color.rgb().y),
298 static_cast<int>(color.rgb().z));
299 ImGui::EndTooltip();
300 }
301 }
302 }
303
304 // Row selection buttons
305 ImGui::Text("Sub-palette Row:");
306 for (int i = 0; i < std::min(8, num_rows); i++) {
307 if (i > 0)
308 ImGui::SameLine();
309 bool selected = (state_->sub_palette_index == static_cast<uint64_t>(i));
310 {
311 std::optional<gui::StyleColorGuard> sel_guard;
312 if (selected) {
313 sel_guard.emplace(ImGuiCol_Button, gui::GetSelectedColor());
314 }
315 if (ImGui::SmallButton(absl::StrFormat("%d", i).c_str())) {
316 state_->sub_palette_index = static_cast<uint64_t>(i);
317 state_->refresh_graphics = true;
318 }
319 }
320 }
321}
322
324 gui::TextWithSeparators("Apply Palette");
325
326 // Apply to current sheet
327 bool no_sheets = state_->open_sheets.empty();
328 ImGui::BeginDisabled(no_sheets);
329 if (ImGui::Button(ICON_MD_BRUSH " Apply to Current Sheet")) {
331 }
332 ImGui::EndDisabled();
333 if (no_sheets && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
334 ImGui::SetTooltip("Open a graphics sheet first");
335 } else {
336 HOVER_HINT("Apply palette to the currently selected sheet");
337 }
338
339 ImGui::SameLine();
340
341 // Apply to all sheets
342 if (ImGui::Button(ICON_MD_FORMAT_PAINT " Apply to All Sheets")) {
344 }
345 HOVER_HINT("Apply palette to all active graphics sheets");
346
347 // Apply to selected sheets (multi-select)
348 if (!state_->selected_sheets.empty()) {
349 if (ImGui::Button(absl::StrFormat(ICON_MD_CHECKLIST
350 " Apply to %zu Selected",
351 state_->selected_sheets.size())
352 .c_str())) {
353 for (uint16_t sheet_id : state_->selected_sheets) {
354 ApplyPaletteToSheet(sheet_id);
355 }
356 }
357 HOVER_HINT("Apply palette to all selected sheets in browser");
358 }
359
360 // Refresh button
361 ImGui::Separator();
362 if (ImGui::Button(ICON_MD_REFRESH " Refresh Graphics")) {
363 state_->refresh_graphics = true;
364 if (!state_->open_sheets.empty()) {
366 }
367 }
368 HOVER_HINT("Force refresh of current sheet graphics");
369}
370
372 if (!rom_ || !rom_->is_loaded() || !game_data_)
373 return;
374
375 auto palette_group_name =
376 std::string_view(kPaletteGroupAddressesKeys[state_->palette_group_index]);
377 auto palette_group_result =
378 game_data_->palette_groups.get_group(std::string(palette_group_name));
379 if (!palette_group_result)
380 return;
381
382 auto palette_group = *palette_group_result;
383 if (state_->palette_index >= palette_group.size())
384 return;
385
386 auto palette = palette_group.palette(state_->palette_index);
387 if (palette.empty()) {
388 return;
389 }
390
391 auto& sheet = gfx::Arena::Get().mutable_gfx_sheets()->at(sheet_id);
392 if (sheet.is_active() && sheet.surface()) {
393 size_t palette_offset = 0;
394 int palette_length = 0;
395 if (ComputePaletteSlice(palette_group_name, palette,
396 static_cast<int>(state_->sub_palette_index),
397 palette_offset, palette_length)) {
398 sheet.SetPaletteWithTransparent(palette, palette_offset, palette_length);
399 } else {
400 sheet.SetPaletteWithTransparent(
401 palette, 0, std::min(7, static_cast<int>(palette.size())));
402 }
404 }
405}
406
408 if (!rom_ || !rom_->is_loaded() || !game_data_)
409 return;
410
411 auto palette_group_name =
412 std::string_view(kPaletteGroupAddressesKeys[state_->palette_group_index]);
413 auto palette_group_result =
414 game_data_->palette_groups.get_group(std::string(palette_group_name));
415 if (!palette_group_result)
416 return;
417
418 auto palette_group = *palette_group_result;
419 if (state_->palette_index >= palette_group.size())
420 return;
421
422 auto palette = palette_group.palette(state_->palette_index);
423 if (palette.empty()) {
424 return;
425 }
426 size_t palette_offset = 0;
427 int palette_length = 0;
428 const bool has_slice = ComputePaletteSlice(
429 palette_group_name, palette, static_cast<int>(state_->sub_palette_index),
430 palette_offset, palette_length);
431
432 for (int i = 0; i < zelda3::kNumGfxSheets; i++) {
433 auto& sheet = gfx::Arena::Get().mutable_gfx_sheets()->data()[i];
434 if (sheet.is_active() && sheet.surface()) {
435 if (has_slice) {
436 sheet.SetPaletteWithTransparent(palette, palette_offset,
437 palette_length);
438 } else {
439 sheet.SetPaletteWithTransparent(
440 palette, 0, std::min(7, static_cast<int>(palette.size())));
441 }
443 }
444 }
445}
446
447} // namespace editor
448} // namespace yaze
bool is_loaded() const
Definition rom.h:132
absl::Status Update()
Legacy Update method for backward compatibility.
void DrawPresets()
Draw quick preset buttons.
void Draw(bool *p_open) override
Draw the palette controls UI (EditorPanel interface)
void ApplyPaletteToSheet(uint16_t sheet_id)
Apply current palette to specified sheet.
void DrawPaletteDisplay()
Draw the current palette display.
void ApplyPaletteToAllSheets()
Apply current palette to all active sheets.
void DrawPaletteGroupSelector()
Draw palette group selection.
auto mutable_gfx_sheets()
Get mutable reference to all graphics sheets.
Definition arena.h:178
void NotifySheetModified(int sheet_index)
Notify Arena that a graphics sheet has been modified.
Definition arena.cc:393
static Arena & Get()
Definition arena.cc:21
Represents a palette of colors for the Super Nintendo Entertainment System (SNES).
static float GetStandardInputWidth()
#define ICON_MD_LANDSCAPE
Definition icons.h:1059
#define ICON_MD_CHECKLIST
Definition icons.h:402
#define ICON_MD_BRUSH
Definition icons.h:325
#define ICON_MD_REFRESH
Definition icons.h:1572
#define ICON_MD_CASTLE
Definition icons.h:380
#define ICON_MD_PERSON
Definition icons.h:1415
#define ICON_MD_ACCOUNT_BOX
Definition icons.h:81
#define ICON_MD_MENU
Definition icons.h:1196
#define ICON_MD_FORMAT_PAINT
Definition icons.h:841
#define HOVER_HINT(string)
Definition macro.h:24
PaletteRowLayout GetPaletteRowLayout(std::string_view group_name, size_t palette_size)
bool ComputePaletteSlice(std::string_view group_name, const gfx::SnesPalette &palette, int row_index, size_t &out_offset, int &out_length)
int GetPaletteRowCount(size_t palette_size, int colors_per_row)
constexpr const char * kPaletteGroupAddressesKeys[]
ImVec4 GetSelectedColor()
Definition ui_helpers.cc:98
ImVec4 GetWarningColor()
Definition ui_helpers.cc:53
void TextWithSeparators(const absl::string_view &text)
Definition style.cc:1315
constexpr uint32_t kNumGfxSheets
Definition game_data.h:25
PaletteGroup * get_group(const std::string &group_name)
auto palette(int i) const
gfx::PaletteGroupMap palette_groups
Definition game_data.h:89