yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
palette_controls_view.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 PaletteControlsView::Draw(bool* p_open) {
116 // WindowContent 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(gui::LayoutHelpers::GetStandardInputWidth() * 0.75f);
207 int palette_idx = static_cast<int>(state_->palette_index);
208 if (ImGui::InputInt("Palette", &palette_idx)) {
209 state_->palette_index = static_cast<uint64_t>(std::max(0, palette_idx));
210 state_->refresh_graphics = true;
211 }
212 HOVER_HINT("Palette index within the group");
213
214 // Sub-palette index (for multi-row palettes)
215 ImGui::SetNextItemWidth(gui::LayoutHelpers::GetStandardInputWidth() * 0.75f);
216 int sub_idx = static_cast<int>(state_->sub_palette_index);
217 if (ImGui::InputInt("Sub-Palette", &sub_idx)) {
218 state_->sub_palette_index = static_cast<uint64_t>(std::max(0, sub_idx));
219 state_->refresh_graphics = true;
220 }
221 HOVER_HINT("Sub-palette row (0-7 for SNES 128-color palettes)");
222}
223
225 gui::TextWithSeparators("Current Palette");
226
227 // Get the current palette from GameData
228 if (!game_data_)
229 return;
230 auto palette_group_result = game_data_->palette_groups.get_group(
231 kPaletteGroupAddressesKeys[state_->palette_group_index]);
232 if (!palette_group_result) {
233 ImGui::TextDisabled("Invalid palette group");
234 return;
235 }
236
237 auto palette_group = *palette_group_result;
238 if (state_->palette_index >= palette_group.size()) {
239 ImGui::TextDisabled("Invalid palette index");
240 return;
241 }
242
243 auto palette = palette_group.palette(state_->palette_index);
244
245 auto palette_group_name =
246 std::string_view(kPaletteGroupAddressesKeys[state_->palette_group_index]);
247 auto layout = GetPaletteRowLayout(palette_group_name, palette.size());
248
249 int colors_per_row = layout.colors_per_row;
250 int total_colors = static_cast<int>(palette.size());
251 int num_rows = GetPaletteRowCount(palette.size(), colors_per_row);
252 if (state_->sub_palette_index >= static_cast<uint64_t>(num_rows)) {
254 }
255
256 for (int row = 0; row < num_rows; row++) {
257 for (int col = 0; col < colors_per_row; col++) {
258 int idx = row * colors_per_row + col;
259 if (idx >= total_colors)
260 break;
261
262 if (col > 0)
263 ImGui::SameLine();
264
265 auto& color = palette[idx];
266 ImVec4 im_color(color.rgb().x / 255.0f, color.rgb().y / 255.0f,
267 color.rgb().z / 255.0f, 1.0f);
268
269 // Highlight current sub-palette row
270 bool in_sub_palette =
271 (row == static_cast<int>(state_->sub_palette_index));
272 std::optional<gui::StyleVarGuard> pal_border_var;
273 std::optional<gui::StyleColorGuard> pal_border_color;
274 if (in_sub_palette) {
275 pal_border_var.emplace(ImGuiStyleVar_FrameBorderSize, 2.0f);
276 pal_border_color.emplace(ImGuiCol_Border, gui::GetWarningColor());
277 }
278
279 std::string id = absl::StrFormat("##PalColor%d", idx);
280 if (ImGui::ColorButton(id.c_str(), im_color,
281 ImGuiColorEditFlags_NoTooltip, ImVec2(18, 18))) {
282 // Clicking a color in a row selects that sub-palette
283 state_->sub_palette_index = static_cast<uint64_t>(row);
284 state_->refresh_graphics = true;
285 }
286
287 pal_border_color.reset();
288 pal_border_var.reset();
289
290 if (ImGui::IsItemHovered()) {
291 ImGui::BeginTooltip();
292 ImGui::Text("Index: %d (Row %d, Col %d)", idx, row, col);
293 ImGui::Text("SNES: $%04X", color.snes());
294 ImGui::Text("RGB: %d, %d, %d", static_cast<int>(color.rgb().x),
295 static_cast<int>(color.rgb().y),
296 static_cast<int>(color.rgb().z));
297 ImGui::EndTooltip();
298 }
299 }
300 }
301
302 // Row selection buttons
303 ImGui::Text("Sub-palette Row:");
304 for (int i = 0; i < std::min(8, num_rows); i++) {
305 if (i > 0)
306 ImGui::SameLine();
307 bool selected = (state_->sub_palette_index == static_cast<uint64_t>(i));
308 {
309 std::optional<gui::StyleColorGuard> sel_guard;
310 if (selected) {
311 sel_guard.emplace(ImGuiCol_Button, gui::GetSelectedColor());
312 }
313 if (ImGui::SmallButton(absl::StrFormat("%d", i).c_str())) {
314 state_->sub_palette_index = static_cast<uint64_t>(i);
315 state_->refresh_graphics = true;
316 }
317 }
318 }
319}
320
322 gui::TextWithSeparators("Apply Palette");
323
324 // Apply to current sheet
325 bool no_sheets = state_->open_sheets.empty();
326 ImGui::BeginDisabled(no_sheets);
327 if (ImGui::Button(ICON_MD_BRUSH " Apply to Current Sheet")) {
329 }
330 ImGui::EndDisabled();
331 if (no_sheets && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
332 ImGui::SetTooltip("Open a graphics sheet first");
333 } else {
334 HOVER_HINT("Apply palette to the currently selected sheet");
335 }
336
337 ImGui::SameLine();
338
339 // Apply to all sheets
340 if (ImGui::Button(ICON_MD_FORMAT_PAINT " Apply to All Sheets")) {
342 }
343 HOVER_HINT("Apply palette to all active graphics sheets");
344
345 // Apply to selected sheets (multi-select)
346 if (!state_->selected_sheets.empty()) {
347 if (ImGui::Button(absl::StrFormat(ICON_MD_CHECKLIST
348 " Apply to %zu Selected",
349 state_->selected_sheets.size())
350 .c_str())) {
351 for (uint16_t sheet_id : state_->selected_sheets) {
352 ApplyPaletteToSheet(sheet_id);
353 }
354 }
355 HOVER_HINT("Apply palette to all selected sheets in browser");
356 }
357
358 // Refresh button
359 ImGui::Separator();
360 if (ImGui::Button(ICON_MD_REFRESH " Refresh Graphics")) {
361 state_->refresh_graphics = true;
362 if (!state_->open_sheets.empty()) {
364 }
365 }
366 HOVER_HINT("Force refresh of current sheet graphics");
367}
368
370 if (!rom_ || !rom_->is_loaded() || !game_data_)
371 return;
372
373 auto palette_group_name =
374 std::string_view(kPaletteGroupAddressesKeys[state_->palette_group_index]);
375 auto palette_group_result =
376 game_data_->palette_groups.get_group(std::string(palette_group_name));
377 if (!palette_group_result)
378 return;
379
380 auto palette_group = *palette_group_result;
381 if (state_->palette_index >= palette_group.size())
382 return;
383
384 auto palette = palette_group.palette(state_->palette_index);
385 if (palette.empty()) {
386 return;
387 }
388
389 auto& sheet = gfx::Arena::Get().mutable_gfx_sheets()->at(sheet_id);
390 if (sheet.is_active() && sheet.surface()) {
391 size_t palette_offset = 0;
392 int palette_length = 0;
393 if (ComputePaletteSlice(palette_group_name, palette,
394 static_cast<int>(state_->sub_palette_index),
395 palette_offset, palette_length)) {
396 sheet.SetPaletteWithTransparent(palette, palette_offset, palette_length);
397 } else {
398 sheet.SetPaletteWithTransparent(
399 palette, 0, std::min(7, static_cast<int>(palette.size())));
400 }
402 }
403}
404
406 if (!rom_ || !rom_->is_loaded() || !game_data_)
407 return;
408
409 auto palette_group_name =
410 std::string_view(kPaletteGroupAddressesKeys[state_->palette_group_index]);
411 auto palette_group_result =
412 game_data_->palette_groups.get_group(std::string(palette_group_name));
413 if (!palette_group_result)
414 return;
415
416 auto palette_group = *palette_group_result;
417 if (state_->palette_index >= palette_group.size())
418 return;
419
420 auto palette = palette_group.palette(state_->palette_index);
421 if (palette.empty()) {
422 return;
423 }
424 size_t palette_offset = 0;
425 int palette_length = 0;
426 const bool has_slice = ComputePaletteSlice(
427 palette_group_name, palette, static_cast<int>(state_->sub_palette_index),
428 palette_offset, palette_length);
429
430 for (int i = 0; i < zelda3::kNumGfxSheets; i++) {
431 auto& sheet = gfx::Arena::Get().mutable_gfx_sheets()->data()[i];
432 if (sheet.is_active() && sheet.surface()) {
433 if (has_slice) {
434 sheet.SetPaletteWithTransparent(palette, palette_offset,
435 palette_length);
436 } else {
437 sheet.SetPaletteWithTransparent(
438 palette, 0, std::min(7, static_cast<int>(palette.size())));
439 }
441 }
442 }
443}
444
445} // namespace editor
446} // namespace yaze
bool is_loaded() const
Definition rom.h:132
void DrawApplyButtons()
Draw apply buttons.
void ApplyPaletteToAllSheets()
Apply current palette to all active sheets.
void DrawPresets()
Draw quick preset buttons.
absl::Status Update()
Legacy Update method for backward compatibility.
void DrawPaletteDisplay()
Draw the current palette display.
void DrawPaletteGroupSelector()
Draw palette group selection.
void ApplyPaletteToSheet(uint16_t sheet_id)
Apply current palette to specified sheet.
void Draw(bool *p_open) override
Draw the palette controls UI (WindowContent interface)
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
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:1320
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:91