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"
10#include "app/gui/core/icons.h"
11#include "app/gui/core/input.h"
12#include "imgui/imgui.h"
13#include "rom/rom.h"
14
15namespace yaze {
16namespace editor {
17
18using ImGui::BeginChild;
19using ImGui::BeginCombo;
20using ImGui::BeginGroup;
21using ImGui::BeginTabBar;
22using ImGui::BeginTabItem;
23using ImGui::BeginTable;
24using ImGui::EndChild;
25using ImGui::EndCombo;
26using ImGui::EndGroup;
27using ImGui::EndTabBar;
28using ImGui::EndTabItem;
29using ImGui::EndTable;
30using ImGui::GetContentRegionAvail;
31using ImGui::GetStyle;
32using ImGui::IsItemClicked;
33using ImGui::PopID;
34using ImGui::PushID;
35using ImGui::SameLine;
36using ImGui::Selectable;
37using ImGui::Separator;
38using ImGui::SetNextItemWidth;
39using ImGui::SliderFloat;
40using ImGui::TableHeadersRow;
41using ImGui::TableNextColumn;
42using ImGui::TableNextRow;
43using ImGui::TableSetupColumn;
44using ImGui::Text;
45
46using gfx::kPaletteGroupNames;
48
49namespace {
50
51// Constants for sheet display
52constexpr int kSheetDisplayWidth = 256; // 2x scale from 128px sheets
53constexpr int kSheetDisplayHeight = 64; // 2x scale from 32px sheets
54constexpr float kDefaultScale = 2.0f;
55constexpr int kTileSize = 16; // 8px tiles at 2x scale
56
57// Draw a single sheet with proper scaling and unique ID
58void DrawScaledSheet(gui::Canvas& canvas, gfx::Bitmap& sheet, int unique_id,
59 float scale = kDefaultScale) {
60 PushID(unique_id);
61
62 // Calculate scaled dimensions
63 int display_width =
64 static_cast<int>(gfx::kTilesheetWidth * scale);
65 int display_height =
66 static_cast<int>(gfx::kTilesheetHeight * scale);
67
68 // Draw canvas background
69 canvas.DrawBackground(ImVec2(display_width + 1, display_height + 1));
70 canvas.DrawContextMenu();
71
72 // Draw bitmap with proper scale
73 canvas.DrawBitmap(sheet, 2, scale);
74
75 // Draw grid at scaled tile size
76 canvas.DrawGrid(static_cast<int>(8 * scale));
77 canvas.DrawOverlay();
78
79 PopID();
80}
81
82} // namespace
83
84absl::Status GfxGroupEditor::Update() {
85 // Palette controls at top for all tabs
87 Separator();
88
89 if (BeginTabBar("##GfxGroupEditorTabs")) {
90 if (BeginTabItem("Blocksets")) {
91 gui::InputHexByte("Selected Blockset", &selected_blockset_,
92 static_cast<uint8_t>(0x24));
94 false, "blockset", "0x" + std::to_string(selected_blockset_),
95 "Blockset " + std::to_string(selected_blockset_));
97 EndTabItem();
98 }
99
100 if (BeginTabItem("Roomsets")) {
101 gui::InputHexByte("Selected Roomset", &selected_roomset_,
102 static_cast<uint8_t>(81));
104 false, "roomset", "0x" + std::to_string(selected_roomset_),
105 "Roomset " + std::to_string(selected_roomset_));
107 EndTabItem();
108 }
109
110 if (BeginTabItem("Spritesets")) {
111 gui::InputHexByte("Selected Spriteset", &selected_spriteset_,
112 static_cast<uint8_t>(143));
114 false, "spriteset", "0x" + std::to_string(selected_spriteset_),
115 "Spriteset " + std::to_string(selected_spriteset_));
117 EndTabItem();
118 }
119
120 EndTabBar();
121 }
122
123 return absl::OkStatus();
124}
125
127 if (!game_data()) {
128 Text("No game data loaded");
129 return;
130 }
131
132 PushID("BlocksetViewer");
133
134 if (BeginTable("##BlocksetTable", sheet_only ? 1 : 2,
135 ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable,
136 ImVec2(0, 0))) {
137 if (!sheet_only) {
138 TableSetupColumn("Inputs", ImGuiTableColumnFlags_WidthStretch,
139 GetContentRegionAvail().x);
140 }
141
142 TableSetupColumn("Sheets", ImGuiTableColumnFlags_WidthFixed,
143 kSheetDisplayWidth + 16);
144 TableHeadersRow();
145 TableNextRow();
146
147 if (!sheet_only) {
148 TableNextColumn();
149 BeginGroup();
150 for (int idx = 0; idx < 8; idx++) {
151 SetNextItemWidth(100.f);
153 ("Slot " + std::to_string(idx)).c_str(),
154 &game_data()->main_blockset_ids[selected_blockset_][idx]);
155 }
156 EndGroup();
157 }
158
159 TableNextColumn();
160 BeginGroup();
161 for (int idx = 0; idx < 8; idx++) {
162 int sheet_id = game_data()->main_blockset_ids[selected_blockset_][idx];
163 auto& sheet = gfx::Arena::Get().mutable_gfx_sheets()->at(sheet_id);
164
165 // Apply current palette if selected
167 sheet.SetPalette(*current_palette_);
169 }
170
171 // Unique ID combining blockset, slot, and sheet
172 int unique_id = (selected_blockset_ << 16) | (idx << 8) | sheet_id;
173 DrawScaledSheet(blockset_canvases_[idx], sheet, unique_id, view_scale_);
174 }
175 EndGroup();
176 EndTable();
177 }
178
179 PopID();
180}
181
183 if (!game_data()) {
184 Text("No game data loaded");
185 return;
186 }
187
188 PushID("RoomsetViewer");
189 Text("Roomsets overwrite slots 4-7 of the main blockset");
190
191 if (BeginTable("##RoomsTable", 3,
192 ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable,
193 ImVec2(0, 0))) {
194 TableSetupColumn("List", ImGuiTableColumnFlags_WidthFixed, 120);
195 TableSetupColumn("Inputs", ImGuiTableColumnFlags_WidthStretch,
196 GetContentRegionAvail().x);
197 TableSetupColumn("Sheets", ImGuiTableColumnFlags_WidthFixed,
198 kSheetDisplayWidth + 16);
199 TableHeadersRow();
200 TableNextRow();
201
202 // Roomset list column
203 TableNextColumn();
204 if (BeginChild("##RoomsetListChild", ImVec2(0, 300))) {
205 for (int idx = 0; idx < 0x51; idx++) {
206 PushID(idx);
207 std::string roomset_label = absl::StrFormat("0x%02X", idx);
208 bool is_selected = (selected_roomset_ == idx);
209 if (Selectable(roomset_label.c_str(), is_selected)) {
210 selected_roomset_ = idx;
211 }
212 PopID();
213 }
214 }
215 EndChild();
216
217 // Inputs column
218 TableNextColumn();
219 BeginGroup();
220 Text("Sheet IDs (overwrites slots 4-7):");
221 for (int idx = 0; idx < 4; idx++) {
222 SetNextItemWidth(100.f);
224 ("Slot " + std::to_string(idx + 4)).c_str(),
225 &game_data()->room_blockset_ids[selected_roomset_][idx]);
226 }
227 EndGroup();
228
229 // Sheets column
230 TableNextColumn();
231 BeginGroup();
232 for (int idx = 0; idx < 4; idx++) {
233 int sheet_id = game_data()->room_blockset_ids[selected_roomset_][idx];
234 auto& sheet = gfx::Arena::Get().mutable_gfx_sheets()->at(sheet_id);
235
236 // Apply current palette if selected
238 sheet.SetPalette(*current_palette_);
240 }
241
242 // Unique ID combining roomset, slot, and sheet
243 int unique_id = (0x1000) | (selected_roomset_ << 8) | (idx << 4) | sheet_id;
244 DrawScaledSheet(roomset_canvases_[idx], sheet, unique_id, view_scale_);
245 }
246 EndGroup();
247 EndTable();
248 }
249
250 PopID();
251}
252
254 if (!game_data()) {
255 Text("No game data loaded");
256 return;
257 }
258
259 PushID("SpritesetViewer");
260
261 if (BeginTable("##SpritesTable", sheet_only ? 1 : 2,
262 ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable,
263 ImVec2(0, 0))) {
264 if (!sheet_only) {
265 TableSetupColumn("Inputs", ImGuiTableColumnFlags_WidthStretch,
266 GetContentRegionAvail().x);
267 }
268 TableSetupColumn("Sheets", ImGuiTableColumnFlags_WidthFixed,
269 kSheetDisplayWidth + 16);
270 TableHeadersRow();
271 TableNextRow();
272
273 if (!sheet_only) {
274 TableNextColumn();
275 BeginGroup();
276 Text("Sprite sheet IDs (base 115+):");
277 for (int idx = 0; idx < 4; idx++) {
278 SetNextItemWidth(100.f);
280 ("Slot " + std::to_string(idx)).c_str(),
281 &game_data()->spriteset_ids[selected_spriteset_][idx]);
282 }
283 EndGroup();
284 }
285
286 TableNextColumn();
287 BeginGroup();
288 for (int idx = 0; idx < 4; idx++) {
289 int sheet_offset = game_data()->spriteset_ids[selected_spriteset_][idx];
290 int sheet_id = 115 + sheet_offset;
291 auto& sheet = gfx::Arena::Get().mutable_gfx_sheets()->at(sheet_id);
292
293 // Apply current palette if selected
295 sheet.SetPalette(*current_palette_);
297 }
298
299 // Unique ID combining spriteset, slot, and sheet
300 int unique_id =
301 (0x2000) | (selected_spriteset_ << 8) | (idx << 4) | sheet_offset;
302 DrawScaledSheet(spriteset_canvases_[idx], sheet, unique_id, view_scale_);
303 }
304 EndGroup();
305 EndTable();
306 }
307
308 PopID();
309}
310
311namespace {
313 if (palette.empty()) {
314 return;
315 }
316 for (size_t color_idx = 0; color_idx < palette.size(); color_idx++) {
317 PushID(static_cast<int>(color_idx));
318 if ((color_idx % 8) != 0) {
319 SameLine(0.0f, GetStyle().ItemSpacing.y);
320 }
321
322 // Small icon of the color in the palette
323 gui::SnesColorButton(absl::StrCat("Palette", color_idx), palette[color_idx],
324 ImGuiColorEditFlags_NoAlpha |
325 ImGuiColorEditFlags_NoPicker |
326 ImGuiColorEditFlags_NoTooltip);
327
328 PopID();
329 }
330}
331} // namespace
332
334 if (!game_data()) {
335 return;
336 }
337
338 // View scale control
339 Text(ICON_MD_ZOOM_IN " View");
340 SameLine();
341 SetNextItemWidth(100.f);
342 SliderFloat("##ViewScale", &view_scale_, 1.0f, 4.0f, "%.1fx");
343 SameLine();
344
345 // Palette category selector
346 Text(ICON_MD_PALETTE " Palette");
347 SameLine();
348 SetNextItemWidth(150.f);
349
350 // Use the category names array for display
351 static constexpr int kNumPaletteCategories = 14;
352 if (BeginCombo("##PaletteCategory",
353 gfx::kPaletteCategoryNames[selected_palette_category_].data())) {
354 for (int cat = 0; cat < kNumPaletteCategories; cat++) {
355 auto category = static_cast<PaletteCategory>(cat);
356 bool is_selected = (selected_palette_category_ == category);
357 if (Selectable(gfx::kPaletteCategoryNames[category].data(), is_selected)) {
361 }
362 if (is_selected) {
363 ImGui::SetItemDefaultFocus();
364 }
365 }
366 EndCombo();
367 }
368
369 SameLine();
370 SetNextItemWidth(80.f);
371 if (gui::InputHexByte("##PaletteIndex", &selected_palette_index_)) {
373 }
374
375 SameLine();
376 ImGui::Checkbox("Apply", &use_custom_palette_);
377 if (ImGui::IsItemHovered()) {
378 ImGui::SetTooltip("Apply selected palette to sheet previews");
379 }
380
381 // Show current palette preview
383 SameLine();
384 DrawPaletteFromPaletteGroup(*current_palette_);
385 }
386}
387
389 if (!game_data()) {
390 current_palette_ = nullptr;
391 return;
392 }
393
394 auto& groups = game_data()->palette_groups;
396 case PaletteCategory::kSword:
397 current_palette_ = groups.swords.mutable_palette(selected_palette_index_);
398 break;
399 case PaletteCategory::kShield:
400 current_palette_ = groups.shields.mutable_palette(selected_palette_index_);
401 break;
402 case PaletteCategory::kClothes:
403 current_palette_ = groups.armors.mutable_palette(selected_palette_index_);
404 break;
405 case PaletteCategory::kWorldColors:
407 groups.overworld_main.mutable_palette(selected_palette_index_);
408 break;
409 case PaletteCategory::kAreaColors:
411 groups.overworld_aux.mutable_palette(selected_palette_index_);
412 break;
413 case PaletteCategory::kGlobalSprites:
415 groups.global_sprites.mutable_palette(selected_palette_index_);
416 break;
417 case PaletteCategory::kSpritesAux1:
419 groups.sprites_aux1.mutable_palette(selected_palette_index_);
420 break;
421 case PaletteCategory::kSpritesAux2:
423 groups.sprites_aux2.mutable_palette(selected_palette_index_);
424 break;
425 case PaletteCategory::kSpritesAux3:
427 groups.sprites_aux3.mutable_palette(selected_palette_index_);
428 break;
429 case PaletteCategory::kDungeons:
431 groups.dungeon_main.mutable_palette(selected_palette_index_);
432 break;
433 case PaletteCategory::kWorldMap:
434 case PaletteCategory::kDungeonMap:
436 groups.overworld_mini_map.mutable_palette(selected_palette_index_);
437 break;
438 case PaletteCategory::kTriforce:
439 case PaletteCategory::kCrystal:
441 groups.object_3d.mutable_palette(selected_palette_index_);
442 break;
443 default:
444 current_palette_ = nullptr;
445 break;
446 }
447}
448
449} // namespace editor
450} // namespace yaze
project::ResourceLabelManager * resource_label()
Definition rom.h:146
std::array< gui::Canvas, 8 > blockset_canvases_
zelda3::GameData * game_data() const
gfx::PaletteCategory selected_palette_category_
std::array< gui::Canvas, 4 > roomset_canvases_
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:127
void NotifySheetModified(int sheet_index)
Notify Arena that a graphics sheet has been modified.
Definition arena.cc:313
static Arena & Get()
Definition arena.cc:19
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:1075
void DrawContextMenu()
Definition canvas.cc:602
void DrawBackground(ImVec2 canvas_size=ImVec2(0, 0))
Definition canvas.cc:547
void DrawGrid(float grid_step=64.0f, int tile_id_offset=8)
Definition canvas.cc:1398
#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:37
bool InputHexByte(const char *label, uint8_t *data, float input_width, bool no_step)
Definition input.cc:370
void SelectableLabelWithNameEdit(bool selected, const std::string &type, const std::string &key, const std::string &defaultValue)
Definition project.cc:1294
std::array< std::array< uint8_t, 4 >, kNumSpritesets > spriteset_ids
Definition game_data.h:93
std::array< std::array< uint8_t, 4 >, kNumRoomBlocksets > room_blockset_ids
Definition game_data.h:92
gfx::PaletteGroupMap palette_groups
Definition game_data.h:89
std::array< std::array< uint8_t, 8 >, kNumMainBlocksets > main_blockset_ids
Definition game_data.h:91