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