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