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#include "util/i18n/tr.h"
3
4#include <algorithm>
5#include <string>
6#include <string_view>
7
8#include "absl/strings/str_format.h"
12#include "app/gui/core/icons.h"
14#include "app/gui/core/style.h"
17#include "imgui/imgui.h"
18
19namespace yaze {
20namespace editor {
21
25using internal::PaletteRowLayout;
26
27namespace {
28bool ComputePaletteSlice(std::string_view group_name,
29 const gfx::SnesPalette& palette, int row_index,
30 size_t& out_offset, int& out_length) {
31 if (palette.empty()) {
32 return false;
33 }
34
35 const auto layout = GetPaletteRowLayout(group_name, palette.size());
36 const int max_rows =
37 GetPaletteRowCount(palette.size(), layout.colors_per_row);
38 const int clamped_row = std::clamp(row_index, 0, std::max(0, max_rows - 1));
39 const int row_offset = clamped_row * layout.colors_per_row;
40 const size_t offset = static_cast<size_t>(
41 row_offset + (layout.has_explicit_transparent ? 1 : 0));
42 int length =
43 layout.colors_per_row - (layout.has_explicit_transparent ? 1 : 0);
44 length = std::clamp(length, 1, 15);
45
46 if (offset >= palette.size()) {
47 return false;
48 }
49
50 if (offset + length > palette.size()) {
51 length = static_cast<int>(palette.size() - offset);
52 }
53
54 out_offset = offset;
55 out_length = length;
56 return out_length > 0;
57}
58} // namespace
59
61 // Initialize with default palette group
65}
66
67void PaletteControlsView::Draw(bool* p_open) {
68 // WindowContent interface - delegate to existing Update() logic
69 if (!rom_ || !rom_->is_loaded()) {
70 ImGui::TextDisabled(tr("Load a ROM to manage palettes"));
71 return;
72 }
73
75 ImGui::Separator();
77 ImGui::Separator();
79 ImGui::Separator();
81}
82
84 if (!rom_ || !rom_->is_loaded()) {
85 ImGui::TextDisabled(tr("Load a ROM to manage palettes"));
86 return absl::OkStatus();
87 }
88
90 ImGui::Separator();
92 ImGui::Separator();
94 ImGui::Separator();
96
97 return absl::OkStatus();
98}
99
101 gui::TextWithSeparators("Quick Presets");
102
103 if (ImGui::Button(ICON_MD_LANDSCAPE " Overworld")) {
104 state_->palette_group_index = 0; // Dungeon Main (used for overworld too)
106 state_->refresh_graphics = true;
107 }
108 HOVER_HINT("Standard overworld palette");
109
110 ImGui::SameLine();
111
112 if (ImGui::Button(ICON_MD_CASTLE " Dungeon")) {
113 state_->palette_group_index = 0; // Dungeon Main
115 state_->refresh_graphics = true;
116 }
117 HOVER_HINT("Standard dungeon palette");
118
119 ImGui::SameLine();
120
121 if (ImGui::Button(ICON_MD_PERSON " Sprites")) {
122 state_->palette_group_index = 4; // Sprites Aux1
124 state_->refresh_graphics = true;
125 }
126 HOVER_HINT("Sprite/enemy palette");
127
128 if (ImGui::Button(ICON_MD_ACCOUNT_BOX " Link")) {
129 state_->palette_group_index = 3; // Sprite Aux3 (Link's palettes)
131 state_->refresh_graphics = true;
132 }
133 HOVER_HINT("Link's palette");
134
135 ImGui::SameLine();
136
137 if (ImGui::Button(ICON_MD_MENU " HUD")) {
138 state_->palette_group_index = 6; // HUD palettes
140 state_->refresh_graphics = true;
141 }
142 HOVER_HINT("HUD/menu palette");
143}
144
146 gui::TextWithSeparators("Palette Selection");
147
148 // Palette group combo
149 ImGui::SetNextItemWidth(gui::LayoutHelpers::GetComboWidth());
150 if (ImGui::Combo(tr("Group"),
151 reinterpret_cast<int*>(&state_->palette_group_index),
152 kPaletteGroupAddressesKeys,
153 IM_ARRAYSIZE(kPaletteGroupAddressesKeys))) {
154 state_->refresh_graphics = true;
155 }
156
157 // Palette index within group
158 ImGui::SetNextItemWidth(gui::LayoutHelpers::GetStandardInputWidth() * 0.75f);
159 int palette_idx = static_cast<int>(state_->palette_index);
160 if (ImGui::InputInt(tr("Palette"), &palette_idx)) {
161 state_->palette_index = static_cast<uint64_t>(std::max(0, palette_idx));
162 state_->refresh_graphics = true;
163 }
164 HOVER_HINT("Palette index within the group");
165
166 // Sub-palette index (for multi-row palettes)
167 ImGui::SetNextItemWidth(gui::LayoutHelpers::GetStandardInputWidth() * 0.75f);
168 int sub_idx = static_cast<int>(state_->sub_palette_index);
169 if (ImGui::InputInt(tr("Sub-Palette"), &sub_idx)) {
170 state_->sub_palette_index = static_cast<uint64_t>(std::max(0, sub_idx));
171 state_->refresh_graphics = true;
172 }
173 HOVER_HINT("Sub-palette row (0-7 for SNES 128-color palettes)");
174}
175
177 gui::TextWithSeparators("Current Palette");
178
179 // Get the current palette from GameData
180 if (!game_data_)
181 return;
182 auto palette_group_result = game_data_->palette_groups.get_group(
183 kPaletteGroupAddressesKeys[state_->palette_group_index]);
184 if (!palette_group_result) {
185 ImGui::TextDisabled(tr("Invalid palette group"));
186 return;
187 }
188
189 auto palette_group = *palette_group_result;
190 if (state_->palette_index >= palette_group.size()) {
191 ImGui::TextDisabled(tr("Invalid palette index"));
192 return;
193 }
194
195 auto palette = palette_group.palette(state_->palette_index);
196
197 auto palette_group_name =
198 std::string_view(kPaletteGroupAddressesKeys[state_->palette_group_index]);
199 auto layout = GetPaletteRowLayout(palette_group_name, palette.size());
200
201 int colors_per_row = layout.colors_per_row;
202 int total_colors = static_cast<int>(palette.size());
203 int num_rows = GetPaletteRowCount(palette.size(), colors_per_row);
204 if (state_->sub_palette_index >= static_cast<uint64_t>(num_rows)) {
206 }
207
208 for (int row = 0; row < num_rows; row++) {
209 for (int col = 0; col < colors_per_row; col++) {
210 int idx = row * colors_per_row + col;
211 if (idx >= total_colors)
212 break;
213
214 if (col > 0)
215 ImGui::SameLine();
216
217 auto& color = palette[idx];
218 ImVec4 im_color(color.rgb().x / 255.0f, color.rgb().y / 255.0f,
219 color.rgb().z / 255.0f, 1.0f);
220
221 // Highlight current sub-palette row
222 bool in_sub_palette =
223 (row == static_cast<int>(state_->sub_palette_index));
224 std::optional<gui::StyleVarGuard> pal_border_var;
225 std::optional<gui::StyleColorGuard> pal_border_color;
226 if (in_sub_palette) {
227 pal_border_var.emplace(ImGuiStyleVar_FrameBorderSize, 2.0f);
228 pal_border_color.emplace(ImGuiCol_Border, gui::GetWarningColor());
229 }
230
231 std::string id = absl::StrFormat("##PalColor%d", idx);
232 if (ImGui::ColorButton(id.c_str(), im_color,
233 ImGuiColorEditFlags_NoTooltip, ImVec2(18, 18))) {
234 // Clicking a color in a row selects that sub-palette
235 state_->sub_palette_index = static_cast<uint64_t>(row);
236 state_->refresh_graphics = true;
237 }
238
239 pal_border_color.reset();
240 pal_border_var.reset();
241
242 if (ImGui::IsItemHovered()) {
243 ImGui::BeginTooltip();
244 ImGui::Text(tr("Index: %d (Row %d, Col %d)"), idx, row, col);
245 ImGui::Text(tr("SNES: $%04X"), color.snes());
246 ImGui::Text(tr("RGB: %d, %d, %d"), static_cast<int>(color.rgb().x),
247 static_cast<int>(color.rgb().y),
248 static_cast<int>(color.rgb().z));
249 ImGui::EndTooltip();
250 }
251 }
252 }
253
254 // Row selection buttons
255 ImGui::Text(tr("Sub-palette Row:"));
256 for (int i = 0; i < std::min(8, num_rows); i++) {
257 if (i > 0)
258 ImGui::SameLine();
259 bool selected = (state_->sub_palette_index == static_cast<uint64_t>(i));
260 {
261 std::optional<gui::StyleColorGuard> sel_guard;
262 if (selected) {
263 sel_guard.emplace(ImGuiCol_Button, gui::GetSelectedColor());
264 }
265 if (ImGui::SmallButton(absl::StrFormat("%d", i).c_str())) {
266 state_->sub_palette_index = static_cast<uint64_t>(i);
267 state_->refresh_graphics = true;
268 }
269 }
270 }
271}
272
274 gui::TextWithSeparators("Apply Palette");
275
276 // Apply to current sheet
277 bool no_sheets = state_->open_sheets.empty();
278 ImGui::BeginDisabled(no_sheets);
279 if (ImGui::Button(ICON_MD_BRUSH " Apply to Current Sheet")) {
281 }
282 ImGui::EndDisabled();
283 if (no_sheets && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
284 ImGui::SetTooltip(tr("Open a graphics sheet first"));
285 } else {
286 HOVER_HINT("Apply palette to the currently selected sheet");
287 }
288
289 ImGui::SameLine();
290
291 // Apply to all sheets
292 if (ImGui::Button(ICON_MD_FORMAT_PAINT " Apply to All Sheets")) {
294 }
295 HOVER_HINT("Apply palette to all active graphics sheets");
296
297 // Apply to selected sheets (multi-select)
298 if (!state_->selected_sheets.empty()) {
299 if (ImGui::Button(absl::StrFormat(ICON_MD_CHECKLIST
300 " Apply to %zu Selected",
301 state_->selected_sheets.size())
302 .c_str())) {
303 for (uint16_t sheet_id : state_->selected_sheets) {
304 ApplyPaletteToSheet(sheet_id);
305 }
306 }
307 HOVER_HINT("Apply palette to all selected sheets in browser");
308 }
309
310 // Refresh button
311 ImGui::Separator();
312 if (ImGui::Button(ICON_MD_REFRESH " Refresh Graphics")) {
313 state_->refresh_graphics = true;
314 if (!state_->open_sheets.empty()) {
316 }
317 }
318 HOVER_HINT("Force refresh of current sheet graphics");
319}
320
322 if (!rom_ || !rom_->is_loaded() || !game_data_)
323 return;
324
325 auto palette_group_name =
326 std::string_view(kPaletteGroupAddressesKeys[state_->palette_group_index]);
327 auto palette_group_result =
328 game_data_->palette_groups.get_group(std::string(palette_group_name));
329 if (!palette_group_result)
330 return;
331
332 auto palette_group = *palette_group_result;
333 if (state_->palette_index >= palette_group.size())
334 return;
335
336 auto palette = palette_group.palette(state_->palette_index);
337 if (palette.empty()) {
338 return;
339 }
340
341 auto& sheet = gfx::Arena::Get().mutable_gfx_sheets()->at(sheet_id);
342 if (sheet.is_active() && sheet.surface()) {
343 size_t palette_offset = 0;
344 int palette_length = 0;
345 if (ComputePaletteSlice(palette_group_name, palette,
346 static_cast<int>(state_->sub_palette_index),
347 palette_offset, palette_length)) {
348 sheet.SetPaletteWithTransparent(palette, palette_offset, palette_length);
349 } else {
350 sheet.SetPaletteWithTransparent(
351 palette, 0, std::min(7, static_cast<int>(palette.size())));
352 }
354 }
355}
356
358 if (!rom_ || !rom_->is_loaded() || !game_data_)
359 return;
360
361 auto palette_group_name =
362 std::string_view(kPaletteGroupAddressesKeys[state_->palette_group_index]);
363 auto palette_group_result =
364 game_data_->palette_groups.get_group(std::string(palette_group_name));
365 if (!palette_group_result)
366 return;
367
368 auto palette_group = *palette_group_result;
369 if (state_->palette_index >= palette_group.size())
370 return;
371
372 auto palette = palette_group.palette(state_->palette_index);
373 if (palette.empty()) {
374 return;
375 }
376 size_t palette_offset = 0;
377 int palette_length = 0;
378 const bool has_slice = ComputePaletteSlice(
379 palette_group_name, palette, static_cast<int>(state_->sub_palette_index),
380 palette_offset, palette_length);
381
382 for (int i = 0; i < zelda3::kNumGfxSheets; i++) {
383 auto& sheet = gfx::Arena::Get().mutable_gfx_sheets()->data()[i];
384 if (sheet.is_active() && sheet.surface()) {
385 if (has_slice) {
386 sheet.SetPaletteWithTransparent(palette, palette_offset,
387 palette_length);
388 } else {
389 sheet.SetPaletteWithTransparent(
390 palette, 0, std::min(7, static_cast<int>(palette.size())));
391 }
393 }
394 }
395}
396
397} // namespace editor
398} // namespace yaze
bool is_loaded() const
Definition rom.h:144
void DrawApplyButtons()
Draw apply buttons.
void Initialize()
Initialize the view.
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
int GetPaletteRowCount(std::size_t palette_size, int colors_per_row)
PaletteRowLayout GetPaletteRowLayout(std::string_view group_name, std::size_t palette_size)
constexpr const char * kPaletteGroupAddressesKeys[]
ImVec4 GetSelectedColor()
Definition ui_helpers.cc:99
ImVec4 GetWarningColor()
Definition ui_helpers.cc:54
void TextWithSeparators(const absl::string_view &text)
Definition style.cc:1325
constexpr uint32_t kNumGfxSheets
Definition game_data.h:26
PaletteGroup * get_group(const std::string &group_name)
auto palette(int i) const
gfx::PaletteGroupMap palette_groups
Definition game_data.h:92