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