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"
10#include "imgui/imgui.h"
11#include "rom/rom.h"
12#include "util/file_util.h"
13#include "util/log.h"
14
15namespace yaze {
16namespace editor {
17
19 : state_(state), rom_(rom) {}
20
25
26void LinkSpritePanel::Draw(bool* p_open) {
27 // EditorPanel interface - delegate to existing Update() logic
28 // Lazy-load Link sheets on first update
29 if (!sheets_loaded_ && rom_ && rom_->is_loaded()) {
30 auto status = LoadLinkSheets();
31 if (!status.ok()) {
32 ImGui::TextColored(gui::GetErrorColor(),
33 "Failed to load Link sheets: %s",
34 status.message().data());
35 return;
36 }
37 }
38
40 ImGui::Separator();
41
42 // Split layout: left side grid, right side preview
43 float panel_width = ImGui::GetContentRegionAvail().x;
44 float grid_width = std::min(300.0f, panel_width * 0.4f);
45
46 // Left column: Sheet grid
47 ImGui::BeginChild("##LinkSheetGrid", ImVec2(grid_width, 0), true);
49 ImGui::EndChild();
50
51 ImGui::SameLine();
52
53 // Right column: Preview and controls
54 ImGui::BeginChild("##LinkPreviewArea", ImVec2(0, 0), true);
56 ImGui::Separator();
58 ImGui::Separator();
60 ImGui::EndChild();
61}
62
64 // Lazy-load Link sheets on first update
65 if (!sheets_loaded_ && rom_ && rom_->is_loaded()) {
66 auto status = LoadLinkSheets();
67 if (!status.ok()) {
68 ImGui::TextColored(gui::GetErrorColor(),
69 "Failed to load Link sheets: %s",
70 status.message().data());
71 return status;
72 }
73 }
74
76 ImGui::Separator();
77
78 // Split layout: left side grid, right side preview
79 float panel_width = ImGui::GetContentRegionAvail().x;
80 float grid_width = std::min(300.0f, panel_width * 0.4f);
81
82 // Left column: Sheet grid
83 ImGui::BeginChild("##LinkSheetGrid", ImVec2(grid_width, 0), true);
85 ImGui::EndChild();
86
87 ImGui::SameLine();
88
89 // Right column: Preview and controls
90 ImGui::BeginChild("##LinkPreviewArea", ImVec2(0, 0), true);
92 ImGui::Separator();
94 ImGui::Separator();
96 ImGui::EndChild();
97
98 return absl::OkStatus();
99}
100
102 if (ImGui::Button(ICON_MD_FILE_UPLOAD " Import ZSPR")) {
103 ImportZspr();
104 }
105 HOVER_HINT("Import a .zspr Link sprite file");
106
107 ImGui::SameLine();
108 if (ImGui::Button(ICON_MD_RESTORE " Reset to Vanilla")) {
110 }
111 HOVER_HINT("Reset Link graphics to vanilla ROM data");
112
113 // Show loaded ZSPR info
114 if (loaded_zspr_.has_value()) {
115 ImGui::SameLine();
116 ImGui::TextColored(gui::GetSuccessColor(),
117 ICON_MD_CHECK_CIRCLE " Loaded: %s",
118 loaded_zspr_->metadata.display_name.c_str());
119 }
120
121 // Unsaved changes indicator
123 ImGui::SameLine();
124 ImGui::TextColored(gui::GetWarningColor(),
125 ICON_MD_EDIT " [Unsaved]");
126 }
127}
128
130 ImGui::Text("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("Link Sheet %d", sheet_index);
210 ImGui::Text("Double-click to edit");
211 ImGui::EndTooltip();
212 }
213}
214
216 ImGui::Text("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
231 auto rt = gui::BeginCanvas(preview_canvas_, frame_opts);
232
233 auto& sheet = link_sheets_[selected_sheet_];
234 if (sheet.is_active() && sheet.texture()) {
235 gui::BitmapDrawOpts draw_opts;
236 draw_opts.dest_pos = ImVec2(0, 0);
237 draw_opts.dest_size = ImVec2(canvas_width, canvas_height);
238 draw_opts.ensure_texture = false;
239 gui::DrawBitmap(rt, sheet, draw_opts);
240 }
241
242 gui::EndCanvas(preview_canvas_, rt, frame_opts);
243 }
244
245 ImGui::Spacing();
246
247 // Open in editor button
248 if (ImGui::Button(ICON_MD_EDIT " Open in Pixel Editor")) {
250 }
251 HOVER_HINT("Open this sheet in the main pixel editor");
252
253 // Zoom slider
254 ImGui::SameLine();
255 ImGui::SetNextItemWidth(gui::LayoutHelpers::GetSliderWidth());
256 ImGui::SliderFloat("Zoom", &preview_zoom_, 1.0f, 8.0f, "%.1fx");
257}
258
260 ImGui::Text("Display Palette:");
261 ImGui::SameLine();
262
263 const char* palette_names[] = {"Green Mail", "Blue Mail", "Red Mail",
264 "Bunny"};
265 int current = static_cast<int>(selected_palette_);
266
267 ImGui::SetNextItemWidth(gui::LayoutHelpers::GetComboWidth());
268 if (ImGui::Combo("##PaletteSelect", &current, palette_names, 4)) {
269 selected_palette_ = static_cast<PaletteType>(current);
271 }
272 HOVER_HINT("Change the display palette for preview");
273}
274
276 ImGui::Text("Info:");
277 ImGui::BulletText("896 total tiles (8x8 each)");
278 ImGui::BulletText("14 graphics sheets");
279 ImGui::BulletText("4BPP format");
280
281 if (loaded_zspr_.has_value()) {
282 ImGui::Separator();
283 ImGui::Text("Loaded ZSPR:");
284 ImGui::BulletText("Name: %s", loaded_zspr_->metadata.display_name.c_str());
285 ImGui::BulletText("Author: %s", loaded_zspr_->metadata.author.c_str());
286 ImGui::BulletText("Tiles: %zu", loaded_zspr_->tile_count());
287 }
288}
289
291 // Open file dialog for .zspr files
293 if (file_path.empty()) {
294 return;
295 }
296
297 LOG_INFO("LinkSpritePanel", "Importing ZSPR: %s", file_path.c_str());
298
299 // Load ZSPR file
300 auto zspr_result = gfx::ZsprLoader::LoadFromFile(file_path);
301 if (!zspr_result.ok()) {
302 LOG_ERROR("LinkSpritePanel", "Failed to load ZSPR: %s",
303 zspr_result.status().message().data());
304 return;
305 }
306
307 loaded_zspr_ = std::move(zspr_result.value());
308
309 // Verify it's a Link sprite
310 if (!loaded_zspr_->is_link_sprite()) {
311 LOG_ERROR("LinkSpritePanel", "ZSPR is not a Link sprite (type=%d)",
312 loaded_zspr_->metadata.sprite_type);
313 loaded_zspr_.reset();
314 return;
315 }
316
317 // Apply to ROM
318 if (rom_ && rom_->is_loaded()) {
320 if (!status.ok()) {
321 LOG_ERROR("LinkSpritePanel", "Failed to apply ZSPR to ROM: %s",
322 status.message().data());
323 return;
324 }
325
326 // Also apply palette
328 if (!status.ok()) {
329 LOG_WARN("LinkSpritePanel", "Failed to apply ZSPR palette: %s",
330 status.message().data());
331 }
332
333 // Reload Link sheets to reflect changes
334 sheets_loaded_ = false;
336
337 LOG_INFO("LinkSpritePanel", "ZSPR '%s' imported successfully",
338 loaded_zspr_->metadata.display_name.c_str());
339 }
340}
341
343 // TODO: Implement reset to vanilla
344 // This would require keeping a backup of the original Link graphics
345 // or reloading from a vanilla ROM file
346 LOG_WARN("LinkSpritePanel", "Reset to vanilla not yet implemented");
347 loaded_zspr_.reset();
348}
349
351 // Signal to open the selected Link sheet in the main pixel editor
352 // Link sheets are separate from the main 223 sheets, so we need
353 // a special handling mechanism
354
355 // For now, log the intent - full integration requires additional state
356 LOG_INFO("LinkSpritePanel", "Request to open Link sheet %d in pixel editor",
358
359 // TODO: Add Link sheet to open_sheets with a special identifier
360 // or add a link_sheets_to_edit set to GraphicsEditorState
361}
362
364 if (!rom_ || !rom_->is_loaded()) {
365 return absl::FailedPreconditionError("ROM not loaded");
366 }
367
368 // Use the existing LoadLinkGraphics function
369 auto result = zelda3::LoadLinkGraphics(*rom_);
370 if (!result.ok()) {
371 return result.status();
372 }
373
374 link_sheets_ = std::move(result.value());
375 sheets_loaded_ = true;
376
377 LOG_INFO("LinkSpritePanel", "Loaded %d Link graphics sheets",
379
380 // Apply default palette for display
382
383 return absl::OkStatus();
384}
385
387 if (!rom_ || !rom_->is_loaded())
388 return;
389
390 // Get the appropriate palette based on selection
391 // Link palettes are in Group 4 (Sprites Aux1) and Group 5 (Sprites Aux2)
392 // Green Mail: Group 4, Index 0 (Standard Link)
393 // Blue Mail: Group 4, Index 0 (Standard Link) - but with different colors in game
394 // Red Mail: Group 4, Index 0 (Standard Link) - but with different colors in game
395 // Bunny: Group 4, Index 1 (Bunny Link)
396
397 // For now, we'll use the standard sprite palettes from GameData if available
398 // In a full implementation, we would load the specific mail palettes
399
400 // Default to Green Mail (Standard Link palette)
401 const gfx::SnesPalette* palette = nullptr;
402
403 // We need access to GameData to get the palettes
404 // Since we don't have direct access to GameData here (only Rom), we'll try to find it
405 // or use a hardcoded fallback if necessary.
406 // Ideally, LinkSpritePanel should have access to GameData.
407 // For this fix, we will assume the standard sprite palette location in ROM if GameData isn't available,
408 // or use a simplified approach.
409
410 // Actually, we can get GameData from the main Editor instance if we had access,
411 // but we only have Rom. Let's try to read the palette directly from ROM for now
412 // to ensure it works without refactoring the whole dependency injection.
413
414 // Standard Link Palette (Green Mail) is usually at 0x1BD318 (PC) / 0x37D318 (SNES) in vanilla
415 // But we should use the loaded palette data if possible.
416
417 // Let's use a safe fallback: Create a default Link palette
418 static gfx::SnesPalette default_palette;
419 if (default_palette.empty()) {
420 // Basic Green Mail colors (approximate)
421 default_palette.Resize(16);
422 default_palette[0] = gfx::SnesColor(0, 0, 0); // Transparent
423 default_palette[1] = gfx::SnesColor(24, 24, 24); // Tunic Dark
424 default_palette[2] = gfx::SnesColor(0, 19, 0); // Tunic Green
425 default_palette[3] = gfx::SnesColor(255, 255, 255); // White
426 default_palette[4] = gfx::SnesColor(255, 165, 66); // Skin
427 default_palette[5] = gfx::SnesColor(255, 100, 50); // Skin Dark
428 default_palette[6] = gfx::SnesColor(255, 0, 0); // Red
429 default_palette[7] = gfx::SnesColor(255, 255, 0); // Yellow
430 // ... fill others as needed
431 }
432
433 // If we can't get the real palette, use default
434 palette = &default_palette;
435
436 // Apply to all Link sheets
437 for (auto& sheet : link_sheets_) {
438 if (sheet.is_active() && sheet.surface()) {
439 // Use the palette
440 sheet.SetPaletteWithTransparent(*palette, 0);
441
442 // Force texture update
445 }
446 }
447
448 LOG_INFO("LinkSpritePanel", "Applied palette %s to %zu sheets",
450}
451
453 switch (type) {
455 return "Green Mail";
457 return "Blue Mail";
459 return "Red Mail";
461 return "Bunny";
462 default:
463 return "Unknown";
464 }
465}
466
467} // namespace editor
468} // 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:132
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: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:466
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)
Definition canvas.cc:1591
ImVec4 GetSuccessColor()
Definition ui_helpers.cc:48
void BeginCanvas(Canvas &canvas, ImVec2 child_size)
Definition canvas.cc:1568
ImVec4 GetErrorColor()
Definition ui_helpers.cc:58
ImVec4 GetWarningColor()
Definition ui_helpers.cc:53
void DrawBitmap(const CanvasRuntime &rt, gfx::Bitmap &bitmap, int border_offset, float scale)
Definition canvas.cc:2169
absl::StatusOr< std::array< gfx::Bitmap, kNumLinkSheets > > LoadLinkGraphics(const Rom &rom)
Loads Link's graphics sheets from ROM.
Definition game_data.cc:535
constexpr uint32_t kNumLinkSheets
Definition game_data.h:26
std::optional< float > grid_step
Definition canvas.h:70