yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
link_sprite_panel.cc
Go to the documentation of this file.
2
3#include "absl/strings/str_format.h"
7#include "imgui/imgui.h"
8#include "rom/rom.h"
9#include "util/file_util.h"
10#include "util/log.h"
11
12namespace yaze {
13namespace editor {
14
16 : state_(state), rom_(rom) {}
17
22
23void LinkSpritePanel::Draw(bool* p_open) {
24 // EditorPanel interface - delegate to existing Update() logic
25 // Lazy-load Link sheets on first update
26 if (!sheets_loaded_ && rom_ && rom_->is_loaded()) {
27 auto status = LoadLinkSheets();
28 if (!status.ok()) {
29 ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f),
30 "Failed to load Link sheets: %s",
31 status.message().data());
32 return;
33 }
34 }
35
37 ImGui::Separator();
38
39 // Split layout: left side grid, right side preview
40 float panel_width = ImGui::GetContentRegionAvail().x;
41 float grid_width = std::min(300.0f, panel_width * 0.4f);
42
43 // Left column: Sheet grid
44 ImGui::BeginChild("##LinkSheetGrid", ImVec2(grid_width, 0), true);
46 ImGui::EndChild();
47
48 ImGui::SameLine();
49
50 // Right column: Preview and controls
51 ImGui::BeginChild("##LinkPreviewArea", ImVec2(0, 0), true);
53 ImGui::Separator();
55 ImGui::Separator();
57 ImGui::EndChild();
58}
59
61 // Lazy-load Link sheets on first update
62 if (!sheets_loaded_ && rom_ && rom_->is_loaded()) {
63 auto status = LoadLinkSheets();
64 if (!status.ok()) {
65 ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f),
66 "Failed to load Link sheets: %s",
67 status.message().data());
68 return status;
69 }
70 }
71
73 ImGui::Separator();
74
75 // Split layout: left side grid, right side preview
76 float panel_width = ImGui::GetContentRegionAvail().x;
77 float grid_width = std::min(300.0f, panel_width * 0.4f);
78
79 // Left column: Sheet grid
80 ImGui::BeginChild("##LinkSheetGrid", ImVec2(grid_width, 0), true);
82 ImGui::EndChild();
83
84 ImGui::SameLine();
85
86 // Right column: Preview and controls
87 ImGui::BeginChild("##LinkPreviewArea", ImVec2(0, 0), true);
89 ImGui::Separator();
91 ImGui::Separator();
93 ImGui::EndChild();
94
95 return absl::OkStatus();
96}
97
99 if (ImGui::Button(ICON_MD_FILE_UPLOAD " Import ZSPR")) {
100 ImportZspr();
101 }
102 HOVER_HINT("Import a .zspr Link sprite file");
103
104 ImGui::SameLine();
105 if (ImGui::Button(ICON_MD_RESTORE " Reset to Vanilla")) {
107 }
108 HOVER_HINT("Reset Link graphics to vanilla ROM data");
109
110 // Show loaded ZSPR info
111 if (loaded_zspr_.has_value()) {
112 ImGui::SameLine();
113 ImGui::TextColored(ImVec4(0.5f, 1.0f, 0.5f, 1.0f), "Loaded: %s",
114 loaded_zspr_->metadata.display_name.c_str());
115 }
116
117 // Unsaved changes indicator
119 ImGui::SameLine();
120 ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.2f, 1.0f), "[Unsaved]");
121 }
122}
123
125 ImGui::Text("Link Sheets (14)");
126 ImGui::Separator();
127
128 // 4x4 grid (14 sheets + 2 empty slots)
129 const float cell_size = kThumbnailSize + kThumbnailPadding * 2;
130 int col = 0;
131
132 for (int i = 0; i < kNumLinkSheets; i++) {
133 if (col > 0) {
134 ImGui::SameLine();
135 }
136
137 ImGui::PushID(i);
139 ImGui::PopID();
140
141 col++;
142 if (col >= 4) {
143 col = 0;
144 }
145 }
146}
147
149 bool is_selected = (selected_sheet_ == sheet_index);
150
151 // Selection highlight
152 if (is_selected) {
153 ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.3f, 0.5f, 0.8f, 0.4f));
154 }
155
156 ImGui::BeginChild(absl::StrFormat("##LinkSheet%d", sheet_index).c_str(),
159 true, ImGuiWindowFlags_NoScrollbar);
160
161 // Draw thumbnail
162 auto& sheet = link_sheets_[sheet_index];
163 if (sheet.is_active()) {
164 // Ensure texture exists
165 if (!sheet.texture() && sheet.surface()) {
168 const_cast<gfx::Bitmap*>(&sheet));
169 }
170
171 if (sheet.texture()) {
172 ImVec2 cursor_pos = ImGui::GetCursorScreenPos();
173 ImGui::GetWindowDrawList()->AddImage(
174 (ImTextureID)(intptr_t)sheet.texture(), cursor_pos,
175 ImVec2(cursor_pos.x + kThumbnailSize,
176 cursor_pos.y + kThumbnailSize / 4)); // 128x32 aspect
177 }
178 }
179
180 // Click handling
181 if (ImGui::IsWindowHovered() &&
182 ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
183 selected_sheet_ = sheet_index;
184 }
185
186 // Double-click to open in pixel editor
187 if (ImGui::IsWindowHovered() &&
188 ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
190 }
191
192 // Sheet label
193 ImGui::SetCursorPosY(ImGui::GetCursorPosY() + kThumbnailSize / 4 + 2);
194 ImGui::Text("%d", sheet_index);
195
196 ImGui::EndChild();
197
198 if (is_selected) {
199 ImGui::PopStyleColor();
200 }
201
202 // Tooltip
203 if (ImGui::IsItemHovered()) {
204 ImGui::BeginTooltip();
205 ImGui::Text("Link Sheet %d", sheet_index);
206 ImGui::Text("Double-click to edit");
207 ImGui::EndTooltip();
208 }
209}
210
212 ImGui::Text("Sheet %d Preview", selected_sheet_);
213
214 // Preview canvas
215 float canvas_width = ImGui::GetContentRegionAvail().x - 16;
216 float canvas_height = canvas_width / 4; // 4:1 aspect ratio (128x32)
217
218 preview_canvas_.SetCanvasSize(ImVec2(canvas_width, canvas_height));
219 const float grid_step = 8.0f * (canvas_width / 128.0f);
220 {
221 gui::CanvasFrameOptions frame_opts;
222 frame_opts.canvas_size = ImVec2(canvas_width, canvas_height);
223 frame_opts.draw_context_menu = false;
224 frame_opts.draw_grid = true;
225 frame_opts.grid_step = grid_step;
226
227 auto rt = gui::BeginCanvas(preview_canvas_, frame_opts);
228
229 auto& sheet = link_sheets_[selected_sheet_];
230 if (sheet.is_active() && sheet.texture()) {
231 gui::BitmapDrawOpts draw_opts;
232 draw_opts.dest_pos = ImVec2(0, 0);
233 draw_opts.dest_size = ImVec2(canvas_width, canvas_height);
234 draw_opts.ensure_texture = false;
235 gui::DrawBitmap(rt, sheet, draw_opts);
236 }
237
238 gui::EndCanvas(preview_canvas_, rt, frame_opts);
239 }
240
241 ImGui::Spacing();
242
243 // Open in editor button
244 if (ImGui::Button(ICON_MD_EDIT " Open in Pixel Editor")) {
246 }
247 HOVER_HINT("Open this sheet in the main pixel editor");
248
249 // Zoom slider
250 ImGui::SameLine();
251 ImGui::SetNextItemWidth(100);
252 ImGui::SliderFloat("Zoom", &preview_zoom_, 1.0f, 8.0f, "%.1fx");
253}
254
256 ImGui::Text("Display Palette:");
257 ImGui::SameLine();
258
259 const char* palette_names[] = {"Green Mail", "Blue Mail", "Red Mail",
260 "Bunny"};
261 int current = static_cast<int>(selected_palette_);
262
263 ImGui::SetNextItemWidth(120);
264 if (ImGui::Combo("##PaletteSelect", &current, palette_names, 4)) {
265 selected_palette_ = static_cast<PaletteType>(current);
267 }
268 HOVER_HINT("Change the display palette for preview");
269}
270
272 ImGui::Text("Info:");
273 ImGui::BulletText("896 total tiles (8x8 each)");
274 ImGui::BulletText("14 graphics sheets");
275 ImGui::BulletText("4BPP format");
276
277 if (loaded_zspr_.has_value()) {
278 ImGui::Separator();
279 ImGui::Text("Loaded ZSPR:");
280 ImGui::BulletText("Name: %s", loaded_zspr_->metadata.display_name.c_str());
281 ImGui::BulletText("Author: %s", loaded_zspr_->metadata.author.c_str());
282 ImGui::BulletText("Tiles: %zu", loaded_zspr_->tile_count());
283 }
284}
285
287 // Open file dialog for .zspr files
289 if (file_path.empty()) {
290 return;
291 }
292
293 LOG_INFO("LinkSpritePanel", "Importing ZSPR: %s", file_path.c_str());
294
295 // Load ZSPR file
296 auto zspr_result = gfx::ZsprLoader::LoadFromFile(file_path);
297 if (!zspr_result.ok()) {
298 LOG_ERROR("LinkSpritePanel", "Failed to load ZSPR: %s",
299 zspr_result.status().message().data());
300 return;
301 }
302
303 loaded_zspr_ = std::move(zspr_result.value());
304
305 // Verify it's a Link sprite
306 if (!loaded_zspr_->is_link_sprite()) {
307 LOG_ERROR("LinkSpritePanel", "ZSPR is not a Link sprite (type=%d)",
308 loaded_zspr_->metadata.sprite_type);
309 loaded_zspr_.reset();
310 return;
311 }
312
313 // Apply to ROM
314 if (rom_ && rom_->is_loaded()) {
316 if (!status.ok()) {
317 LOG_ERROR("LinkSpritePanel", "Failed to apply ZSPR to ROM: %s",
318 status.message().data());
319 return;
320 }
321
322 // Also apply palette
324 if (!status.ok()) {
325 LOG_WARN("LinkSpritePanel", "Failed to apply ZSPR palette: %s",
326 status.message().data());
327 }
328
329 // Reload Link sheets to reflect changes
330 sheets_loaded_ = false;
332
333 LOG_INFO("LinkSpritePanel", "ZSPR '%s' imported successfully",
334 loaded_zspr_->metadata.display_name.c_str());
335 }
336}
337
339 // TODO: Implement reset to vanilla
340 // This would require keeping a backup of the original Link graphics
341 // or reloading from a vanilla ROM file
342 LOG_WARN("LinkSpritePanel", "Reset to vanilla not yet implemented");
343 loaded_zspr_.reset();
344}
345
347 // Signal to open the selected Link sheet in the main pixel editor
348 // Link sheets are separate from the main 223 sheets, so we need
349 // a special handling mechanism
350
351 // For now, log the intent - full integration requires additional state
352 LOG_INFO("LinkSpritePanel", "Request to open Link sheet %d in pixel editor",
354
355 // TODO: Add Link sheet to open_sheets with a special identifier
356 // or add a link_sheets_to_edit set to GraphicsEditorState
357}
358
360 if (!rom_ || !rom_->is_loaded()) {
361 return absl::FailedPreconditionError("ROM not loaded");
362 }
363
364 // Use the existing LoadLinkGraphics function
365 auto result = zelda3::LoadLinkGraphics(*rom_);
366 if (!result.ok()) {
367 return result.status();
368 }
369
370 link_sheets_ = std::move(result.value());
371 sheets_loaded_ = true;
372
373 LOG_INFO("LinkSpritePanel", "Loaded %d Link graphics sheets",
375
376 // Apply default palette for display
378
379 return absl::OkStatus();
380}
381
383 if (!rom_ || !rom_->is_loaded())
384 return;
385
386 // Get the appropriate palette based on selection
387 // Link palettes are in Group 4 (Sprites Aux1) and Group 5 (Sprites Aux2)
388 // Green Mail: Group 4, Index 0 (Standard Link)
389 // Blue Mail: Group 4, Index 0 (Standard Link) - but with different colors in game
390 // Red Mail: Group 4, Index 0 (Standard Link) - but with different colors in game
391 // Bunny: Group 4, Index 1 (Bunny Link)
392
393 // For now, we'll use the standard sprite palettes from GameData if available
394 // In a full implementation, we would load the specific mail palettes
395
396 // Default to Green Mail (Standard Link palette)
397 const gfx::SnesPalette* palette = nullptr;
398
399 // We need access to GameData to get the palettes
400 // Since we don't have direct access to GameData here (only Rom), we'll try to find it
401 // or use a hardcoded fallback if necessary.
402 // Ideally, LinkSpritePanel should have access to GameData.
403 // For this fix, we will assume the standard sprite palette location in ROM if GameData isn't available,
404 // or use a simplified approach.
405
406 // Actually, we can get GameData from the main Editor instance if we had access,
407 // but we only have Rom. Let's try to read the palette directly from ROM for now
408 // to ensure it works without refactoring the whole dependency injection.
409
410 // Standard Link Palette (Green Mail) is usually at 0x1BD318 (PC) / 0x37D318 (SNES) in vanilla
411 // But we should use the loaded palette data if possible.
412
413 // Let's use a safe fallback: Create a default Link palette
414 static gfx::SnesPalette default_palette;
415 if (default_palette.empty()) {
416 // Basic Green Mail colors (approximate)
417 default_palette.Resize(16);
418 default_palette[0] = gfx::SnesColor(0, 0, 0); // Transparent
419 default_palette[1] = gfx::SnesColor(24, 24, 24); // Tunic Dark
420 default_palette[2] = gfx::SnesColor(0, 19, 0); // Tunic Green
421 default_palette[3] = gfx::SnesColor(255, 255, 255); // White
422 default_palette[4] = gfx::SnesColor(255, 165, 66); // Skin
423 default_palette[5] = gfx::SnesColor(255, 100, 50); // Skin Dark
424 default_palette[6] = gfx::SnesColor(255, 0, 0); // Red
425 default_palette[7] = gfx::SnesColor(255, 255, 0); // Yellow
426 // ... fill others as needed
427 }
428
429 // If we can't get the real palette, use default
430 palette = &default_palette;
431
432 // Apply to all Link sheets
433 for (auto& sheet : link_sheets_) {
434 if (sheet.is_active() && sheet.surface()) {
435 // Use the palette
436 sheet.SetPaletteWithTransparent(*palette, 0);
437
438 // Force texture update
441 }
442 }
443
444 LOG_INFO("LinkSpritePanel", "Applied palette %s to %zu sheets",
446}
447
449 switch (type) {
451 return "Green Mail";
453 return "Blue Mail";
455 return "Red Mail";
457 return "Bunny";
458 default:
459 return "Unknown";
460 }
461}
462
463} // namespace editor
464} // namespace yaze
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:24
bool is_loaded() const
Definition rom.h:128
Shared state between GraphicsEditor panel components.
static constexpr float kThumbnailPadding
PaletteType
Link sprite palette types.
absl::Status LoadLinkSheets()
Load Link graphics sheets from ROM.
void Draw(bool *p_open) override
Draw the panel UI (EditorPanel interface)
void Initialize()
Initialize the panel and load Link sheets.
void OpenSheetInPixelEditor()
Open selected sheet in the main pixel editor.
absl::Status Update()
Legacy Update method for backward compatibility.
std::optional< gfx::ZsprData > loaded_zspr_
void ApplySelectedPalette()
Apply the selected palette to Link sheets for display.
void ImportZspr()
Handle ZSPR file import.
void DrawSheetGrid()
Draw the 4x4 sheet selection grid.
static constexpr int kNumLinkSheets
void DrawPreviewCanvas()
Draw the preview canvas for selected sheet.
void DrawInfoPanel()
Draw info panel with stats.
static const char * GetPaletteName(PaletteType type)
Get the name of a palette type.
void DrawPaletteSelector()
Draw the palette selector dropdown.
std::array< gfx::Bitmap, kNumLinkSheets > link_sheets_
LinkSpritePanel(GraphicsEditorState *state, Rom *rom)
void DrawToolbar()
Draw the toolbar with Import/Reset buttons.
void ResetToVanilla()
Reset Link sheets to vanilla ROM data.
void DrawSheetThumbnail(int sheet_index)
Draw a single Link sheet thumbnail.
static constexpr float kThumbnailSize
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
Definition arena.cc:35
static Arena & Get()
Definition arena.cc:20
Represents a bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:67
SNES Color container.
Definition snes_color.h:110
Represents a palette of colors for the Super Nintendo Entertainment System (SNES).
void Resize(size_t size)
static absl::Status ApplyToRom(Rom &rom, const ZsprData &zspr)
Apply loaded ZSPR sprite data to ROM's Link graphics.
static absl::Status ApplyPaletteToRom(Rom &rom, const ZsprData &zspr)
Apply ZSPR palette data to ROM.
static absl::StatusOr< ZsprData > LoadFromFile(const std::string &path)
Load ZSPR data from a file path.
void SetCanvasSize(ImVec2 canvas_size)
Definition canvas.h:469
static std::string ShowOpenFileDialog()
ShowOpenFileDialog opens a file dialog and returns the selected filepath. Uses global feature flag to...
#define ICON_MD_EDIT
Definition icons.h:645
#define ICON_MD_FILE_UPLOAD
Definition icons.h:749
#define ICON_MD_RESTORE
Definition icons.h:1605
#define LOG_ERROR(category, format,...)
Definition log.h:109
#define LOG_WARN(category, format,...)
Definition log.h:107
#define LOG_INFO(category, format,...)
Definition log.h:105
#define HOVER_HINT(string)
Definition macro.h:24
void EndCanvas(Canvas &canvas)
Definition canvas.cc:1509
void BeginCanvas(Canvas &canvas, ImVec2 child_size)
Definition canvas.cc:1486
void DrawBitmap(const CanvasRuntime &rt, gfx::Bitmap &bitmap, int border_offset, float scale)
Definition canvas.cc:2087
absl::StatusOr< std::array< gfx::Bitmap, kNumLinkSheets > > LoadLinkGraphics(const Rom &rom)
Loads Link's graphics sheets from ROM.
Definition game_data.cc:498
constexpr uint32_t kNumLinkSheets
Definition game_data.h:26
std::optional< float > grid_step
Definition canvas.h:70