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);
117 if (IsItemHovered() && g.IO.MouseWheel != 0.0f) {
118 float scroll_amount = g.IO.MouseWheel;
119 float scroll_speed = 0.25f;
121 if (g.IO.KeyCtrl && p_step_fast)
122 scroll_amount *= *(
const float*)p_step_fast;
124 scroll_amount *= *(
const float*)p_step;
126 if (scroll_amount > 0.0f) {
127 scroll_amount *= scroll_speed;
128 DataTypeApplyOp(data_type,
'+', p_data, p_data, &scroll_amount);
129 value_changed =
true;
130 }
else if (scroll_amount < 0.0f) {
131 scroll_amount *= -scroll_speed;
132 DataTypeApplyOp(data_type,
'-', p_data, p_data, &scroll_amount);
133 value_changed =
true;
139 const ImVec2 backup_frame_padding = style.FramePadding;
140 style.FramePadding.x = style.FramePadding.y;
141 ImGuiButtonFlags button_flags = ImGuiButtonFlags_PressedOnClick;
142 if (flags & ImGuiInputTextFlags_ReadOnly)
144 SameLine(0, style.ItemInnerSpacing.x);
145 if (ButtonEx(
"-", ImVec2(button_size, button_size), button_flags)) {
146 DataTypeApplyOp(data_type,
'-', p_data, p_data,
147 g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step);
148 value_changed =
true;
150 SameLine(0, style.ItemInnerSpacing.x);
151 if (ButtonEx(
"+", ImVec2(button_size, button_size), button_flags)) {
152 DataTypeApplyOp(data_type,
'+', p_data, p_data,
153 g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step);
154 value_changed =
true;
157 if (flags & ImGuiInputTextFlags_ReadOnly)
160 style.FramePadding = backup_frame_padding;
164 ImGui::PopStyleVar(2);
167 MarkItemEdited(g.LastItemData.ID);
169 return value_changed;
174 void* p_data,
const void* p_step,
175 const void* p_step_fast,
const char* format,
177 ImGuiInputTextFlags flags,
178 bool no_step =
false) {
181 ImGuiWindow* window = ImGui::GetCurrentWindow();
182 if (window->SkipItems)
185 ImGuiContext& g = *GImGui;
186 ImGuiStyle& style = g.Style;
189 format = DataTypeGetInfo(data_type)->PrintFmt;
192 DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, p_data, format);
194 if (g.ActiveId == 0 && (flags & (ImGuiInputTextFlags_CharsDecimal |
195 ImGuiInputTextFlags_CharsHexadecimal |
196 ImGuiInputTextFlags_CharsScientific)) == 0)
197 flags |= InputScalar_DefaultCharsFilter(data_type, format);
198 flags |= ImGuiInputTextFlags_AutoSelectAll;
200 const float button_size = GetFrameHeight();
204 bool invisible_label = IsInvisibleLabel(label);
206 if (!invisible_label) {
207 AlignTextToFramePadding();
214 SetNextItemWidth(ImMax(
215 1.0f, CalcItemWidth() - (button_size + style.ItemInnerSpacing.x) * 2));
217 PushStyleVar(ImGuiStyleVar_ItemSpacing,
218 ImVec2{style.ItemSpacing.x, style.ItemSpacing.y});
219 PushStyleVar(ImGuiStyleVar_FramePadding,
220 ImVec2{style.FramePadding.x, style.FramePadding.y});
222 SetNextItemWidth(input_width);
223 if (InputText(
"", buf, IM_ARRAYSIZE(buf), flags)) {
224 if (DataTypeApplyFromText(buf, data_type, p_data, format)) {
231 if (IsItemDeactivatedAfterEdit()) {
235 IMGUI_TEST_ENGINE_ITEM_INFO(
236 g.LastItemData.ID, label,
237 g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Inputable);
240 if (IsItemHovered() && g.IO.MouseWheel != 0.0f) {
241 float scroll_amount = g.IO.MouseWheel;
242 float scroll_speed = 0.25f;
244 if (g.IO.KeyCtrl && p_step_fast)
245 scroll_amount *= *(
const float*)p_step_fast;
247 scroll_amount *= *(
const float*)p_step;
249 if (scroll_amount > 0.0f) {
250 scroll_amount *= scroll_speed;
251 DataTypeApplyOp(data_type,
'+', p_data, p_data, &scroll_amount);
254 }
else if (scroll_amount < 0.0f) {
255 scroll_amount *= -scroll_speed;
256 DataTypeApplyOp(data_type,
'-', p_data, p_data, &scroll_amount);
264 const ImVec2 backup_frame_padding = style.FramePadding;
265 style.FramePadding.x = style.FramePadding.y;
266 ImGuiButtonFlags button_flags = ImGuiButtonFlags_PressedOnClick;
267 if (flags & ImGuiInputTextFlags_ReadOnly)
269 SameLine(0, style.ItemInnerSpacing.x);
270 if (ButtonEx(
"-", ImVec2(button_size, button_size), button_flags)) {
271 DataTypeApplyOp(data_type,
'-', p_data, p_data,
272 g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step);
276 SameLine(0, style.ItemInnerSpacing.x);
277 if (ButtonEx(
"+", ImVec2(button_size, button_size), button_flags)) {
278 DataTypeApplyOp(data_type,
'+', p_data, p_data,
279 g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step);
284 if (flags & ImGuiInputTextFlags_ReadOnly)
287 style.FramePadding = backup_frame_padding;
291 ImGui::PopStyleVar(2);
294 MarkItemEdited(g.LastItemData.ID);
308 ImGuiDataType data_type = ImGuiDataType_COUNT;
310 alignas(uint64_t) std::array<uint8_t, kMaxDeferredScalarBytes> candidate = {};
312 uint64_t) std::array<uint8_t, kMaxDeferredScalarBytes> source_model = {};
313 int last_seen_frame = -1;
314 bool editing =
false;
315 bool commit_blocked =
false;
319 ImGuiContext* context =
nullptr;
321 std::unordered_map<ImGuiID, DeferredScalarEdit>
edits;
327 const void* data,
size_t data_size,
int frame,
332 std::memcpy(edit.
candidate.data(), data, data_size);
339 if (storage.context != context || frame < storage.last_frame) {
341 storage.edits.clear();
344 for (
auto it = storage.edits.begin(); it != storage.edits.end();) {
345 if (frame - it->second.last_seen_frame > 1) {
346 it = storage.edits.erase(it);
351 storage.last_frame = frame;
355 if (!ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) {
359 const ImGuiIO& io = ImGui::GetIO();
360 const bool platform_primary_held = io.KeyCtrl || io.KeySuper;
361 return ImGui::IsItemActive() || platform_primary_held;
370 const float wheel = ImGui::GetIO().MouseWheel;
375 using Numeric =
long long;
376 Numeric new_value =
static_cast<Numeric
>(*data) + (wheel > 0.0f ? 1 : -1);
377 new_value = std::clamp(new_value,
static_cast<Numeric
>(min_value),
378 static_cast<Numeric
>(max_value));
379 if (
static_cast<T
>(new_value) != *data) {
380 *data =
static_cast<T
>(new_value);
381 ImGui::ClearActiveID();
393 const char* format, ImGuiInputTextFlags flags,
395 ImGuiContext* context = ImGui::GetCurrentContext();
396 ImGuiWindow* window = context ? ImGui::GetCurrentWindow() :
nullptr;
397 if (!context || !window || window->SkipItems || !label || !data ||
398 data_type < 0 || data_type >= ImGuiDataType_COUNT) {
402 const ImGuiDataTypeInfo* data_type_info = ImGui::DataTypeGetInfo(data_type);
403 const size_t data_size = data_type_info->Size;
404 if (data_size == 0 || data_size > kMaxDeferredScalarBytes) {
408 const int frame = context->FrameCount;
409 PrepareDeferredScalarStorage(context, frame);
411 const ImGuiID item_id = ImGui::GetID(label);
412 auto [it, inserted] = g_deferred_scalar_storage.
edits.try_emplace(item_id);
413 DeferredScalarEdit& edit = it->second;
414 const bool target_changed =
415 !inserted && !(edit.target_identity == target_identity);
416 const bool reset_before_draw = inserted || edit.data_type != data_type ||
417 (target_changed && !edit.editing);
418 if (reset_before_draw) {
419 ResetDeferredScalarEdit(edit, data_type, data, data_size, frame,
421 }
else if (target_changed) {
424 edit.target_identity = target_identity;
425 edit.commit_blocked =
true;
426 }
else if (edit.editing &&
427 std::memcmp(edit.source_model.data(), data, data_size) != 0) {
430 edit.commit_blocked =
true;
431 }
else if (!edit.editing) {
432 ResetDeferredScalarEdit(edit, data_type, data, data_size, frame,
435 edit.last_seen_frame = frame;
437 const ImGuiInputTextFlags scalar_flags =
438 flags & ~ImGuiInputTextFlags_EnterReturnsTrue;
439 ImGui::InputScalar(label, data_type, edit.candidate.data(),
nullptr,
nullptr,
440 format, scalar_flags);
442 if (ImGui::IsItemDeactivatedAfterEdit()) {
445 if (reset_before_draw || edit.commit_blocked) {
446 ResetDeferredScalarEdit(edit, data_type, data, data_size, frame,
451 std::memcmp(data, edit.candidate.data(), data_size) != 0;
453 std::memcpy(data, edit.candidate.data(), data_size);
455 ResetDeferredScalarEdit(edit, data_type, data, data_size, frame,
460 if (ImGui::IsItemActive()) {
462 }
else if (edit.editing) {
464 ResetDeferredScalarEdit(edit, data_type, data, data_size, frame,
471 return ImGui::InputScalar(label, ImGuiDataType_U64, data, &
kStepOneHex,
473 ImGuiInputTextFlags_CharsHexadecimal);
476bool InputHex(
const char* label,
int* data,
int num_digits,
float input_width) {
477 const std::string format =
"%0" + std::to_string(num_digits) +
"X";
480 ImGuiInputTextFlags_CharsHexadecimal);
484 return ImGui::InputScalar(label, ImGuiDataType_U32, data, &
kStepOneHex,
486 ImGuiInputTextFlags_CharsHexadecimal);
493 input_width, ImGuiInputTextFlags_CharsHexadecimal, no_step);
494 bool wheel_changed = ApplyHexMouseWheel<uint16_t>(
495 data, 0u, std::numeric_limits<uint16_t>::max());
496 return changed || wheel_changed;
503 input_width, ImGuiInputTextFlags_CharsHexadecimal, no_step);
505 ApplyHexMouseWheel<int16_t>(data, std::numeric_limits<int16_t>::min(),
506 std::numeric_limits<int16_t>::max());
507 return changed || wheel_changed;
514 input_width, ImGuiInputTextFlags_CharsHexadecimal, no_step);
515 bool wheel_changed = ApplyHexMouseWheel<uint8_t>(
516 data, 0u, std::numeric_limits<uint8_t>::max());
517 return changed || wheel_changed;
521 float input_width,
bool no_step) {
524 input_width, ImGuiInputTextFlags_CharsHexadecimal, no_step);
525 if (changed && *data > max_value) {
528 bool wheel_changed = ApplyHexMouseWheel<uint8_t>(data, 0u, max_value);
529 return changed || wheel_changed;
534 float input_width,
bool no_step) {
537 input_width, ImGuiInputTextFlags_CharsHexadecimal, no_step);
539 hex_result.
changed = result.changed;
546 uint8_t max_value,
float input_width,
550 input_width, ImGuiInputTextFlags_CharsHexadecimal, no_step);
551 if (result.changed && *data > max_value) {
555 hex_result.
changed = result.changed;
562 float input_width,
bool no_step) {
565 input_width, ImGuiInputTextFlags_CharsHexadecimal, no_step);
567 hex_result.
changed = result.changed;
574 ImGui::TextWrapped(
"%s", text.c_str());
580 ImGui::PushID(text.c_str());
583 ImVec2 text_size = ImGui::CalcTextSize(text.c_str());
586 ImVec2 pos = ImGui::GetCursorScreenPos();
587 ImRect bb(pos, ImVec2(pos.x + text_size.x, pos.y + text_size.y));
590 const ImGuiID
id = ImGui::GetID(text.c_str());
592 if (ImGui::ItemAdd(bb,
id)) {
593 bool hovered = ImGui::IsItemHovered();
594 bool clicked = ImGui::IsItemClicked();
597 ImVec4 link_color = ImGui::GetStyleColorVec4(ImGuiCol_TextLink);
598 ImVec4 bg_color = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg);
601 float contrast_factor =
602 (bg_color.x + bg_color.y + bg_color.z) < 1.5f ? 1.0f : 0.3f;
607 color = ImVec4(std::min(1.0f, link_color.x + 0.3f),
608 std::min(1.0f, link_color.y + 0.3f),
609 std::min(1.0f, link_color.z + 0.3f), 1.0f);
612 color = ImVec4(std::max(contrast_factor, link_color.x),
613 std::max(contrast_factor, link_color.y),
614 std::max(contrast_factor, link_color.z), 1.0f);
617 ImGui::GetWindowDrawList()->AddText(
618 pos, ImGui::ColorConvertFloat4ToU32(color), text.c_str());
626 ImGui::Dummy(text_size);
633 ImGuiWindow* window = ImGui::GetCurrentWindow();
634 const ImVec2 lineStart = ImGui::GetCursorScreenPos();
635 const ImGuiStyle& style = ImGui::GetStyle();
636 float fullWidth = ImGui::GetContentRegionAvail().x;
637 float itemWidth = ImGui::CalcItemWidth() + style.ItemSpacing.x;
639 ImGui::CalcTextSize(title.data(), title.data() + title.size());
641 textRect.Min = ImGui::GetCursorScreenPos();
642 if (flags & ItemLabelFlag::Right)
643 textRect.Min.x = textRect.Min.x + itemWidth;
644 textRect.Max = textRect.Min;
645 textRect.Max.x += fullWidth - itemWidth;
646 textRect.Max.y += textSize.y;
648 ImGui::SetCursorScreenPos(textRect.Min);
650 ImGui::AlignTextToFramePadding();
653 textRect.Min.y += window->DC.CurrLineTextBaseOffset;
654 textRect.Max.y += window->DC.CurrLineTextBaseOffset;
656 ImGui::ItemSize(textRect);
658 textRect, window->GetID(title.data(), title.data() + title.size()))) {
659 ImGui::RenderTextEllipsis(ImGui::GetWindowDrawList(), textRect.Min,
660 textRect.Max, textRect.Max.x, title.data(),
661 title.data() + title.size(), &textSize);
663 if (textRect.GetWidth() < textSize.x && ImGui::IsItemHovered())
664 ImGui::SetTooltip(
"%.*s", (
int)title.size(), title.data());
666 if (flags & ItemLabelFlag::Left) {
668 auto other = ImVec2{0, textSize.y + window->DC.CurrLineTextBaseOffset};
669 result.x = textRect.Max.x - other.x;
670 result.y = textRect.Max.y - other.y;
671 ImGui::SetCursorScreenPos(result);
673 }
else if (flags & ItemLabelFlag::Right)
674 ImGui::SetCursorScreenPos(lineStart);
677bool ListBox(
const char* label,
int* current_item,
678 const std::vector<std::string>& items,
int height_in_items) {
679 std::vector<const char*> items_ptr;
680 items_ptr.reserve(items.size());
681 for (
const auto& item : items) {
682 items_ptr.push_back(item.c_str());
684 int items_count =
static_cast<int>(items.size());
685 return ImGui::ListBox(label, current_item, items_ptr.data(), items_count,
690 ImGui::PushID(label);
692 bool changed =
false;
695 changed |= ImGui::Checkbox(tr(
"Priority"), &tile_info->
over_);
696 changed |= ImGui::Checkbox(tr(
"Vertical Flip"), &tile_info->
vertical_mirror_);
704ImGuiID
GetID(
const std::string&
id) {
705 return ImGui::GetID(
id.c_str());
763 return ImGuiKey_Slash;
765 return ImGuiKey_Minus;
767 return ImGuiKey_COUNT;
784 ImGui::TableNextColumn();
799 ImGuiContext* context = ImGui::GetCurrentContext();
800 if (context ==
nullptr || url.empty())
809 const opened = window.open(
'about:blank',
'_blank');
812 opened.opener = null;
813 opened.location.href = UTF8ToString($0);
818 ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
819 if (platform_io.Platform_OpenInShellFn ==
nullptr)
822 return platform_io.Platform_OpenInShellFn(context, url.c_str());
827 static bool open =
false;
829 if (ImGui::Button(tr(
"View Data"))) {
833 ImGui::Begin(label.c_str(), &open);
841 ImGui::PushID(label);
845 snprintf(buf,
sizeof(buf),
"%02X", *data);
847 ImGui::SetNextItemWidth(input_width);
848 bool changed = ImGui::InputText(
849 label, buf,
sizeof(buf),
850 ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_AutoSelectAll);
854 if (sscanf(buf,
"%X", &temp) == 1) {
855 *data =
static_cast<uint8_t
>(temp & 0xFF);
864 ImGui::PushID(label);
868 snprintf(buf,
sizeof(buf),
"%04X", *data);
870 ImGui::SetNextItemWidth(input_width);
871 bool changed = ImGui::InputText(
872 label, buf,
sizeof(buf),
873 ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_AutoSelectAll);
877 if (sscanf(buf,
"%X", &temp) == 1) {
878 *data =
static_cast<uint16_t
>(temp & 0xFFFF);
887 const char* format,
float wheel_step,
888 ImGuiSliderFlags flags) {
889 bool changed = ImGui::SliderFloat(label, v, v_min, v_max, format, flags);
893 if (IsValueWheelAdjustmentAllowedForCurrentItem()) {
894 float wheel = ImGui::GetIO().MouseWheel;
896 *v = std::clamp(*v + wheel * wheel_step, v_min, v_max);
904 const char* format,
int wheel_step,
905 ImGuiSliderFlags flags) {
906 bool changed = ImGui::SliderInt(label, v, v_min, v_max, format, flags);
908 if (IsValueWheelAdjustmentAllowedForCurrentItem()) {
909 float wheel = ImGui::GetIO().MouseWheel;
911 int delta =
static_cast<int>(wheel) * wheel_step;
912 *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)
void Paragraph(const std::string &text)
void ItemLabel(absl::string_view title, ItemLabelFlags flags)
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)
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)
ImGuiID GetID(const std::string &id)
ImGuiKey MapKeyToImGuiKey(char key)
bool InputHexByte(const char *label, uint8_t *data, float input_width, bool no_step)
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