10#include <unordered_map>
13#include "absl/strings/string_view.h"
16#include "imgui/imgui.h"
17#include "imgui/imgui_internal.h"
20#include <emscripten.h>
25 using Ts::operator()...;
32static inline ImGuiInputTextFlags InputScalar_DefaultCharsFilter(
33 ImGuiDataType data_type,
const char* format) {
34 if (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double)
35 return ImGuiInputTextFlags_CharsScientific;
36 const char format_last_char = format[0] ? format[strlen(format) - 1] : 0;
37 return (format_last_char ==
'x' || format_last_char ==
'X')
38 ? ImGuiInputTextFlags_CharsHexadecimal
39 : ImGuiInputTextFlags_CharsDecimal;
43static inline bool IsInvisibleLabel(
const char* label) {
44 return label && label[0] ==
'#' && label[1] ==
'#';
56 const void* p_step,
const void* p_step_fast,
57 const char* format,
float input_width,
58 ImGuiInputTextFlags flags,
bool no_step =
false) {
63 ImGuiWindow* window = ImGui::GetCurrentWindow();
64 if (window->SkipItems)
67 ImGuiContext& g = *GImGui;
68 ImGuiStyle& style = g.Style;
71 format = DataTypeGetInfo(data_type)->PrintFmt;
74 DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, p_data, format);
76 if (g.ActiveId == 0 && (flags & (ImGuiInputTextFlags_CharsDecimal |
77 ImGuiInputTextFlags_CharsHexadecimal |
78 ImGuiInputTextFlags_CharsScientific)) == 0)
79 flags |= InputScalar_DefaultCharsFilter(data_type, format);
80 flags |= ImGuiInputTextFlags_AutoSelectAll;
82 bool value_changed =
false;
83 const float button_size = GetFrameHeight();
87 bool invisible_label = IsInvisibleLabel(label);
89 if (!invisible_label) {
90 AlignTextToFramePadding();
98 SetNextItemWidth(ImMax(
99 1.0f, CalcItemWidth() - (button_size + style.ItemInnerSpacing.x) * 2));
102 PushStyleVar(ImGuiStyleVar_ItemSpacing,
103 ImVec2{style.ItemSpacing.x, style.ItemSpacing.y});
104 PushStyleVar(ImGuiStyleVar_FramePadding,
105 ImVec2{style.FramePadding.x, style.FramePadding.y});
107 SetNextItemWidth(input_width);
108 if (InputText(
"", buf, IM_ARRAYSIZE(buf),
111 value_changed = DataTypeApplyFromText(buf, data_type, p_data, format);
112 IMGUI_TEST_ENGINE_ITEM_INFO(
113 g.LastItemData.ID, label,
114 g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Inputable);
121 const ImVec2 backup_frame_padding = style.FramePadding;
122 style.FramePadding.x = style.FramePadding.y;
123 ImGuiButtonFlags button_flags = ImGuiButtonFlags_PressedOnClick;
124 if (flags & ImGuiInputTextFlags_ReadOnly)
126 SameLine(0, style.ItemInnerSpacing.x);
127 if (ButtonEx(
"-", ImVec2(button_size, button_size), button_flags)) {
128 DataTypeApplyOp(data_type,
'-', p_data, p_data,
129 g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step);
130 value_changed =
true;
132 SameLine(0, style.ItemInnerSpacing.x);
133 if (ButtonEx(
"+", ImVec2(button_size, button_size), button_flags)) {
134 DataTypeApplyOp(data_type,
'+', p_data, p_data,
135 g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step);
136 value_changed =
true;
139 if (flags & ImGuiInputTextFlags_ReadOnly)
142 style.FramePadding = backup_frame_padding;
146 ImGui::PopStyleVar(2);
149 MarkItemEdited(g.LastItemData.ID);
151 return value_changed;
156 void* p_data,
const void* p_step,
157 const void* p_step_fast,
const char* format,
159 ImGuiInputTextFlags flags,
160 bool no_step =
false) {
163 ImGuiWindow* window = ImGui::GetCurrentWindow();
164 if (window->SkipItems)
167 ImGuiContext& g = *GImGui;
168 ImGuiStyle& style = g.Style;
171 format = DataTypeGetInfo(data_type)->PrintFmt;
174 DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, p_data, format);
176 if (g.ActiveId == 0 && (flags & (ImGuiInputTextFlags_CharsDecimal |
177 ImGuiInputTextFlags_CharsHexadecimal |
178 ImGuiInputTextFlags_CharsScientific)) == 0)
179 flags |= InputScalar_DefaultCharsFilter(data_type, format);
180 flags |= ImGuiInputTextFlags_AutoSelectAll;
182 const float button_size = GetFrameHeight();
186 bool invisible_label = IsInvisibleLabel(label);
188 if (!invisible_label) {
189 AlignTextToFramePadding();
196 SetNextItemWidth(ImMax(
197 1.0f, CalcItemWidth() - (button_size + style.ItemInnerSpacing.x) * 2));
199 PushStyleVar(ImGuiStyleVar_ItemSpacing,
200 ImVec2{style.ItemSpacing.x, style.ItemSpacing.y});
201 PushStyleVar(ImGuiStyleVar_FramePadding,
202 ImVec2{style.FramePadding.x, style.FramePadding.y});
204 SetNextItemWidth(input_width);
205 if (InputText(
"", buf, IM_ARRAYSIZE(buf), flags)) {
206 if (DataTypeApplyFromText(buf, data_type, p_data, format)) {
213 if (IsItemDeactivatedAfterEdit()) {
217 IMGUI_TEST_ENGINE_ITEM_INFO(
218 g.LastItemData.ID, label,
219 g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Inputable);
226 const ImVec2 backup_frame_padding = style.FramePadding;
227 style.FramePadding.x = style.FramePadding.y;
228 ImGuiButtonFlags button_flags = ImGuiButtonFlags_PressedOnClick;
229 if (flags & ImGuiInputTextFlags_ReadOnly)
231 SameLine(0, style.ItemInnerSpacing.x);
232 if (ButtonEx(
"-", ImVec2(button_size, button_size), button_flags)) {
233 DataTypeApplyOp(data_type,
'-', p_data, p_data,
234 g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step);
238 SameLine(0, style.ItemInnerSpacing.x);
239 if (ButtonEx(
"+", ImVec2(button_size, button_size), button_flags)) {
240 DataTypeApplyOp(data_type,
'+', p_data, p_data,
241 g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step);
246 if (flags & ImGuiInputTextFlags_ReadOnly)
249 style.FramePadding = backup_frame_padding;
253 ImGui::PopStyleVar(2);
256 MarkItemEdited(g.LastItemData.ID);
270 ImGuiDataType data_type = ImGuiDataType_COUNT;
272 alignas(uint64_t) std::array<uint8_t, kMaxDeferredScalarBytes> candidate = {};
274 uint64_t) std::array<uint8_t, kMaxDeferredScalarBytes> source_model = {};
275 int last_seen_frame = -1;
276 bool editing =
false;
277 bool commit_blocked =
false;
281 ImGuiContext* context =
nullptr;
283 std::unordered_map<ImGuiID, DeferredScalarEdit>
edits;
289 const void* data,
size_t data_size,
int frame,
294 std::memcpy(edit.
candidate.data(), data, data_size);
301 if (storage.context != context || frame < storage.last_frame) {
303 storage.edits.clear();
306 for (
auto it = storage.edits.begin(); it != storage.edits.end();) {
307 if (frame - it->second.last_seen_frame > 1) {
308 it = storage.edits.erase(it);
313 storage.last_frame = frame;
317 ImGuiHoveredFlags hover_flags = ImGuiHoveredFlags_AllowWhenBlockedByPopup) {
318 if (!ImGui::IsItemHovered(hover_flags)) {
322 const ImGuiIO& io = ImGui::GetIO();
323 const bool platform_primary_held = io.KeyCtrl || io.KeySuper;
324 return ImGui::IsItemActive() || platform_primary_held;
329 T* data, T min_value, T max_value,
330 ImGuiHoveredFlags hover_flags = ImGuiHoveredFlags_AllowWhenBlockedByPopup) {
335 const float wheel = ImGui::GetIO().MouseWheel;
340 using Numeric =
long long;
341 Numeric new_value =
static_cast<Numeric
>(*data) + (wheel > 0.0f ? 1 : -1);
342 new_value = std::clamp(new_value,
static_cast<Numeric
>(min_value),
343 static_cast<Numeric
>(max_value));
344 if (
static_cast<T
>(new_value) != *data) {
345 *data =
static_cast<T
>(new_value);
346 ImGui::ClearActiveID();
361 const char* format, ImGuiInputTextFlags flags,
363 ImGuiContext* context = ImGui::GetCurrentContext();
364 ImGuiWindow* window = context ? ImGui::GetCurrentWindow() :
nullptr;
365 if (!context || !window || window->SkipItems || !label || !data ||
366 data_type < 0 || data_type >= ImGuiDataType_COUNT) {
370 const ImGuiDataTypeInfo* data_type_info = ImGui::DataTypeGetInfo(data_type);
371 const size_t data_size = data_type_info->Size;
372 if (data_size == 0 || data_size > kMaxDeferredScalarBytes) {
376 const int frame = context->FrameCount;
377 PrepareDeferredScalarStorage(context, frame);
379 const ImGuiID item_id = ImGui::GetID(label);
380 auto [it, inserted] = g_deferred_scalar_storage.
edits.try_emplace(item_id);
381 DeferredScalarEdit& edit = it->second;
382 const bool target_changed =
383 !inserted && !(edit.target_identity == target_identity);
384 const bool reset_before_draw = inserted || edit.data_type != data_type ||
385 (target_changed && !edit.editing);
386 if (reset_before_draw) {
387 ResetDeferredScalarEdit(edit, data_type, data, data_size, frame,
389 }
else if (target_changed) {
392 edit.target_identity = target_identity;
393 edit.commit_blocked =
true;
394 }
else if (edit.editing &&
395 std::memcmp(edit.source_model.data(), data, data_size) != 0) {
398 edit.commit_blocked =
true;
399 }
else if (!edit.editing) {
400 ResetDeferredScalarEdit(edit, data_type, data, data_size, frame,
403 edit.last_seen_frame = frame;
405 const ImGuiInputTextFlags scalar_flags =
406 flags & ~ImGuiInputTextFlags_EnterReturnsTrue;
407 ImGui::InputScalar(label, data_type, edit.candidate.data(),
nullptr,
nullptr,
408 format, scalar_flags);
410 if (ImGui::IsItemDeactivatedAfterEdit()) {
413 if (reset_before_draw || edit.commit_blocked) {
414 ResetDeferredScalarEdit(edit, data_type, data, data_size, frame,
419 std::memcmp(data, edit.candidate.data(), data_size) != 0;
421 std::memcpy(data, edit.candidate.data(), data_size);
423 ResetDeferredScalarEdit(edit, data_type, data, data_size, frame,
428 if (ImGui::IsItemActive()) {
430 }
else if (edit.editing) {
432 ResetDeferredScalarEdit(edit, data_type, data, data_size, frame,
439 return ImGui::InputScalar(label, ImGuiDataType_U64, data, &
kStepOneHex64,
441 ImGuiInputTextFlags_CharsHexadecimal);
444bool InputHex(
const char* label,
int* data,
int num_digits,
float input_width) {
445 const std::string format =
"%0" + std::to_string(num_digits) +
"X";
448 format.c_str(), input_width, ImGuiInputTextFlags_CharsHexadecimal);
452 ApplyHexMouseWheel<int>(data, 0, std::numeric_limits<int>::max());
453 return changed || wheel_changed;
457 return ImGui::InputScalar(label, ImGuiDataType_U32, data, &
kStepOneHex,
459 ImGuiInputTextFlags_CharsHexadecimal);
466 input_width, ImGuiInputTextFlags_CharsHexadecimal, no_step);
467 bool wheel_changed = ApplyHexMouseWheel<uint16_t>(
468 data, 0u, std::numeric_limits<uint16_t>::max());
469 return changed || wheel_changed;
476 input_width, ImGuiInputTextFlags_CharsHexadecimal, no_step);
478 ApplyHexMouseWheel<int16_t>(data, std::numeric_limits<int16_t>::min(),
479 std::numeric_limits<int16_t>::max());
480 return changed || wheel_changed;
487 input_width, ImGuiInputTextFlags_CharsHexadecimal, no_step);
488 bool wheel_changed = ApplyHexMouseWheel<uint8_t>(
489 data, 0u, std::numeric_limits<uint8_t>::max());
490 return changed || wheel_changed;
494 float input_width,
bool no_step) {
497 input_width, ImGuiInputTextFlags_CharsHexadecimal, no_step);
498 if (changed && *data > max_value) {
501 bool wheel_changed = ApplyHexMouseWheel<uint8_t>(data, 0u, max_value);
502 return changed || wheel_changed;
507 float input_width,
bool no_step) {
510 input_width, ImGuiInputTextFlags_CharsHexadecimal, no_step);
511 if (ApplyHexMouseWheel<uint8_t>(data, 0u, std::numeric_limits<uint8_t>::max(),
512 ImGuiHoveredFlags_None)) {
513 result.changed =
true;
514 result.immediate =
true;
517 hex_result.
changed = result.changed;
524 uint8_t max_value,
float input_width,
528 input_width, ImGuiInputTextFlags_CharsHexadecimal, no_step);
529 if (result.changed && *data > max_value) {
532 if (ApplyHexMouseWheel<uint8_t>(data, 0u, max_value,
533 ImGuiHoveredFlags_None)) {
534 result.changed =
true;
535 result.immediate =
true;
538 hex_result.
changed = result.changed;
545 float input_width,
bool no_step) {
548 input_width, ImGuiInputTextFlags_CharsHexadecimal, no_step);
549 if (ApplyHexMouseWheel<uint16_t>(data, 0u,
550 std::numeric_limits<uint16_t>::max(),
551 ImGuiHoveredFlags_None)) {
552 result.changed =
true;
553 result.immediate =
true;
556 hex_result.
changed = result.changed;
563 ImGui::TextWrapped(
"%s", text.c_str());
569 ImGui::PushID(text.c_str());
572 ImVec2 text_size = ImGui::CalcTextSize(text.c_str());
575 ImVec2 pos = ImGui::GetCursorScreenPos();
576 ImRect bb(pos, ImVec2(pos.x + text_size.x, pos.y + text_size.y));
579 const ImGuiID
id = ImGui::GetID(text.c_str());
581 if (ImGui::ItemAdd(bb,
id)) {
582 bool hovered = ImGui::IsItemHovered();
583 bool clicked = ImGui::IsItemClicked();
586 ImVec4 link_color = ImGui::GetStyleColorVec4(ImGuiCol_TextLink);
587 ImVec4 bg_color = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg);
590 float contrast_factor =
591 (bg_color.x + bg_color.y + bg_color.z) < 1.5f ? 1.0f : 0.3f;
596 color = ImVec4(std::min(1.0f, link_color.x + 0.3f),
597 std::min(1.0f, link_color.y + 0.3f),
598 std::min(1.0f, link_color.z + 0.3f), 1.0f);
601 color = ImVec4(std::max(contrast_factor, link_color.x),
602 std::max(contrast_factor, link_color.y),
603 std::max(contrast_factor, link_color.z), 1.0f);
606 ImGui::GetWindowDrawList()->AddText(
607 pos, ImGui::ColorConvertFloat4ToU32(color), text.c_str());
615 ImGui::Dummy(text_size);
622 ImGuiWindow* window = ImGui::GetCurrentWindow();
623 const ImVec2 lineStart = ImGui::GetCursorScreenPos();
624 const ImGuiStyle& style = ImGui::GetStyle();
625 float fullWidth = ImGui::GetContentRegionAvail().x;
626 float itemWidth = ImGui::CalcItemWidth() + style.ItemSpacing.x;
628 ImGui::CalcTextSize(title.data(), title.data() + title.size());
630 textRect.Min = ImGui::GetCursorScreenPos();
631 if (flags & ItemLabelFlag::Right)
632 textRect.Min.x = textRect.Min.x + itemWidth;
633 textRect.Max = textRect.Min;
634 textRect.Max.x += fullWidth - itemWidth;
635 textRect.Max.y += textSize.y;
637 ImGui::SetCursorScreenPos(textRect.Min);
639 ImGui::AlignTextToFramePadding();
642 textRect.Min.y += window->DC.CurrLineTextBaseOffset;
643 textRect.Max.y += window->DC.CurrLineTextBaseOffset;
645 ImGui::ItemSize(textRect);
647 textRect, window->GetID(title.data(), title.data() + title.size()))) {
648 ImGui::RenderTextEllipsis(ImGui::GetWindowDrawList(), textRect.Min,
649 textRect.Max, textRect.Max.x, title.data(),
650 title.data() + title.size(), &textSize);
652 if (textRect.GetWidth() < textSize.x && ImGui::IsItemHovered())
653 ImGui::SetTooltip(
"%.*s", (
int)title.size(), title.data());
655 if (flags & ItemLabelFlag::Left) {
657 auto other = ImVec2{0, textSize.y + window->DC.CurrLineTextBaseOffset};
658 result.x = textRect.Max.x - other.x;
659 result.y = textRect.Max.y - other.y;
660 ImGui::SetCursorScreenPos(result);
662 }
else if (flags & ItemLabelFlag::Right)
663 ImGui::SetCursorScreenPos(lineStart);
666bool ListBox(
const char* label,
int* current_item,
667 const std::vector<std::string>& items,
int height_in_items) {
668 std::vector<const char*> items_ptr;
669 items_ptr.reserve(items.size());
670 for (
const auto& item : items) {
671 items_ptr.push_back(item.c_str());
673 int items_count =
static_cast<int>(items.size());
674 return ImGui::ListBox(label, current_item, items_ptr.data(), items_count,
679 ImGui::PushID(label);
681 bool changed =
false;
684 changed |= ImGui::Checkbox(tr(
"Priority"), &tile_info->
over_);
685 changed |= ImGui::Checkbox(tr(
"Vertical Flip"), &tile_info->
vertical_mirror_);
693ImGuiID
GetID(
const std::string&
id) {
694 return ImGui::GetID(
id.c_str());
752 return ImGuiKey_Slash;
754 return ImGuiKey_Minus;
756 return ImGuiKey_COUNT;
773 ImGui::TableNextColumn();
788 ImGuiContext* context = ImGui::GetCurrentContext();
789 if (context ==
nullptr || url.empty())
798 const opened = window.open(
'about:blank',
'_blank');
801 opened.opener = null;
802 opened.location.href = UTF8ToString($0);
807 ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
808 if (platform_io.Platform_OpenInShellFn ==
nullptr)
811 return platform_io.Platform_OpenInShellFn(context, url.c_str());
816 static bool open =
false;
818 if (ImGui::Button(tr(
"View Data"))) {
822 ImGui::Begin(label.c_str(), &open);
830 ImGui::PushID(label);
834 snprintf(buf,
sizeof(buf),
"%02X", *data);
836 ImGui::SetNextItemWidth(input_width);
837 bool changed = ImGui::InputText(
838 label, buf,
sizeof(buf),
839 ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_AutoSelectAll);
843 if (sscanf(buf,
"%X", &temp) == 1) {
844 *data =
static_cast<uint8_t
>(temp & 0xFF);
853 ImGui::PushID(label);
857 snprintf(buf,
sizeof(buf),
"%04X", *data);
859 ImGui::SetNextItemWidth(input_width);
860 bool changed = ImGui::InputText(
861 label, buf,
sizeof(buf),
862 ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_AutoSelectAll);
866 if (sscanf(buf,
"%X", &temp) == 1) {
867 *data =
static_cast<uint16_t
>(temp & 0xFFFF);
876 const char* format,
float wheel_step,
877 ImGuiSliderFlags flags) {
878 bool changed = ImGui::SliderFloat(label, v, v_min, v_max, format, flags);
882 if (IsValueWheelAdjustmentAllowedForCurrentItem()) {
883 float wheel = ImGui::GetIO().MouseWheel;
885 *v = std::clamp(*v + wheel * wheel_step, v_min, v_max);
893 const char* format,
int wheel_step,
894 ImGuiSliderFlags flags) {
895 bool changed = ImGui::SliderInt(label, v, v_min, v_max, format, flags);
897 if (IsValueWheelAdjustmentAllowedForCurrentItem()) {
898 float wheel = ImGui::GetIO().MouseWheel;
900 int delta =
static_cast<int>(wheel) * wheel_step;
901 *v = std::clamp(*v + delta, v_min, v_max);
SNES 16-bit tile metadata container.
InputScalarResult InputScalarLeftEx(const char *label, ImGuiDataType data_type, void *p_data, const void *p_step, const void *p_step_fast, const char *format, float input_width, ImGuiInputTextFlags flags, bool no_step=false)
bool InputScalarLeft(const char *label, ImGuiDataType data_type, void *p_data, const void *p_step, const void *p_step_fast, const char *format, float input_width, ImGuiInputTextFlags flags, bool no_step=false)
bool InputHexByteCustom(const char *label, uint8_t *data, float input_width)
bool ClickableText(const std::string &text)
bool SliderIntWheel(const char *label, int *v, int v_min, int v_max, const char *format, int wheel_step, ImGuiSliderFlags flags)
ImGuiID GetID(const std::string &id)
void Paragraph(const std::string &text)
bool InputHexWord(const char *label, uint16_t *data, float input_width, bool no_step)
bool ListBox(const char *label, int *current_item, const std::vector< std::string > &items, int height_in_items)
bool SliderFloatWheel(const char *label, float *v, float v_min, float v_max, const char *format, float wheel_step, ImGuiSliderFlags flags)
bool InputHexShort(const char *label, uint32_t *data)
void AddTableColumn(Table &table, const std::string &label, GuiElement element)
enum ItemLabelFlag { Left=1u<< 0u, Right=1u<< 1u, Default=Left, } ItemLabelFlags
void MemoryEditorPopup(const std::string &label, std::span< uint8_t > memory)
void DrawTable(Table ¶ms)
bool OpenUrl(const std::string &url)
bool InputHexWordCustom(const char *label, uint16_t *data, float input_width)
bool InputHex(const char *label, uint64_t *data)
const uint64_t kStepFastHex64
bool InputTileInfo(const char *label, gfx::TileInfo *tile_info)
bool InputScalarDeferred(const char *label, ImGuiDataType data_type, void *data, const char *format, ImGuiInputTextFlags flags, InputScalarTargetIdentity target_identity)
std::variant< std::function< void()>, std::string > GuiElement
InputHexResult InputHexByteEx(const char *label, uint8_t *data, float input_width, bool no_step)
ImGuiKey MapKeyToImGuiKey(char key)
bool InputHexByte(const char *label, uint8_t *data, float input_width, bool no_step)
void ItemLabel(absl::string_view title, ItemLabelFlags flags)
const uint64_t kStepOneHex64
InputHexResult InputHexWordEx(const char *label, uint16_t *data, float input_width, bool no_step)
std::vector< std::string > column_labels
std::vector< GuiElement > column_contents