yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
gfx_group_editor.cc
Go to the documentation of this file.
1#include "gfx_group_editor.h"
2
3#include "absl/status/status.h"
4#include "absl/strings/str_cat.h"
5#include "absl/strings/str_format.h"
10#include "app/gui/core/color.h"
11#include "app/gui/core/icons.h"
12#include "app/gui/core/input.h"
15#include "imgui/imgui.h"
16#include "rom/rom.h"
17
18namespace yaze {
19namespace editor {
20
21using ImGui::BeginChild;
22using ImGui::BeginCombo;
23using ImGui::BeginGroup;
24using ImGui::BeginTabItem;
25using ImGui::BeginTable;
26using ImGui::EndChild;
27using ImGui::EndCombo;
28using ImGui::EndGroup;
29using ImGui::EndTabItem;
30using ImGui::EndTable;
31using ImGui::GetContentRegionAvail;
32using ImGui::GetStyle;
33using ImGui::IsItemClicked;
34using ImGui::PopID;
35using ImGui::PushID;
36using ImGui::SameLine;
37using ImGui::Selectable;
38using ImGui::Separator;
39using ImGui::SetNextItemWidth;
40using ImGui::SliderFloat;
41using ImGui::TableHeadersRow;
42using ImGui::TableNextColumn;
43using ImGui::TableNextRow;
44using ImGui::TableSetupColumn;
45using ImGui::Text;
46
47using gfx::kPaletteGroupNames;
49
50namespace {
51
52// Constants for sheet display
53constexpr int kSheetDisplayWidth = 256; // 2x scale from 128px sheets
54constexpr int kSheetDisplayHeight = 64; // 2x scale from 32px sheets
55constexpr float kDefaultScale = 2.0f;
56constexpr int kTileSize = 16; // 8px tiles at 2x scale
57
58// Draw a single sheet with proper scaling and unique ID
59void DrawScaledSheet(gui::Canvas& canvas, gfx::Bitmap& sheet, int unique_id,
60 float scale = kDefaultScale) {
61 PushID(unique_id);
62
63 // Calculate scaled dimensions
64 int display_width = static_cast<int>(gfx::kTilesheetWidth * scale);
65 int display_height = static_cast<int>(gfx::kTilesheetHeight * scale);
66
67 // Draw canvas background
68 canvas.DrawBackground(ImVec2(display_width + 1, display_height + 1));
69 canvas.DrawContextMenu();
70
71 // Draw bitmap with proper scale
72 canvas.DrawBitmap(sheet, 2, scale);
73
74 // Draw grid at scaled tile size
75 canvas.DrawGrid(static_cast<int>(8 * scale));
76 canvas.DrawOverlay();
77
78 PopID();
79}
80
81} // namespace
82
83absl::Status GfxGroupEditor::Update() {
84 if (!host_surface_hint_.empty()) {
85 ImGui::TextDisabled("%s", host_surface_hint_.c_str());
86 Separator();
87 }
88
89 // Palette controls at top for all tabs
91 Separator();
92
93 if (gui::BeginThemedTabBar("##GfxGroupEditorTabs")) {
94 if (BeginTabItem("Blocksets")) {
95 gui::InputHexByte("Selected Blockset", &Ws().selected_blockset,
96 static_cast<uint8_t>(0x24));
98 false, "blockset", "0x" + std::to_string(Ws().selected_blockset),
99 "Blockset " + std::to_string(Ws().selected_blockset));
101 EndTabItem();
102 }
103
104 if (BeginTabItem("Roomsets")) {
105 gui::InputHexByte("Selected Roomset", &Ws().selected_roomset,
106 static_cast<uint8_t>(81));
108 false, "roomset", "0x" + std::to_string(Ws().selected_roomset),
109 "Roomset " + std::to_string(Ws().selected_roomset));
111 EndTabItem();
112 }
113
114 if (BeginTabItem("Spritesets")) {
115 gui::InputHexByte("Selected Spriteset", &Ws().selected_spriteset,
116 static_cast<uint8_t>(143));
118 false, "spriteset", "0x" + std::to_string(Ws().selected_spriteset),
119 "Spriteset " + std::to_string(Ws().selected_spriteset));
121 EndTabItem();
122 }
123
125 }
126
127 return absl::OkStatus();
128}
129
131 if (!game_data()) {
132 Text("No game data loaded");
133 return;
134 }
135
136 PushID("BlocksetViewer");
137 auto& ws = Ws();
138
139 if (BeginTable("##BlocksetTable", sheet_only ? 1 : 2,
140 ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable,
141 ImVec2(0, 0))) {
142 if (!sheet_only) {
143 TableSetupColumn("Inputs", ImGuiTableColumnFlags_WidthStretch,
144 GetContentRegionAvail().x);
145 }
146
147 TableSetupColumn("Sheets", ImGuiTableColumnFlags_WidthFixed,
148 kSheetDisplayWidth + 16);
149 TableHeadersRow();
150 TableNextRow();
151
152 if (!sheet_only) {
153 TableNextColumn();
154 BeginGroup();
155 for (int idx = 0; idx < 8; idx++) {
156 SetNextItemWidth(gui::LayoutHelpers::GetSliderWidth());
158 ("Slot " + std::to_string(idx)).c_str(),
159 &game_data()->main_blockset_ids[ws.selected_blockset][idx]);
160 }
161 EndGroup();
162 }
163
164 TableNextColumn();
165 BeginGroup();
166 for (int idx = 0; idx < 8; idx++) {
167 int sheet_id = game_data()->main_blockset_ids[ws.selected_blockset][idx];
168 auto& sheet = gfx::Arena::Get().mutable_gfx_sheets()->at(sheet_id);
169
170 // Apply current palette if selected
171 if (ws.use_custom_palette && current_palette_) {
172 sheet.SetPalette(*current_palette_);
174 }
175
176 // Unique ID combining blockset, slot, and sheet
177 int unique_id = (ws.selected_blockset << 16) | (idx << 8) | sheet_id;
178 DrawScaledSheet(blockset_canvases_[idx], sheet, unique_id, ws.view_scale);
179 }
180 EndGroup();
181 EndTable();
182 }
183
184 PopID();
185}
186
188 if (!game_data()) {
189 Text("No game data loaded");
190 return;
191 }
192
193 PushID("RoomsetViewer");
194 auto& ws = Ws();
195 Text("Roomsets overwrite slots 4-7 of the main blockset");
196
197 if (BeginTable("##RoomsTable", 3,
198 ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable,
199 ImVec2(0, 0))) {
200 TableSetupColumn("List", ImGuiTableColumnFlags_WidthFixed, 120);
201 TableSetupColumn("Inputs", ImGuiTableColumnFlags_WidthStretch,
202 GetContentRegionAvail().x);
203 TableSetupColumn("Sheets", ImGuiTableColumnFlags_WidthFixed,
204 kSheetDisplayWidth + 16);
205 TableHeadersRow();
206 TableNextRow();
207
208 // Roomset list column
209 TableNextColumn();
210 if (BeginChild("##RoomsetListChild", ImVec2(0, 300))) {
211 for (int idx = 0; idx < 0x51; idx++) {
212 PushID(idx);
213 std::string roomset_label = absl::StrFormat("0x%02X", idx);
214 bool is_selected = (ws.selected_roomset == static_cast<uint8_t>(idx));
215 if (Selectable(roomset_label.c_str(), is_selected)) {
216 ws.selected_roomset = static_cast<uint8_t>(idx);
217 }
218 PopID();
219 }
220 }
221 EndChild();
222
223 // Inputs column
224 TableNextColumn();
225 BeginGroup();
226 Text("Sheet IDs (overwrites slots 4-7):");
227 for (int idx = 0; idx < 4; idx++) {
228 SetNextItemWidth(gui::LayoutHelpers::GetSliderWidth());
230 ("Slot " + std::to_string(idx + 4)).c_str(),
231 &game_data()->room_blockset_ids[ws.selected_roomset][idx]);
232 }
233 EndGroup();
234
235 // Sheets column
236 TableNextColumn();
237 BeginGroup();
238 for (int idx = 0; idx < 4; idx++) {
239 int sheet_id = game_data()->room_blockset_ids[ws.selected_roomset][idx];
240 auto& sheet = gfx::Arena::Get().mutable_gfx_sheets()->at(sheet_id);
241
242 // Apply current palette if selected
243 if (ws.use_custom_palette && current_palette_) {
244 sheet.SetPalette(*current_palette_);
246 }
247
248 // Unique ID combining roomset, slot, and sheet
249 int unique_id =
250 (0x1000) | (ws.selected_roomset << 8) | (idx << 4) | sheet_id;
251 DrawScaledSheet(roomset_canvases_[idx], sheet, unique_id, ws.view_scale);
252 }
253 EndGroup();
254 EndTable();
255 }
256
257 PopID();
258}
259
261 if (!game_data()) {
262 Text("No game data loaded");
263 return;
264 }
265
266 PushID("SpritesetViewer");
267 auto& ws = Ws();
268
269 if (BeginTable("##SpritesTable", sheet_only ? 1 : 2,
270 ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable,
271 ImVec2(0, 0))) {
272 if (!sheet_only) {
273 TableSetupColumn("Inputs", ImGuiTableColumnFlags_WidthStretch,
274 GetContentRegionAvail().x);
275 }
276 TableSetupColumn("Sheets", ImGuiTableColumnFlags_WidthFixed,
277 kSheetDisplayWidth + 16);
278 TableHeadersRow();
279 TableNextRow();
280
281 if (!sheet_only) {
282 TableNextColumn();
283 BeginGroup();
284 Text("Sprite sheet IDs (base 115+):");
285 for (int idx = 0; idx < 4; idx++) {
286 SetNextItemWidth(gui::LayoutHelpers::GetSliderWidth());
288 ("Slot " + std::to_string(idx)).c_str(),
289 &game_data()->spriteset_ids[ws.selected_spriteset][idx]);
290 }
291 EndGroup();
292 }
293
294 TableNextColumn();
295 BeginGroup();
296 for (int idx = 0; idx < 4; idx++) {
297 int sheet_offset = game_data()->spriteset_ids[ws.selected_spriteset][idx];
298 int sheet_id = 115 + sheet_offset;
299 auto& sheet = gfx::Arena::Get().mutable_gfx_sheets()->at(sheet_id);
300
301 // Apply current palette if selected
302 if (ws.use_custom_palette && current_palette_) {
303 sheet.SetPalette(*current_palette_);
305 }
306
307 // Unique ID combining spriteset, slot, and sheet
308 int unique_id =
309 (0x2000) | (ws.selected_spriteset << 8) | (idx << 4) | sheet_offset;
310 DrawScaledSheet(spriteset_canvases_[idx], sheet, unique_id,
311 ws.view_scale);
312 }
313 EndGroup();
314 EndTable();
315 }
316
317 PopID();
318}
319
320namespace {
322 if (palette.empty()) {
323 return;
324 }
325 for (size_t color_idx = 0; color_idx < palette.size(); color_idx++) {
326 PushID(static_cast<int>(color_idx));
327 if ((color_idx % 8) != 0) {
328 SameLine(0.0f, GetStyle().ItemSpacing.y);
329 }
330
331 // Small icon of the color in the palette
332 gui::SnesColorButton(absl::StrCat("Palette", color_idx), palette[color_idx],
333 ImGuiColorEditFlags_NoAlpha |
334 ImGuiColorEditFlags_NoPicker |
335 ImGuiColorEditFlags_NoTooltip);
336
337 PopID();
338 }
339}
340} // namespace
341
343 if (!game_data()) {
344 return;
345 }
346
347 auto& ws = Ws();
348
349 // View scale control
350 Text(ICON_MD_ZOOM_IN " View");
351 SameLine();
352 SetNextItemWidth(gui::LayoutHelpers::GetSliderWidth());
353 SliderFloat("##ViewScale", &ws.view_scale, 1.0f, 4.0f, "%.1fx");
354 SameLine();
355
356 // Palette category selector
357 Text(ICON_MD_PALETTE " Palette");
358 SameLine();
359 SetNextItemWidth(gui::LayoutHelpers::GetComboWidth());
360
361 // Use the category names array for display
362 static constexpr int kNumPaletteCategories = 14;
363 if (BeginCombo(
364 "##PaletteCategory",
365 gfx::kPaletteCategoryNames[ws.selected_palette_category].data())) {
366 for (int cat = 0; cat < kNumPaletteCategories; cat++) {
367 auto category = static_cast<PaletteCategory>(cat);
368 bool is_selected = (ws.selected_palette_category == category);
369 if (Selectable(gfx::kPaletteCategoryNames[category].data(),
370 is_selected)) {
371 ws.selected_palette_category = category;
372 ws.selected_palette_index = 0;
374 }
375 if (is_selected) {
376 ImGui::SetItemDefaultFocus();
377 }
378 }
379 EndCombo();
380 }
381
382 SameLine();
383 SetNextItemWidth(gui::LayoutHelpers::GetHexInputWidth());
384 if (gui::InputHexByte("##PaletteIndex", &ws.selected_palette_index)) {
386 }
387
388 SameLine();
389 ImGui::Checkbox("Apply", &ws.use_custom_palette);
390 if (ImGui::IsItemHovered()) {
391 ImGui::SetTooltip("Apply selected palette to sheet previews");
392 }
393
394 // Show current palette preview
396 SameLine();
397 DrawPaletteFromPaletteGroup(*current_palette_);
398 }
399}
400
402 if (!game_data()) {
403 current_palette_ = nullptr;
404 return;
405 }
406
407 const auto& ws = Ws();
408 auto& groups = game_data()->palette_groups;
409 switch (ws.selected_palette_category) {
410 case PaletteCategory::kSword:
412 groups.swords.mutable_palette(ws.selected_palette_index);
413 break;
414 case PaletteCategory::kShield:
416 groups.shields.mutable_palette(ws.selected_palette_index);
417 break;
418 case PaletteCategory::kClothes:
420 groups.armors.mutable_palette(ws.selected_palette_index);
421 break;
422 case PaletteCategory::kWorldColors:
424 groups.overworld_main.mutable_palette(ws.selected_palette_index);
425 break;
426 case PaletteCategory::kAreaColors:
428 groups.overworld_aux.mutable_palette(ws.selected_palette_index);
429 break;
430 case PaletteCategory::kGlobalSprites:
432 groups.global_sprites.mutable_palette(ws.selected_palette_index);
433 break;
434 case PaletteCategory::kSpritesAux1:
436 groups.sprites_aux1.mutable_palette(ws.selected_palette_index);
437 break;
438 case PaletteCategory::kSpritesAux2:
440 groups.sprites_aux2.mutable_palette(ws.selected_palette_index);
441 break;
442 case PaletteCategory::kSpritesAux3:
444 groups.sprites_aux3.mutable_palette(ws.selected_palette_index);
445 break;
446 case PaletteCategory::kDungeons:
448 groups.dungeon_main.mutable_palette(ws.selected_palette_index);
449 break;
450 case PaletteCategory::kWorldMap:
451 case PaletteCategory::kDungeonMap:
453 groups.overworld_mini_map.mutable_palette(ws.selected_palette_index);
454 break;
455 case PaletteCategory::kTriforce:
456 case PaletteCategory::kCrystal:
458 groups.object_3d.mutable_palette(ws.selected_palette_index);
459 break;
460 default:
461 current_palette_ = nullptr;
462 break;
463 }
464}
465
466} // namespace editor
467} // namespace yaze
project::ResourceLabelManager * resource_label()
Definition rom.h:150
std::array< gui::Canvas, 8 > blockset_canvases_
zelda3::GameData * game_data() const
std::array< gui::Canvas, 4 > roomset_canvases_
GfxGroupWorkspaceState & Ws()
gfx::SnesPalette * current_palette_
void DrawSpritesetViewer(bool sheet_only=false)
std::array< gui::Canvas, 4 > spriteset_canvases_
void DrawBlocksetViewer(bool sheet_only=false)
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 bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:67
Represents a palette of colors for the Super Nintendo Entertainment System (SNES).
Modern, robust canvas for drawing and manipulating graphics.
Definition canvas.h:150
void DrawBitmap(Bitmap &bitmap, int border_offset, float scale)
Definition canvas.cc:1157
void DrawContextMenu()
Definition canvas.cc:684
void DrawBackground(ImVec2 canvas_size=ImVec2(0, 0))
Definition canvas.cc:590
void DrawGrid(float grid_step=64.0f, int tile_id_offset=8)
Definition canvas.cc:1480
static float GetHexInputWidth()
static float GetSliderWidth()
#define ICON_MD_ZOOM_IN
Definition icons.h:2194
#define ICON_MD_PALETTE
Definition icons.h:1370
void DrawScaledSheet(gui::Canvas &canvas, gfx::Bitmap &sheet, int unique_id, float scale=kDefaultScale)
PaletteCategory
Categories for organizing palette groups in the UI.
constexpr int kTilesheetHeight
Definition snes_tile.h:17
constexpr int kTilesheetWidth
Definition snes_tile.h:16
IMGUI_API bool SnesColorButton(absl::string_view id, gfx::SnesColor &color, ImGuiColorEditFlags flags, const ImVec2 &size_arg)
Definition color.cc:40
bool BeginThemedTabBar(const char *id, ImGuiTabBarFlags flags)
A stylized tab bar with "Mission Control" branding.
void EndThemedTabBar()
bool InputHexByte(const char *label, uint8_t *data, float input_width, bool no_step)
Definition input.cc:380
void SelectableLabelWithNameEdit(bool selected, const std::string &type, const std::string &key, const std::string &defaultValue)
Definition project.cc:2168
std::array< std::array< uint8_t, 4 >, kNumSpritesets > spriteset_ids
Definition game_data.h:95
std::array< std::array< uint8_t, 4 >, kNumRoomBlocksets > room_blockset_ids
Definition game_data.h:94
gfx::PaletteGroupMap palette_groups
Definition game_data.h:91
std::array< std::array< uint8_t, 8 >, kNumMainBlocksets > main_blockset_ids
Definition game_data.h:93