yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
room_graphics_content.cc
Go to the documentation of this file.
2#include "util/i18n/tr.h"
3
4#include <algorithm>
5#include <cstdint>
6#include <vector>
7
8#include "absl/strings/str_format.h"
16#include "imgui/imgui.h"
19
20namespace yaze {
21namespace editor {
22
23namespace {
24
26 constexpr const char* kLabels[] = {"Fit", "1x", "2x", "4x"};
27 int selected = static_cast<int>(*mode);
28 ImGui::AlignTextToFramePadding();
29 ImGui::TextDisabled("%s", tr("Scale"));
30 ImGui::SameLine();
31 ImGui::SetNextItemWidth(gui::ScaledSize(80.0f, 0.0f).x);
32 if (ImGui::Combo("##RoomGraphicsScale", &selected, kLabels,
33 IM_ARRAYSIZE(kLabels))) {
34 *mode = static_cast<gui::AdaptiveSheetScaleMode>(selected);
35 }
36}
37
38} // namespace
39
46
48 for (auto& [id, bitmap] : picker_previews_) {
50 }
51 picker_previews_.clear();
52}
53
55 runtime_detached_ = true;
56 current_room_id_ = nullptr;
57 rooms_ = nullptr;
58 renderer_ = nullptr;
60 usage_callback_ = {};
61}
62
64 if (!usage_callback_) {
65 ImGui::TextDisabled("Shared usage unavailable.");
66 return;
67 }
68 const auto usage = usage_callback_(roomset);
69 if (!usage.ok()) {
70 ImGui::TextWrapped("Shared usage unavailable: %s",
71 std::string(usage.status().message()).c_str());
72 return;
73 }
74 const auto summary = absl::StrFormat(
75 "Shared by %d room%s and %d map screen%s", usage->rooms.size(),
76 usage->rooms.size() == 1 ? "" : "s", usage->ow_areas.size(),
77 usage->ow_areas.size() == 1 ? "" : "s");
78 ImGui::TextWrapped("%s", summary.c_str());
79 if (usage->unscanned_rooms || usage->unscanned_ow_areas) {
80 ImGui::TextWrapped(
81 "Incomplete scan: %d rooms / %d map screens unavailable.",
82 usage->unscanned_rooms, usage->unscanned_ow_areas);
83 }
84 const bool expanded = ImGui::TreeNode("Shared assignment details");
85 gui::AutoRegisterLastItem("tree", "Shared assignment details");
86 if (!expanded)
87 return;
88 ImGui::TextWrapped("%s", usage->uses_live_overworld
89 ? "Includes unsaved room and map selections."
90 : "Includes unsaved room selections; maps read "
91 "from ROM until the overworld is loaded.");
92 ImGui::TextWrapped(
93 "Map screens include large-map children. Zero inherits "
94 "each room or map's main group.");
95 if (ImGui::BeginChild("##SharedRoomGraphicsUsage", ImVec2(0, 150), true)) {
96 const auto draw_ids = [](const char* label, const std::vector<int>& ids,
97 int digits) {
98 const auto title = absl::StrFormat("%s (%d)", label, ids.size());
99 ImGui::TextWrapped("%s", title.c_str());
100 if (ids.empty()) {
101 ImGui::TextDisabled("None");
102 return;
103 }
104 std::string text;
105 for (const int id : ids) {
106 if (!text.empty())
107 text += ", ";
108 text += absl::StrFormat("%0*X", digits, id);
109 }
110 ImGui::TextWrapped("%s", text.c_str());
111 };
112 draw_ids("Rooms using this assignment", usage->rooms, 3);
113 draw_ids("Map screens using this assignment", usage->ow_areas, 2);
114 draw_ids("Map references overridden by custom sheets",
115 usage->overridden_ow_areas, 2);
116 draw_ids("Map references using a separate ROM table",
117 usage->independent_ow_areas, 2);
118 }
119 ImGui::EndChild();
120 ImGui::TreePop();
121}
122
124 auto* data = rooms_->game_data();
125 const auto blocks = room.blocks();
126 if (!data || selected_slot_ < 0 || selected_slot_ >= 16)
127 return;
128 const bool room_slot = selected_slot_ >= 3 && selected_slot_ <= 6;
129 const bool editable =
130 room_slot && room.blockset() < data->room_blockset_ids.size();
131 ImGui::Separator();
132 ImGui::Text("Slot %d / Sheet %02X", selected_slot_, blocks[selected_slot_]);
133 if (editable) {
134 const auto assigned =
135 data->room_blockset_ids[room.blockset()][selected_slot_ - 3];
136 ImGui::TextDisabled(
137 "%s / Room group %02X",
138 assigned == 0 ? "Inherited from main group" : "Room override",
139 room.blockset());
141 ImGui::BeginDisabled(!assignment_callback_);
142 if (ImGui::Button("Choose replacement sheet")) {
144 pending_sheet_ = -1;
146 }
147 gui::AutoRegisterLastItem("button", "Choose replacement sheet");
148 if (assigned != 0) {
149 ImGui::SameLine();
150 if (ImGui::Button("Use main group")) {
151 const auto status = assignment_callback_(room.id(), selected_slot_, 0);
152 assignment_error_ = status.ok() ? "" : std::string(status.message());
153 show_picker_ = false;
154 }
155 gui::AutoRegisterLastItem("button", "Use main group");
156 }
157 ImGui::EndDisabled();
158 } else {
159 ImGui::TextDisabled("%s", selected_slot_ < 8
160 ? "Main group / animation source"
161 : selected_slot_ < 12 ? "Fixed sprite source"
162 : "Spriteset source");
163 ImGui::TextWrapped(
164 "Assignment editing is available for room override slots 3-6.");
165 }
166 if (!assignment_error_.empty())
167 ImGui::TextWrapped("%s", assignment_error_.c_str());
168 if (editable && show_picker_)
170}
171
173 const auto* data = rooms_->game_data();
174 if (!data || !assignment_callback_)
175 return;
176 // Use the loaded room's actual palette half. Thumbnails are local display
177 // copies; the session store and its palettes are never changed by browsing.
178 std::vector<gfx::SnesColor> colors;
179 const auto& rendered = room.rendered_palette();
180 if (rendered.size() < 16) {
181 ImGui::TextWrapped("Load the room palette to preview replacement sheets.");
182 return;
183 }
184 const int half = room.BackgroundPaletteOffset(selected_slot_);
185 for (int index = 0; index < 8; ++index) {
186 const auto color = rendered[index == 0 ? 0 : half + index];
187 colors.emplace_back(color.r, color.g, color.b);
188 }
189 ImGui::TextDisabled(
190 "Preview: room palette row 0. Select a sheet, then apply.");
191 if (pending_sheet_ > 0) {
192 if (ImGui::Button("Apply to shared group")) {
193 const auto status =
195 assignment_error_ = status.ok() ? "" : std::string(status.message());
196 if (status.ok())
197 show_picker_ = false;
198 }
199 gui::AutoRegisterLastItem("button", "Apply to shared group");
200 ImGui::SameLine();
201 ImGui::Text("Sheet %02X", pending_sheet_);
202 }
203 if (ImGui::BeginChild("##RoomSheetPicker", ImVec2(0, 220), true)) {
204 std::vector<int> candidates;
205 for (int id = 1; id < zelda3::GraphicsSheetStore::kSheetCount; ++id) {
206 if (data->sheet_store.HasSheet(id) &&
209 candidates.push_back(id);
210 }
211 ImGuiListClipper clipper;
212 clipper.Begin(static_cast<int>(candidates.size()), 54.0f);
213 while (clipper.Step()) {
214 for (int row = clipper.DisplayStart; row < clipper.DisplayEnd; ++row) {
215 const int id = candidates[row];
216 auto& preview = picker_previews_[id];
217 const auto pixels = data->sheet_store.Sheet(id);
218 const std::vector<uint8_t> current(pixels.begin(), pixels.end());
219 if (!preview.is_active() || preview.vector() != current) {
220 preview.Create(128, 32, 8, current);
221 }
222 preview.SetPalette(gfx::SnesPalette(colors));
223 if (preview.texture())
224 preview.UpdateTexture();
225 else
226 preview.CreateTexture();
227 const auto position = ImGui::GetCursorScreenPos();
228 const auto label = absl::StrFormat("Sheet %02X##replacement", id);
229 ImGui::PushID(id);
230 if (ImGui::Selectable(label.c_str(), pending_sheet_ == id, 0,
231 ImVec2(0, 50)))
232 pending_sheet_ = id;
233 gui::AutoRegisterLastItem("selectable",
234 absl::StrFormat("Sheet %02X", id));
235 if (preview.texture()) {
236 ImGui::GetWindowDrawList()->AddImage(
237 (ImTextureID)preview.texture(),
238 ImVec2(position.x + 84, position.y + 8),
239 ImVec2(position.x + 212, position.y + 40), ImVec2(0, 0),
240 ImVec2(1, 1), ImGui::GetColorU32(ImVec4(1, 1, 1, 1)));
241 }
242 ImGui::PopID();
243 }
244 }
245 }
246 ImGui::EndChild();
247}
248
250 const auto blocks = room.blocks();
251 if (blocks.size() < sheet_previews_.size()) {
252 preview_cache_valid_ = false;
253 return;
254 }
255
256 bool needs_refresh = palette_dirty_ || !preview_cache_valid_ ||
257 preview_room_id_ != room.id() ||
259 if (!needs_refresh) {
260 for (size_t i = 0; i < preview_block_ids_.size(); ++i) {
261 if (preview_block_ids_[i] != blocks[i]) {
262 needs_refresh = true;
263 break;
264 }
265 }
266 }
267 if (!needs_refresh) {
268 return;
269 }
270
271 std::vector<SDL_Color> palette_colors;
272 if (SDL_Surface* bg_surface = room.bg1_buffer().bitmap().surface()) {
273 if (SDL_Palette* palette = platform::GetSurfacePalette(bg_surface)) {
274 palette_colors.assign(palette->colors,
275 palette->colors + palette->ncolors);
276 }
277 }
278
279 const auto& gfx_buffer = room.get_gfx_buffer();
280 constexpr size_t kSheetBytes = 128 * 32;
281 for (size_t i = 0; i < sheet_previews_.size(); ++i) {
282 preview_block_ids_[i] = blocks[i];
283 const size_t offset = i * kSheetBytes;
284 auto& meta = sheet_preview_metadata_[i];
285 meta = {};
286 meta.block_id = blocks[i];
287 meta.source_offset = offset;
288 if (offset + kSheetBytes > gfx_buffer.size()) {
291 continue;
292 }
293
294 std::vector<uint8_t> sheet_pixels(
295 gfx_buffer.begin() + offset, gfx_buffer.begin() + offset + kSheetBytes);
296 meta.nonzero_pixels = static_cast<int>(
297 std::count_if(sheet_pixels.begin(), sheet_pixels.end(),
298 [](uint8_t value) { return value != 0; }));
299 sheet_previews_[i].Create(128, 32, 8,
300 static_cast<int>(gfx::BitmapFormat::kIndexed),
301 sheet_pixels);
302 if (!palette_colors.empty()) {
303 sheet_previews_[i].SetPalette(palette_colors);
304 }
305 // Bitmap::Create keeps an existing texture and advances its generation,
306 // superseding pending commands. Updating preserves handles that may already
307 // appear in this frame's draw list instead of destroying them during Draw.
308 if (sheet_previews_[i].texture() != nullptr) {
309 sheet_previews_[i].UpdateTexture();
310 } else {
311 sheet_previews_[i].CreateTexture();
312 }
313 meta.width = sheet_previews_[i].width();
314 meta.height = sheet_previews_[i].height();
315 meta.bitmap_active = sheet_previews_[i].is_active();
316 meta.has_surface = sheet_previews_[i].surface() != nullptr;
317 meta.palette_colors = static_cast<int>(palette_colors.size());
318 meta.has_texture = sheet_previews_[i].texture() != nullptr;
319 }
320
321 preview_room_id_ = room.id();
324 palette_dirty_ = false;
325}
326
327void RoomGraphicsContent::Draw(bool* p_open) {
328 gui::AutoWidgetScope scope("Dungeon/RoomGraphics");
329 (void)p_open;
330 if (runtime_detached_) {
331 ImGui::TextDisabled("Room graphics session closed.");
332 return;
333 }
334 // Resolve the active dungeon editor lazily when this panel is hosted through
335 // the workspace window system instead of the legacy direct constructor.
336 if (current_room_id_ == nullptr || rooms_ == nullptr) {
337 const auto ctx = CurrentDungeonWindowContext();
338 if (ctx) {
339 current_room_id_ = ctx.editor->mutable_current_room_id();
340 rooms_ = &ctx.editor->rooms();
341 renderer_ = ctx.editor->renderer();
342 assignment_callback_ = [editor = ctx.editor](int room, int slot,
343 int sheet) {
344 return editor->EditRoomGraphicsSheet(room, slot, sheet);
345 };
346 usage_callback_ = [editor = ctx.editor](int roomset) {
347 return editor->GetRoomGraphicsUsage(roomset);
348 };
349 }
350 }
351
352 if (current_room_id_ == nullptr || rooms_ == nullptr) {
353 ImGui::TextDisabled(tr("No room data available"));
354 return;
355 }
356
357 const int active_room_id = *current_room_id_;
358 if (active_room_id < 0 ||
359 active_room_id >= static_cast<int>(rooms_->size())) {
360 ImGui::TextDisabled(tr("Invalid room ID: %d"), active_room_id);
361 return;
362 }
363
364 auto& room = (*rooms_)[active_room_id];
365 if (selection_room_ != active_room_id ||
366 selection_group_ != room.blockset()) {
367 selection_room_ = active_room_id;
368 selection_group_ = room.blockset();
369 show_picker_ = false;
370 pending_sheet_ = -1;
371 assignment_error_.clear();
373 }
374 // Rebuild pixels and palettes together when entrance/header context changes.
375 // Loading only the sheet IDs can label old pixels with the new assignment.
376 room.PrepareForRender();
378
379 if (renderer_ != nullptr) {
381 }
382 auto blocks = room.blocks();
383
384 constexpr int kBlockWidth = 128;
385 constexpr int kBlockHeight = 32;
386 constexpr int kPreferredBlocksPerRow = 2;
387 constexpr float kPadding = 4.0f;
388
389 const int block_count = static_cast<int>(blocks.size());
390 ImGui::Text(tr("Room %03X Graphics Blocks"), active_room_id);
391 ImGui::TextDisabled(tr("Main %02X | Room %02X | Sprites %02X | Palette %02X"),
392 room.resolved_main_blockset(), room.blockset(),
393 room.spriteset(), room.palette());
394 auto& asset_manager = zelda3::CustomObjectManager::Get();
395 const auto patterns = asset_manager.GetTilePatterns();
396 if (!patterns.ok()) {
397 ImGui::TextWrapped("%s", patterns.status().ToString().c_str());
398 } else if (*patterns) {
399 ImGui::TextWrapped("%s", tr("Standard object patterns: source file"));
400 ImGui::TextWrapped("%s", (*patterns)->source_path.c_str());
401 ImGui::TextDisabled(tr("%zu tile words; ROM bytes unchanged"),
402 (*patterns)->words.size());
403 }
404 if (!patterns.ok() || *patterns) {
405 if (gui::AutoSmallButton(tr("Reload Pattern Source"))) {
406 asset_manager.ReloadAll();
407 }
408 }
409 ImGui::Checkbox(tr("Source Trace"), &show_source_trace_);
410 ImGui::SameLine();
411 DrawRoomGraphicsScaleControl(&sheet_scale_mode_);
412 ImGui::Separator();
413 const float available_width = ImGui::GetContentRegionAvail().x;
414
415 const gui::AdaptiveSheetGridLayout sheet_layout =
417 available_width, kBlockWidth, kBlockHeight, block_count,
418 kPreferredBlocksPerRow, sheet_scale_mode_, 0.35f, 4.0f, kPadding,
419 kPadding * 2.0f);
420 const ImVec2 canvas_size(
421 std::max(available_width, sheet_layout.content_width + kPadding * 2.0f),
422 sheet_layout.content_height + kPadding * 2.0f +
423 ((block_count + sheet_layout.columns - 1) / sheet_layout.columns) *
424 ImGui::GetTextLineHeightWithSpacing());
425
426 gui::CanvasFrameOptions frame_opts;
427 frame_opts.canvas_size = canvas_size;
428 frame_opts.draw_grid = false;
429 frame_opts.draw_overlay = false;
430 frame_opts.draw_context_menu = false;
431 frame_opts.render_popups = false;
432
433 // The canvas backdrop must yield hit testing to the slot buttons below.
434 ImGui::SetNextItemAllowOverlap();
435 auto rt = gui::BeginCanvas(room_gfx_canvas_, frame_opts);
436
437 const float grid_width = sheet_layout.content_width + kPadding * 2.0f;
438 const float x_offset = std::max(0.0f, (canvas_size.x - grid_width) * 0.5f);
439 ImDrawList* draw_list = ImGui::GetWindowDrawList();
440 for (int i = 0; i < block_count; ++i) {
441 const uint8_t block_id = blocks[static_cast<size_t>(i)];
442 const int row = i / sheet_layout.columns;
443 const int col = i % sheet_layout.columns;
444 const ImVec2 local_pos(
445 x_offset + kPadding + col * (sheet_layout.item_width + kPadding),
446 kPadding + row * (sheet_layout.item_height + kPadding +
447 ImGui::GetTextLineHeightWithSpacing()));
448
449 if (i < static_cast<int>(sheet_previews_.size()) &&
450 sheet_previews_[static_cast<size_t>(i)].texture() != 0) {
451 gui::BitmapPreviewOptions preview_opts;
452 preview_opts.dest_pos = local_pos;
453 preview_opts.dest_size =
454 ImVec2(sheet_layout.item_width, sheet_layout.item_height);
455 preview_opts.draw_context_menu = false;
456 preview_opts.draw_grid = false;
457 preview_opts.draw_overlay = false;
458 preview_opts.render_popups = false;
459 preview_opts.ensure_texture = true;
460 gui::DrawBitmapPreview(rt, sheet_previews_[static_cast<size_t>(i)],
461 preview_opts);
462 } else {
463 const ImVec2 zero = room_gfx_canvas_.zero_point();
464 const float scale = room_gfx_canvas_.global_scale();
465 const ImVec2 screen_pos(zero.x + local_pos.x, zero.y + local_pos.y);
466 const ImVec2 screen_end(screen_pos.x + sheet_layout.item_width * scale,
467 screen_pos.y + sheet_layout.item_height * scale);
468 draw_list->AddRect(screen_pos, screen_end,
469 ImGui::GetColorU32(gui::GetOutlineVec4()));
470 draw_list->AddText(ImVec2(screen_pos.x + 6.0f, screen_pos.y + 6.0f),
471 ImGui::GetColorU32(gui::GetTextSecondaryVec4()),
472 block_id == 0 ? "Empty" : "Missing");
473 }
474 const auto zero = room_gfx_canvas_.zero_point();
475 const ImVec2 start(zero.x + local_pos.x, zero.y + local_pos.y);
476 const ImVec2 end(start.x + sheet_layout.item_width,
477 start.y + sheet_layout.item_height);
478 const auto saved_cursor = ImGui::GetCursorScreenPos();
479 ImGui::SetCursorScreenPos(start);
480 ImGui::PushID(i);
481 if (ImGui::InvisibleButton(
482 "##RoomSheetSlot",
483 ImVec2(sheet_layout.item_width, sheet_layout.item_height),
484 ImGuiButtonFlags_EnableNav)) {
485 selected_slot_ = i;
486 show_picker_ = false;
487 assignment_error_.clear();
488 }
489 gui::AutoRegisterLastItem("button", absl::StrFormat("Slot %02d", i));
490 if (i == selected_slot_)
491 draw_list->AddRect(start, end, ImGui::GetColorU32(ImGuiCol_NavHighlight),
492 0, 0, 2);
493 const char* role = i < 3 ? "Main"
494 : i < 7 ? "Room"
495 : i == 7 ? "Main/anim"
496 : i < 12 ? "Fixed sprite"
497 : "Spriteset";
498 const auto label = absl::StrFormat("%02d %s / %02X", i, role, block_id);
499 draw_list->AddText(ImVec2(start.x, end.y),
500 ImGui::GetColorU32(ImGuiCol_Text), label.c_str());
501 ImGui::PopID();
502 ImGui::SetCursorScreenPos(saved_cursor);
503 }
504
505 gui::EndCanvas(room_gfx_canvas_, rt, frame_opts);
507
508 if (show_source_trace_ &&
509 ImGui::CollapsingHeader(ICON_MD_BUG_REPORT " Source Trace",
510 ImGuiTreeNodeFlags_DefaultOpen)) {
511 ImGui::TextDisabled(tr(
512 "Source: Room::get_gfx_buffer() -> preview bitmap -> queued texture"));
513 ImGui::TextDisabled(tr("Room buffer bytes: %zu"),
514 room.get_gfx_buffer().size());
515 ImGui::Separator();
516 for (int i = 0;
517 i < std::min(block_count,
518 static_cast<int>(sheet_preview_metadata_.size()));
519 ++i) {
520 const auto& meta = sheet_preview_metadata_[static_cast<size_t>(i)];
521 const uintptr_t texture_value = reinterpret_cast<uintptr_t>(
522 sheet_previews_[static_cast<size_t>(i)].texture());
523 ImGui::PushID(i);
524 ImGui::Text(
525 tr("Slot %02d | Block %02X | Buf +0x%04zX | %dx%d | nz=%d | pal=%d | "
526 "active=%d surf=%d tex=%d (0x%zx)"),
527 i, meta.block_id, meta.source_offset, meta.width, meta.height,
528 meta.nonzero_pixels, meta.palette_colors, meta.bitmap_active,
529 meta.has_surface, meta.has_texture, texture_value);
530 ImGui::PopID();
531 }
532 }
533}
534
535} // namespace editor
536} // namespace yaze
zelda3::GameData * game_data() const
std::array< uint8_t, 16 > preview_block_ids_
void RefreshSheetPreviews(const zelda3::Room &room)
std::map< int, gfx::Bitmap > picker_previews_
void DrawAssignmentControls(const zelda3::Room &room)
void DrawAssignmentPicker(const zelda3::Room &room)
void Draw(bool *p_open) override
Draw the panel content.
gui::AdaptiveSheetScaleMode sheet_scale_mode_
std::array< gfx::Bitmap, 16 > sheet_previews_
std::array< SheetPreviewMetadata, 16 > sheet_preview_metadata_
void RetireBitmap(Bitmap &bitmap)
Explicitly retire resources owned by a bitmap that is being erased.
Definition arena.cc:63
void ProcessTextureQueue(IRenderer *renderer)
Definition arena.cc:211
static Arena & Get()
Definition arena.cc:24
Represents a bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:70
Represents a palette of colors for the Super Nintendo Entertainment System (SNES).
RAII scope that enables automatic widget registration.
auto global_scale() const
Definition canvas.h:413
auto zero_point() const
Definition canvas.h:364
static CustomObjectManager & Get()
static constexpr uint16_t kSheetCount
const std::array< uint8_t, 0x10000 > & get_gfx_buffer() const
Definition room.h:1120
const std::vector< SDL_Color > & rendered_palette() const
Definition room.h:1138
uint8_t blockset() const
Definition room.h:1015
uint64_t graphics_revision() const
Definition room.h:1125
auto & bg1_buffer()
Definition room.h:1145
auto blocks() const
Definition room.h:1080
int BackgroundPaletteOffset(int slot) const
Definition room.cc:1085
int id() const
Definition room.h:1012
Locates graphics sheets in the ROM and writes edited sheets back.
#define ICON_MD_BUG_REPORT
Definition icons.h:327
void DrawRoomGraphicsScaleControl(gui::AdaptiveSheetScaleMode *mode)
DungeonWindowContext CurrentDungeonWindowContext()
@ kIndexed
Definition bitmap.h:38
void EndCanvas(Canvas &canvas)
void DrawBitmapPreview(const CanvasRuntime &rt, gfx::Bitmap &bitmap, const BitmapPreviewOptions &options)
void AutoRegisterLastItem(const std::string &widget_type, const std::string &explicit_label, const std::string &description)
Automatically register the last ImGui item.
void BeginCanvas(Canvas &canvas, ImVec2 child_size)
ImVec2 ScaledSize(float base_width, float base_height)
Returns a DPI-scaled version of a fixed pixel size.
Definition ui_config.h:148
ImVec4 GetTextSecondaryVec4()
bool AutoSmallButton(const char *label)
ImVec4 GetOutlineVec4()
AdaptiveSheetGridLayout ResolveAdaptiveSheetGridLayout(float available_width, int source_item_width, int source_item_height, int item_count, int preferred_columns=2, AdaptiveSheetScaleMode mode=AdaptiveSheetScaleMode::kFit, float min_scale=0.35f, float max_scale=4.0f, float gap=4.0f, float horizontal_chrome=24.0f)
SDL_Palette * GetSurfacePalette(SDL_Surface *surface)
Get the palette attached to a surface.
Definition sdl_compat.h:398
GfxSheetStorageKind GetGfxSheetStorageKind(uint16_t sheet_id)
SDL2/SDL3 compatibility layer.
Automatic widget registration helpers for ImGui Test Engine integration.