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)); // 128x32 aspect
183 }
184 }
185
186 // Click handling
187 if (ImGui::IsWindowHovered() &&
188 ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
189 selected_sheet_ = sheet_index;
190 }
191
192 // Double-click to open in pixel editor
193 if (ImGui::IsWindowHovered() &&
194 ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
196 }
197
198 // Sheet label
199 ImGui::SetCursorPosY(ImGui::GetCursorPosY() + kThumbnailSize / 4 + 2);
200 ImGui::Text("%d", sheet_index);
201
202 ImGui::EndChild();
203
204 sel_bg_guard.reset();
205
206 // Tooltip
207 if (ImGui::IsItemHovered()) {
208 ImGui::BeginTooltip();
209 ImGui::Text(tr("Link Sheet %d"), sheet_index);
210 ImGui::Text(tr("Double-click to edit"));
211 ImGui::EndTooltip();
212 }
213}
214
216 ImGui::Text(tr("Sheet %d Preview"), selected_sheet_);
217
218 // Preview canvas
219 float canvas_width = ImGui::GetContentRegionAvail().x - 16;
220 float canvas_height = canvas_width / 4; // 4:1 aspect ratio (128x32)
221
222 preview_canvas_.SetCanvasSize(ImVec2(canvas_width, canvas_height));
223 const float grid_step = 8.0f * (canvas_width / 128.0f);
224 {
225 gui::CanvasFrameOptions frame_opts;
226 frame_opts.canvas_size = ImVec2(canvas_width, canvas_height);
227 frame_opts.draw_context_menu = false;
228 frame_opts.draw_grid = true;
229 frame_opts.grid_step = grid_step;
230
232 auto rt = gui::BeginCanvas(preview_canvas_, frame_opts);
233
234 auto& sheet = link_sheets_[selected_sheet_];
235 if (sheet.is_active() && sheet.texture()) {
236 gui::BitmapDrawOpts draw_opts;
237 draw_opts.dest_pos = ImVec2(0, 0);
238 draw_opts.dest_size = ImVec2(canvas_width, canvas_height);
239 draw_opts.ensure_texture = false;
240 gui::DrawBitmap(rt, sheet, draw_opts);
241 }
242
243 gui::EndCanvas(preview_canvas_, rt, frame_opts);
244 }
245
246 ImGui::Spacing();
247
248 // Open in editor button
249 if (ImGui::Button(ICON_MD_EDIT " Open in Pixel Editor")) {
251 }
252 HOVER_HINT("Open this sheet in the main pixel editor");
253
254 // Zoom slider
255 ImGui::SameLine();
256 ImGui::SetNextItemWidth(gui::LayoutHelpers::GetSliderWidth());
257 ImGui::SliderFloat(tr("Zoom"), &preview_zoom_, 1.0f, 8.0f, "%.1fx");
258}
259
261 ImGui::Text(tr("Display Palette:"));
262 ImGui::SameLine();
263
264 const char* palette_names[] = {"Green Mail", "Blue Mail", "Red Mail",
265 "Bunny"};
266 int current = static_cast<int>(selected_palette_);
267
268 ImGui::SetNextItemWidth(gui::LayoutHelpers::GetComboWidth());
269 if (ImGui::Combo("##PaletteSelect", &current, palette_names, 4)) {
270 selected_palette_ = static_cast<PaletteType>(current);
272 }
273 HOVER_HINT("Change the display palette for preview");
274}
275
277 ImGui::Text(tr("Info:"));
278 ImGui::BulletText(tr("896 total tiles (8x8 each)"));
279 ImGui::BulletText(tr("14 graphics sheets"));
280 ImGui::BulletText(tr("4BPP format"));
281
282 if (loaded_zspr_.has_value()) {
283 ImGui::Separator();
284 ImGui::Text(tr("Loaded ZSPR:"));
285 ImGui::BulletText(tr("Name: %s"),
286 loaded_zspr_->metadata.display_name.c_str());
287 ImGui::BulletText(tr("Author: %s"), loaded_zspr_->metadata.author.c_str());
288 ImGui::BulletText(tr("Tiles: %zu"), loaded_zspr_->tile_count());
289 }
290}
291
293 // Open file dialog for .zspr files
295 if (file_path.empty()) {
296 return;
297 }
298
299 LOG_INFO("LinkSpritePanel", "Importing ZSPR: %s", file_path.c_str());
300
301 // Load ZSPR file
302 auto zspr_result = gfx::ZsprLoader::LoadFromFile(file_path);
303 if (!zspr_result.ok()) {
304 LOG_ERROR("LinkSpritePanel", "Failed to load ZSPR: %s",
305 zspr_result.status().message().data());
306 return;
307 }
308
309 loaded_zspr_ = std::move(zspr_result.value());
310
311 // Verify it's a Link sprite
312 if (!loaded_zspr_->is_link_sprite()) {
313 LOG_ERROR("LinkSpritePanel", "ZSPR is not a Link sprite (type=%d)",
314 loaded_zspr_->metadata.sprite_type);
315 loaded_zspr_.reset();
316 return;
317 }
318
319 // Apply to ROM
320 if (rom_ && rom_->is_loaded()) {
322 if (!status.ok()) {
323 LOG_ERROR("LinkSpritePanel", "Failed to apply ZSPR to ROM: %s",
324 status.message().data());
325 return;
326 }
327
328 // Also apply palette
330 if (!status.ok()) {
331 LOG_WARN("LinkSpritePanel", "Failed to apply ZSPR palette: %s",
332 status.message().data());
333 }
334
335 // Reload Link sheets to reflect changes
336 sheets_loaded_ = false;
338
339 LOG_INFO("LinkSpritePanel", "ZSPR '%s' imported successfully",
340 loaded_zspr_->metadata.display_name.c_str());
341 }
342}
343
345 // TODO: Implement reset to vanilla
346 // This would require keeping a backup of the original Link graphics
347 // or reloading from a vanilla ROM file
348 LOG_WARN("LinkSpritePanel", "Reset to vanilla not yet implemented");
349 loaded_zspr_.reset();
350}
351
353 // Signal to open the selected Link sheet in the main pixel editor
354 // Link sheets are separate from the main 223 sheets, so we need
355 // a special handling mechanism
356
357 // For now, log the intent - full integration requires additional state
358 LOG_INFO("LinkSpritePanel", "Request to open Link sheet %d in pixel editor",
360
361 // TODO: Add Link sheet to open_sheets with a special identifier
362 // or add a link_sheets_to_edit set to GraphicsEditorState
363}
364
366 if (!rom_ || !rom_->is_loaded()) {
367 return absl::FailedPreconditionError("ROM not loaded");
368 }
369
370 // Use the existing LoadLinkGraphics function
371 auto result = zelda3::LoadLinkGraphics(*rom_);
372 if (!result.ok()) {
373 return result.status();
374 }
375
376 link_sheets_ = std::move(result.value());
377 sheets_loaded_ = true;
378
379 LOG_INFO("LinkSpritePanel", "Loaded %d Link graphics sheets",
381
382 // Apply default palette for display
384
385 return absl::OkStatus();
386}
387
389 if (!rom_ || !rom_->is_loaded())
390 return;
391
392 // Get the appropriate palette based on selection
393 // Link palettes are in Group 4 (Sprites Aux1) and Group 5 (Sprites Aux2)
394 // Green Mail: Group 4, Index 0 (Standard Link)
395 // Blue Mail: Group 4, Index 0 (Standard Link) - but with different colors in game
396 // Red Mail: Group 4, Index 0 (Standard Link) - but with different colors in game
397 // Bunny: Group 4, Index 1 (Bunny Link)
398
399 // For now, we'll use the standard sprite palettes from GameData if available
400 // In a full implementation, we would load the specific mail palettes
401
402 // Default to Green Mail (Standard Link palette)
403 const gfx::SnesPalette* palette = nullptr;
404
405 // We need access to GameData to get the palettes
406 // Since we don't have direct access to GameData here (only Rom), we'll try to find it
407 // or use a hardcoded fallback if necessary.
408 // Ideally, LinkSpritePanel should have access to GameData.
409 // For this fix, we will assume the standard sprite palette location in ROM if GameData isn't available,
410 // or use a simplified approach.
411
412 // Actually, we can get GameData from the main Editor instance if we had access,
413 // but we only have Rom. Let's try to read the palette directly from ROM for now
414 // to ensure it works without refactoring the whole dependency injection.
415
416 // Standard Link Palette (Green Mail) is usually at 0x1BD318 (PC) / 0x37D318 (SNES) in vanilla
417 // But we should use the loaded palette data if possible.
418
419 // Let's use a safe fallback: Create a default Link palette
420 static gfx::SnesPalette default_palette;
421 if (default_palette.empty()) {
422 // Basic Green Mail colors (approximate)
423 default_palette.Resize(16);
424 default_palette[0] = gfx::SnesColor(0, 0, 0); // Transparent
425 default_palette[1] = gfx::SnesColor(24, 24, 24); // Tunic Dark
426 default_palette[2] = gfx::SnesColor(0, 19, 0); // Tunic Green
427 default_palette[3] = gfx::SnesColor(255, 255, 255); // White
428 default_palette[4] = gfx::SnesColor(255, 165, 66); // Skin
429 default_palette[5] = gfx::SnesColor(255, 100, 50); // Skin Dark
430 default_palette[6] = gfx::SnesColor(255, 0, 0); // Red
431 default_palette[7] = gfx::SnesColor(255, 255, 0); // Yellow
432 // ... fill others as needed
433 }
434
435 // If we can't get the real palette, use default
436 palette = &default_palette;
437
438 // Apply to all Link sheets
439 for (auto& sheet : link_sheets_) {
440 if (sheet.is_active() && sheet.surface()) {
441 // Use the palette
442 sheet.SetPaletteWithTransparent(*palette, 0);
443
444 // Force texture update
447 }
448 }
449
450 LOG_INFO("LinkSpritePanel", "Applied palette %s to %zu sheets",
452}
453
455 switch (type) {
457 return "Green Mail";
459 return "Blue Mail";
461 return "Red Mail";
463 return "Bunny";
464 default:
465 return "Unknown";
466 }
467}
468
469} // namespace editor
470} // 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:28
bool is_loaded() const
Definition rom.h:144
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:36
static Arena & Get()
Definition arena.cc:21
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:372
CanvasConfig & GetConfig()
Definition canvas.h:229
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: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)
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:539
constexpr uint32_t kNumLinkSheets
Definition game_data.h:27
std::optional< float > grid_step