8#include "absl/strings/str_format.h"
18 while (!s.empty() && (s.front() ==
' ' || s.front() ==
'\t' ||
19 s.front() ==
'\n' || s.front() ==
'\r')) {
22 while (!s.empty() && (s.back() ==
' ' || s.back() ==
'\t' ||
23 s.back() ==
'\n' || s.back() ==
'\r')) {
37 if (trimmed.empty()) {
41 bool decimal_mode =
false;
42 if (trimmed.size() >= 2 && (trimmed[0] ==
'd' || trimmed[0] ==
'D') &&
45 trimmed.remove_prefix(2);
46 }
else if (trimmed.size() >= 2 && trimmed[0] ==
'0' &&
47 (trimmed[1] ==
'x' || trimmed[1] ==
'X')) {
48 trimmed.remove_prefix(2);
51 if (trimmed.empty()) {
55 std::string text(trimmed);
56 unsigned int parsed = 0;
58 const char* format = decimal_mode ?
"%u%c" :
"%x%c";
59 if (std::sscanf(text.c_str(), format, &parsed, &trailing) != 1) {
63 *out_value =
static_cast<int>(parsed);
71 total_tiles_(config_.total_tiles),
72 widget_id_(std::move(widget_id)) {}
76 total_tiles_(config.total_tiles),
77 widget_id_(std::move(widget_id)) {
107 const float cell = std::max(1.0f, std::floor(tile_size *
display_scale));
142 for (
const int tile_id : order) {
179 return ImVec2(GridWidthFor(
config_),
185 constexpr float kImGuiDefaultScrollbarSize = 14.0f;
186 return ImGui::GetCurrentContext() ? ImGui::GetStyle().ScrollbarSize
187 : kImGuiDefaultScrollbarSize;
191 float scrollbar_size) {
194 return GridWidthFor(config) + scrollbar_size;
214 ImGui::SetCursorPos(ImVec2(0, 0));
215 ImGui::Dummy(content_size);
216 ImGui::SetCursorPos(ImVec2(0, 0));
224 const ImVec2 window_size = ImGui::GetWindowSize();
226 target.x - (window_size.x / 2.0f) + (tile_display_size / 2.0f);
228 target.y - (window_size.y / 2.0f) + (tile_display_size / 2.0f);
229 scroll_x = std::max(0.0f, scroll_x);
230 scroll_y = std::max(0.0f, scroll_y);
231 ImGui::SetScrollX(scroll_x);
232 ImGui::SetScrollY(scroll_y);
255 ImGui::BeginTooltip();
256 ImGui::Text(tr(
"Tile %d (0x%03X)"), hovered_tile, hovered_tile);
259 if (!detail.empty()) {
260 ImGui::TextUnformatted(detail.c_str());
271 auto* texture_id = atlas.
texture();
272 if (texture_id !=
nullptr) {
273 float atlas_w =
static_cast<float>(atlas.
width());
274 float atlas_h =
static_cast<float>(atlas.
height());
275 if (atlas_w > 0 && atlas_h > 0) {
276 ImVec2 uv0(src_x / atlas_w, src_y / atlas_h);
280 ImGui::Image((ImTextureID)(intptr_t)texture_id,
281 ImVec2(preview_size, preview_size), uv0, uv1);
306 ImGui::SetClipboardText(
317 float tile_display_size) {
320 if (!ImGui::IsItemHovered()) {
324 const bool clicked = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
325 const bool double_clicked =
326 ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left);
327 const bool right_clicked = ImGui::IsMouseClicked(ImGuiMouseButton_Right);
329 if (clicked || double_clicked || right_clicked) {
353 if (!
canvas_ || tile_display_size <= 0.0f) {
357 const ImVec2 screen_pos = ImGui::GetIO().MousePos;
366 if (local.x < 0.0f || local.y < 0.0f) {
370 const int column =
static_cast<int>(local.x / tile_display_size);
371 const int row =
static_cast<int>(local.y / tile_display_size);
387 float tile_display_size)
const {
395 const int first_row = std::clamp(
396 static_cast<int>(std::floor((draw_list->GetClipRectMin().y - grid_top) /
399 const int last_row = std::clamp(
400 static_cast<int>(std::ceil((draw_list->GetClipRectMax().y - grid_top) /
403 return {first_row, last_row};
407 float tile_display_size)
const {
409 if (!draw_list || atlas.
texture() ==
nullptr || atlas.
width() <= 0 ||
414 const float atlas_w =
static_cast<float>(atlas.
width());
415 const float atlas_h =
static_cast<float>(atlas.
height());
417 const auto [first_row, last_row] =
VisibleRows(tile_display_size);
425 const ImVec2 dst_min(
429 (ImTextureID)(intptr_t)atlas.
texture(), dst_min,
430 ImVec2(dst_min.x + tile_display_size, dst_min.y + tile_display_size),
431 ImVec2(src_x / atlas_w, src_y / atlas_h),
448 grid_min.y + num_rows * tile_display_size);
451 const auto [first_row, last_row] =
VisibleRows(tile_display_size);
452 if (first_row >= last_row) {
456 constexpr ImU32 kGridColor = IM_COL32(200, 200, 200, 50);
457 const float top = grid_min.y + first_row * tile_display_size;
458 const float bottom = grid_min.y + last_row * tile_display_size;
460 const float x = grid_min.x + column * tile_display_size;
461 draw_list->AddLine(ImVec2(x, top), ImVec2(x, bottom), kGridColor);
463 for (
int row = first_row; row <= last_row; ++row) {
464 const float y = grid_min.y + row * tile_display_size;
465 draw_list->AddLine(ImVec2(grid_min.x, y), ImVec2(grid_max.x, y),
473 if (!draw_list || cell < 0) {
480 const ImVec2 tile_min(grid_min.x + column * tile_display_size,
481 grid_min.y + row * tile_display_size);
482 const ImVec2 tile_max(tile_min.x + tile_display_size,
483 tile_min.y + tile_display_size);
484 draw_list->AddRect(tile_min, tile_max,
507 return ImVec2(-1, -1);
517 std::string_view input) {
519 if (!ParseTileIdText(input, &tile_id)) {
537 const bool compact = ImGui::GetContentRegionAvail().x < 115.0f;
541 if (ImGui::Button(label)) {
542 ImGui::OpenPopup(
"TileLookup");
545 "button", ImGui::GetItemID());
546 if (ImGui::IsItemHovered()) {
547 ImGui::SetTooltip(
"Jump to a tile ID or filter a range%s",
551 ImGui::SetNextWindowSizeConstraints(ImVec2(300.0f, 0.0f),
552 ImVec2(380.0f, 500.0f));
553 if (ImGui::BeginPopup(
"TileLookup")) {
556 ImGui::CloseCurrentPopup();
567 const float available_width =
568 std::max(ImGui::GetContentRegionAvail().x, 1.0f);
569 const bool compact_layout = available_width < 420.0f;
570 const float jump_input_width =
571 compact_layout ? std::clamp(available_width * 0.28f, 56.0f, 88.0f)
573 const float range_input_width =
575 ? std::clamp((available_width - 110.0f) * 0.5f, 56.0f, 88.0f)
578 constexpr ImGuiInputTextFlags kInputFlags =
579 ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll;
584 ImGui::AlignTextToFramePadding();
585 ImGui::TextUnformatted(tr(
"Go:"));
588 ImGui::SetNextItemWidth(jump_input_width);
601 "input", ImGui::GetItemID());
602 if (ImGui::IsItemHovered()) {
604 tr(
"Enter tile ID and press Enter:\n"
610 ImGui::TextDisabled(tr(
"/ 0x%03X"), max_tile_id);
613 ImGui::TextUnformatted(tr(
"Invalid tile ID (hex or d:decimal)"));
615 ImGui::Text(tr(
"Out of range (max: 0x%03X)"), max_tile_id);
619 if (!compact_layout) {
620 ImGui::SameLine(0, 12.0f);
622 ImGui::AlignTextToFramePadding();
623 ImGui::TextUnformatted(tr(
"Range:"));
626 bool range_changed =
false;
627 ImGui::SetNextItemWidth(range_input_width);
630 range_changed =
true;
633 "input", ImGui::GetItemID());
634 if (ImGui::IsItemHovered()) {
636 tr(
"Min tile ID. Press Enter to apply.\n"
641 ImGui::TextUnformatted(
"-");
643 ImGui::SetNextItemWidth(range_input_width);
646 range_changed =
true;
649 "input", ImGui::GetItemID());
650 if (ImGui::IsItemHovered()) {
652 tr(
"Max tile ID. Press Enter to apply.\n"
656 if (!compact_layout) {
658 ImGui::TextDisabled(tr(
"(hex, d:dec)"));
667 if (has_min && has_max && parsed_min <= parsed_max) {
677 }
else if (!has_min && !has_max) {
681 }
else if (has_min && has_max && parsed_min > parsed_max) {
690 if (!compact_layout) {
693 if (ImGui::SmallButton(tr(
"X##ClearRange"))) {
699 if (ImGui::IsItemHovered()) {
700 ImGui::SetTooltip(tr(
"Clear range filter"));
706 if (!compact_layout) {
707 ImGui::SameLine(0, 8.0f);
709 ImGui::TextColored(ImVec4(1.0f, 0.4f, 0.4f, 1.0f),
710 tr(
"Min must be <= Max"));
712 if (!compact_layout) {
713 ImGui::SameLine(0, 8.0f);
715 ImGui::TextColored(ImVec4(1.0f, 0.4f, 0.4f, 1.0f),
719 if (range_count <= 0) {
720 if (!compact_layout) {
721 ImGui::SameLine(0, 8.0f);
723 ImGui::TextDisabled(tr(
"(no tiles in range)"));
Represents a bitmap image optimized for SNES ROM hacking.
TextureHandle texture() const
Modern, robust canvas for drawing and manipulating graphics.
void DrawBitmap(Bitmap &bitmap, int border_offset, float scale)
CanvasConfig & GetConfig()
void DrawBackground(ImVec2 canvas_size=ImVec2(0, 0), std::optional< ImVec2 > viewport_size=std::nullopt)
#define ICON_MD_CONTENT_COPY
#define ICON_MD_FILTER_ALT
Graphical User Interface (GUI) components for the application.
bool BeginTileDragSource(int tile_id, int map_id)