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 "util/file_util.h"
8#include "rom/rom.h"
9#include "imgui/imgui.h"
10#include "util/log.h"
11
12namespace yaze {
13namespace editor {
14
16 : state_(state), rom_(rom) {}
17
21
22void LinkSpritePanel::Draw(bool* p_open) {
23 // EditorPanel interface - delegate to existing Update() logic
24 // Lazy-load Link sheets on first update
25 if (!sheets_loaded_ && rom_ && rom_->is_loaded()) {
26 auto status = LoadLinkSheets();
27 if (!status.ok()) {
28 ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f),
29 "Failed to load Link sheets: %s",
30 status.message().data());
31 return;
32 }
33 }
34
36 ImGui::Separator();
37
38 // Split layout: left side grid, right side preview
39 float panel_width = ImGui::GetContentRegionAvail().x;
40 float grid_width = std::min(300.0f, panel_width * 0.4f);
41
42 // Left column: Sheet grid
43 ImGui::BeginChild("##LinkSheetGrid", ImVec2(grid_width, 0), true);
45 ImGui::EndChild();
46
47 ImGui::SameLine();
48
49 // Right column: Preview and controls
50 ImGui::BeginChild("##LinkPreviewArea", ImVec2(0, 0), true);
52 ImGui::Separator();
54 ImGui::Separator();
56 ImGui::EndChild();
57}
58
60 // Lazy-load Link sheets on first update
61 if (!sheets_loaded_ && rom_ && rom_->is_loaded()) {
62 auto status = LoadLinkSheets();
63 if (!status.ok()) {
64 ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f),
65 "Failed to load Link sheets: %s",
66 status.message().data());
67 return status;
68 }
69 }
70
72 ImGui::Separator();
73
74 // Split layout: left side grid, right side preview
75 float panel_width = ImGui::GetContentRegionAvail().x;
76 float grid_width = std::min(300.0f, panel_width * 0.4f);
77
78 // Left column: Sheet grid
79 ImGui::BeginChild("##LinkSheetGrid", ImVec2(grid_width, 0), true);
81 ImGui::EndChild();
82
83 ImGui::SameLine();
84
85 // Right column: Preview and controls
86 ImGui::BeginChild("##LinkPreviewArea", ImVec2(0, 0), true);
88 ImGui::Separator();
90 ImGui::Separator();
92 ImGui::EndChild();
93
94 return absl::OkStatus();
95}
96
98 if (ImGui::Button(ICON_MD_FILE_UPLOAD " Import ZSPR")) {
99 ImportZspr();
100 }
101 HOVER_HINT("Import a .zspr Link sprite file");
102
103 ImGui::SameLine();
104 if (ImGui::Button(ICON_MD_RESTORE " Reset to Vanilla")) {
106 }
107 HOVER_HINT("Reset Link graphics to vanilla ROM data");
108
109 // Show loaded ZSPR info
110 if (loaded_zspr_.has_value()) {
111 ImGui::SameLine();
112 ImGui::TextColored(ImVec4(0.5f, 1.0f, 0.5f, 1.0f),
113 "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(),
175 cursor_pos,
176 ImVec2(cursor_pos.x + kThumbnailSize,
177 cursor_pos.y + kThumbnailSize / 4)); // 128x32 aspect
178 }
179 }
180
181 // Click handling
182 if (ImGui::IsWindowHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
183 selected_sheet_ = sheet_index;
184 }
185
186 // Double-click to open in pixel editor
187 if (ImGui::IsWindowHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
189 }
190
191 // Sheet label
192 ImGui::SetCursorPosY(ImGui::GetCursorPosY() + kThumbnailSize / 4 + 2);
193 ImGui::Text("%d", sheet_index);
194
195 ImGui::EndChild();
196
197 if (is_selected) {
198 ImGui::PopStyleColor();
199 }
200
201 // Tooltip
202 if (ImGui::IsItemHovered()) {
203 ImGui::BeginTooltip();
204 ImGui::Text("Link Sheet %d", sheet_index);
205 ImGui::Text("Double-click to edit");
206 ImGui::EndTooltip();
207 }
208}
209
211 ImGui::Text("Sheet %d Preview", selected_sheet_);
212
213 // Preview canvas
214 float canvas_width = ImGui::GetContentRegionAvail().x - 16;
215 float canvas_height = canvas_width / 4; // 4:1 aspect ratio (128x32)
216
217 preview_canvas_.SetCanvasSize(ImVec2(canvas_width, canvas_height));
218 const float grid_step = 8.0f * (canvas_width / 128.0f);
219 {
220 gui::CanvasFrameOptions frame_opts;
221 frame_opts.canvas_size = ImVec2(canvas_width, canvas_height);
222 frame_opts.draw_context_menu = false;
223 frame_opts.draw_grid = true;
224 frame_opts.grid_step = grid_step;
225
226 auto rt = gui::BeginCanvas(preview_canvas_, frame_opts);
227
228 auto& sheet = link_sheets_[selected_sheet_];
229 if (sheet.is_active() && sheet.texture()) {
230 gui::BitmapDrawOpts draw_opts;
231 draw_opts.dest_pos = ImVec2(0, 0);
232 draw_opts.dest_size = ImVec2(canvas_width, canvas_height);
233 draw_opts.ensure_texture = false;
234 gui::DrawBitmap(rt, sheet, draw_opts);
235 }
236
237 gui::EndCanvas(preview_canvas_, rt, frame_opts);
238 }
239
240 ImGui::Spacing();
241
242 // Open in editor button
243 if (ImGui::Button(ICON_MD_EDIT " Open in Pixel Editor")) {
245 }
246 HOVER_HINT("Open this sheet in the main pixel editor");
247
248 // Zoom slider
249 ImGui::SameLine();
250 ImGui::SetNextItemWidth(100);
251 ImGui::SliderFloat("Zoom", &preview_zoom_, 1.0f, 8.0f, "%.1fx");
252}
253
255 ImGui::Text("Display Palette:");
256 ImGui::SameLine();
257
258 const char* palette_names[] = {"Green Mail", "Blue Mail", "Red Mail", "Bunny"};
259 int current = static_cast<int>(selected_palette_);
260
261 ImGui::SetNextItemWidth(120);
262 if (ImGui::Combo("##PaletteSelect", &current, palette_names, 4)) {
263 selected_palette_ = static_cast<PaletteType>(current);
265 }
266 HOVER_HINT("Change the display palette for preview");
267}
268
270 ImGui::Text("Info:");
271 ImGui::BulletText("896 total tiles (8x8 each)");
272 ImGui::BulletText("14 graphics sheets");
273 ImGui::BulletText("4BPP format");
274
275 if (loaded_zspr_.has_value()) {
276 ImGui::Separator();
277 ImGui::Text("Loaded ZSPR:");
278 ImGui::BulletText("Name: %s", loaded_zspr_->metadata.display_name.c_str());
279 ImGui::BulletText("Author: %s", loaded_zspr_->metadata.author.c_str());
280 ImGui::BulletText("Tiles: %zu", loaded_zspr_->tile_count());
281 }
282}
283
285 // Open file dialog for .zspr files
287 if (file_path.empty()) {
288 return;
289 }
290
291 LOG_INFO("LinkSpritePanel", "Importing ZSPR: %s", file_path.c_str());
292
293 // Load ZSPR file
294 auto zspr_result = gfx::ZsprLoader::LoadFromFile(file_path);
295 if (!zspr_result.ok()) {
296 LOG_ERROR("LinkSpritePanel", "Failed to load ZSPR: %s",
297 zspr_result.status().message().data());
298 return;
299 }
300
301 loaded_zspr_ = std::move(zspr_result.value());
302
303 // Verify it's a Link sprite
304 if (!loaded_zspr_->is_link_sprite()) {
305 LOG_ERROR("LinkSpritePanel", "ZSPR is not a Link sprite (type=%d)",
306 loaded_zspr_->metadata.sprite_type);
307 loaded_zspr_.reset();
308 return;
309 }
310
311 // Apply to ROM
312 if (rom_ && rom_->is_loaded()) {
314 if (!status.ok()) {
315 LOG_ERROR("LinkSpritePanel", "Failed to apply ZSPR to ROM: %s",
316 status.message().data());
317 return;
318 }
319
320 // Also apply palette
322 if (!status.ok()) {
323 LOG_WARN("LinkSpritePanel", "Failed to apply ZSPR palette: %s",
324 status.message().data());
325 }
326
327 // Reload Link sheets to reflect changes
328 sheets_loaded_ = false;
330
331 LOG_INFO("LinkSpritePanel", "ZSPR '%s' imported successfully",
332 loaded_zspr_->metadata.display_name.c_str());
333 }
334}
335
337 // TODO: Implement reset to vanilla
338 // This would require keeping a backup of the original Link graphics
339 // or reloading from a vanilla ROM file
340 LOG_WARN("LinkSpritePanel", "Reset to vanilla not yet implemented");
341 loaded_zspr_.reset();
342}
343
345 // Signal to open the selected Link sheet in the main pixel editor
346 // Link sheets are separate from the main 223 sheets, so we need
347 // a special handling mechanism
348
349 // For now, log the intent - full integration requires additional state
350 LOG_INFO("LinkSpritePanel", "Request to open Link sheet %d in pixel editor",
352
353 // TODO: Add Link sheet to open_sheets with a special identifier
354 // or add a link_sheets_to_edit set to GraphicsEditorState
355}
356
358 if (!rom_ || !rom_->is_loaded()) {
359 return absl::FailedPreconditionError("ROM not loaded");
360 }
361
362 // Use the existing LoadLinkGraphics function
363 auto result = zelda3::LoadLinkGraphics(*rom_);
364 if (!result.ok()) {
365 return result.status();
366 }
367
368 link_sheets_ = std::move(result.value());
369 sheets_loaded_ = true;
370
371 LOG_INFO("LinkSpritePanel", "Loaded %d Link graphics sheets", zelda3::kNumLinkSheets);
372
373 // Apply default palette for display
375
376 return absl::OkStatus();
377}
378
380 if (!rom_ || !rom_->is_loaded()) return;
381
382 // Get the appropriate palette based on selection
383 // Link palettes are in Group 4 (Sprites Aux1) and Group 5 (Sprites Aux2)
384 // Green Mail: Group 4, Index 0 (Standard Link)
385 // Blue Mail: Group 4, Index 0 (Standard Link) - but with different colors in game
386 // Red Mail: Group 4, Index 0 (Standard Link) - but with different colors in game
387 // Bunny: Group 4, Index 1 (Bunny Link)
388
389 // For now, we'll use the standard sprite palettes from GameData if available
390 // In a full implementation, we would load the specific mail palettes
391
392 // Default to Green Mail (Standard Link palette)
393 const gfx::SnesPalette* palette = nullptr;
394
395 // We need access to GameData to get the palettes
396 // Since we don't have direct access to GameData here (only Rom), we'll try to find it
397 // or use a hardcoded fallback if necessary.
398 // Ideally, LinkSpritePanel should have access to GameData.
399 // For this fix, we will assume the standard sprite palette location in ROM if GameData isn't available,
400 // or use a simplified approach.
401
402 // Actually, we can get GameData from the main Editor instance if we had access,
403 // but we only have Rom. Let's try to read the palette directly from ROM for now
404 // to ensure it works without refactoring the whole dependency injection.
405
406 // Standard Link Palette (Green Mail) is usually at 0x1BD318 (PC) / 0x37D318 (SNES) in vanilla
407 // But we should use the loaded palette data if possible.
408
409 // Let's use a safe fallback: Create a default Link palette
410 static gfx::SnesPalette default_palette;
411 if (default_palette.empty()) {
412 // Basic Green Mail colors (approximate)
413 default_palette.Resize(16);
414 default_palette[0] = gfx::SnesColor(0, 0, 0); // Transparent
415 default_palette[1] = gfx::SnesColor(24, 24, 24); // Tunic Dark
416 default_palette[2] = gfx::SnesColor(0, 19, 0); // Tunic Green
417 default_palette[3] = gfx::SnesColor(255, 255, 255); // White
418 default_palette[4] = gfx::SnesColor(255, 165, 66); // Skin
419 default_palette[5] = gfx::SnesColor(255, 100, 50); // Skin Dark
420 default_palette[6] = gfx::SnesColor(255, 0, 0); // Red
421 default_palette[7] = gfx::SnesColor(255, 255, 0); // Yellow
422 // ... fill others as needed
423 }
424
425 // If we can't get the real palette, use default
426 palette = &default_palette;
427
428 // Apply to all Link sheets
429 for (auto& sheet : link_sheets_) {
430 if (sheet.is_active() && sheet.surface()) {
431 // Use the palette
432 sheet.SetPaletteWithTransparent(*palette, 0);
433
434 // Force texture update
437 }
438 }
439
440 LOG_INFO("LinkSpritePanel", "Applied palette %s to %zu sheets",
442}
443
445 switch (type) {
446 case PaletteType::kGreenMail: return "Green Mail";
447 case PaletteType::kBlueMail: return "Blue Mail";
448 case PaletteType::kRedMail: return "Red Mail";
449 case PaletteType::kBunny: return "Bunny";
450 default: return "Unknown";
451 }
452}
453
454} // namespace editor
455} // 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:34
static Arena & Get()
Definition arena.cc:19
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:432
constexpr uint32_t kNumLinkSheets
Definition game_data.h:26
std::optional< float > grid_step
Definition canvas.h:70