10#include <unordered_map>
13#include "absl/strings/string_view.h"
16#include "imgui/imgui.h"
17#include "imgui/imgui_internal.h"
21 using Ts::operator()...;
28static inline ImGuiInputTextFlags InputScalar_DefaultCharsFilter(
29 ImGuiDataType data_type,
const char* format) {
30 if (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double)
31 return ImGuiInputTextFlags_CharsScientific;
32 const char format_last_char = format[0] ? format[strlen(format) - 1] : 0;
33 return (format_last_char ==
'x' || format_last_char ==
'X')
34 ? ImGuiInputTextFlags_CharsHexadecimal
35 : ImGuiInputTextFlags_CharsDecimal;
39static inline bool IsInvisibleLabel(
const char* label) {
40 return label && label[0] ==
'#' && label[1] ==
'#';
52 const void* p_step,
const void* p_step_fast,
53 const char* format,
float input_width,
54 ImGuiInputTextFlags flags,
bool no_step =
false) {
59 ImGuiWindow* window = ImGui::GetCurrentWindow();
60 if (window->SkipItems)
63 ImGuiContext& g = *GImGui;
64 ImGuiStyle& style = g.Style;
67 format = DataTypeGetInfo(data_type)->PrintFmt;
70 DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, p_data, format);
72 if (g.ActiveId == 0 && (flags & (ImGuiInputTextFlags_CharsDecimal |
73 ImGuiInputTextFlags_CharsHexadecimal |
74 ImGuiInputTextFlags_CharsScientific)) == 0)
75 flags |= InputScalar_DefaultCharsFilter(data_type, format);
76 flags |= ImGuiInputTextFlags_AutoSelectAll;
78 bool value_changed =
false;
79 const float button_size = GetFrameHeight();
83 bool invisible_label = IsInvisibleLabel(label);
85 if (!invisible_label) {
86 AlignTextToFramePadding();
94 SetNextItemWidth(ImMax(
95 1.0f, CalcItemWidth() - (button_size + style.ItemInnerSpacing.x) * 2));
98 PushStyleVar(ImGuiStyleVar_ItemSpacing,
99 ImVec2{style.ItemSpacing.x, style.ItemSpacing.y});
100 PushStyleVar(ImGuiStyleVar_FramePadding,
101 ImVec2{style.FramePadding.x, style.FramePadding.y});
103 SetNextItemWidth(input_width);
104 if (InputText(
"", buf, IM_ARRAYSIZE(buf),
107 value_changed = DataTypeApplyFromText(buf, data_type, p_data, format);
108 IMGUI_TEST_ENGINE_ITEM_INFO(
109 g.LastItemData.ID, label,
110 g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Inputable);
113 if (IsItemHovered() && g.IO.MouseWheel != 0.0f) {
114 float scroll_amount = g.IO.MouseWheel;
115 float scroll_speed = 0.25f;
117 if (g.IO.KeyCtrl && p_step_fast)
118 scroll_amount *= *(
const float*)p_step_fast;
120 scroll_amount *= *(
const float*)p_step;
122 if (scroll_amount > 0.0f) {
123 scroll_amount *= scroll_speed;
124 DataTypeApplyOp(data_type,
'+', p_data, p_data, &scroll_amount);
125 value_changed =
true;
126 }
else if (scroll_amount < 0.0f) {
127 scroll_amount *= -scroll_speed;
128 DataTypeApplyOp(data_type,
'-', p_data, p_data, &scroll_amount);
129 value_changed =
true;
135 const ImVec2 backup_frame_padding = style.FramePadding;
136 style.FramePadding.x = style.FramePadding.y;
137 ImGuiButtonFlags button_flags = ImGuiButtonFlags_PressedOnClick;
138 if (flags & ImGuiInputTextFlags_ReadOnly)
140 SameLine(0, style.ItemInnerSpacing.x);
141 if (ButtonEx(
"-", ImVec2(button_size, button_size), button_flags)) {
142 DataTypeApplyOp(data_type,
'-', p_data, p_data,
143 g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step);
144 value_changed =
true;
146 SameLine(0, style.ItemInnerSpacing.x);
147 if (ButtonEx(
"+", ImVec2(button_size, button_size), button_flags)) {
148 DataTypeApplyOp(data_type,
'+', p_data, p_data,
149 g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step);
150 value_changed =
true;
153 if (flags & ImGuiInputTextFlags_ReadOnly)
156 style.FramePadding = backup_frame_padding;
160 ImGui::PopStyleVar(2);
163 MarkItemEdited(g.LastItemData.ID);
165 return value_changed;
170 void* p_data,
const void* p_step,
171 const void* p_step_fast,
const char* format,
173 ImGuiInputTextFlags flags,
174 bool no_step =
false) {
177 ImGuiWindow* window = ImGui::GetCurrentWindow();
178 if (window->SkipItems)
181 ImGuiContext& g = *GImGui;
182 ImGuiStyle& style = g.Style;
185 format = DataTypeGetInfo(data_type)->PrintFmt;
188 DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, p_data, format);
190 if (g.ActiveId == 0 && (flags & (ImGuiInputTextFlags_CharsDecimal |
191 ImGuiInputTextFlags_CharsHexadecimal |
192 ImGuiInputTextFlags_CharsScientific)) == 0)
193 flags |= InputScalar_DefaultCharsFilter(data_type, format);
194 flags |= ImGuiInputTextFlags_AutoSelectAll;
196 const float button_size = GetFrameHeight();
200 bool invisible_label = IsInvisibleLabel(label);
202 if (!invisible_label) {
203 AlignTextToFramePadding();
210 SetNextItemWidth(ImMax(
211 1.0f, CalcItemWidth() - (button_size + style.ItemInnerSpacing.x) * 2));
213 PushStyleVar(ImGuiStyleVar_ItemSpacing,
214 ImVec2{style.ItemSpacing.x, style.ItemSpacing.y});
215 PushStyleVar(ImGuiStyleVar_FramePadding,
216 ImVec2{style.FramePadding.x, style.FramePadding.y});
218 SetNextItemWidth(input_width);
219 if (InputText(
"", buf, IM_ARRAYSIZE(buf), flags)) {
220 if (DataTypeApplyFromText(buf, data_type, p_data, format)) {
227 if (IsItemDeactivatedAfterEdit()) {
231 IMGUI_TEST_ENGINE_ITEM_INFO(
232 g.LastItemData.ID, label,
233 g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Inputable);
236 if (IsItemHovered() && g.IO.MouseWheel != 0.0f) {
237 float scroll_amount = g.IO.MouseWheel;
238 float scroll_speed = 0.25f;
240 if (g.IO.KeyCtrl && p_step_fast)
241 scroll_amount *= *(
const float*)p_step_fast;
243 scroll_amount *= *(
const float*)p_step;
245 if (scroll_amount > 0.0f) {
246 scroll_amount *= scroll_speed;
247 DataTypeApplyOp(data_type,
'+', p_data, p_data, &scroll_amount);
250 }
else if (scroll_amount < 0.0f) {
251 scroll_amount *= -scroll_speed;
252 DataTypeApplyOp(data_type,
'-', p_data, p_data, &scroll_amount);
260 const ImVec2 backup_frame_padding = style.FramePadding;
261 style.FramePadding.x = style.FramePadding.y;
262 ImGuiButtonFlags button_flags = ImGuiButtonFlags_PressedOnClick;
263 if (flags & ImGuiInputTextFlags_ReadOnly)
265 SameLine(0, style.ItemInnerSpacing.x);
266 if (ButtonEx(
"-", ImVec2(button_size, button_size), button_flags)) {
267 DataTypeApplyOp(data_type,
'-', p_data, p_data,
268 g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step);
272 SameLine(0, style.ItemInnerSpacing.x);
273 if (ButtonEx(
"+", ImVec2(button_size, button_size), button_flags)) {
274 DataTypeApplyOp(data_type,
'+', p_data, p_data,
275 g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step);
280 if (flags & ImGuiInputTextFlags_ReadOnly)
283 style.FramePadding = backup_frame_padding;
287 ImGui::PopStyleVar(2);
290 MarkItemEdited(g.LastItemData.ID);
304 ImGuiDataType data_type = ImGuiDataType_COUNT;
306 alignas(uint64_t) std::array<uint8_t, kMaxDeferredScalarBytes> candidate = {};
308 uint64_t) std::array<uint8_t, kMaxDeferredScalarBytes> source_model = {};
309 int last_seen_frame = -1;
310 bool editing =
false;
311 bool commit_blocked =
false;
315 ImGuiContext* context =
nullptr;
317 std::unordered_map<ImGuiID, DeferredScalarEdit>
edits;
323 const void* data,
size_t data_size,
int frame,
328 std::memcpy(edit.
candidate.data(), data, data_size);
335 if (storage.context != context || frame < storage.last_frame) {
337 storage.edits.clear();
340 for (
auto it = storage.edits.begin(); it != storage.edits.end();) {
341 if (frame - it->second.last_seen_frame > 1) {
342 it = storage.edits.erase(it);
347 storage.last_frame = frame;
351 if (!ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) {
355 const ImGuiIO& io = ImGui::GetIO();
356 const bool platform_primary_held = io.KeyCtrl || io.KeySuper;
357 return ImGui::IsItemActive() || platform_primary_held;
366 const float wheel = ImGui::GetIO().MouseWheel;
371 using Numeric =
long long;
372 Numeric new_value =
static_cast<Numeric
>(*data) + (wheel > 0.0f ? 1 : -1);
373 new_value = std::clamp(new_value,
static_cast<Numeric
>(min_value),
374 static_cast<Numeric
>(max_value));
375 if (
static_cast<T
>(new_value) != *data) {
376 *data =
static_cast<T
>(new_value);
377 ImGui::ClearActiveID();
389 const char* format, ImGuiInputTextFlags flags,
391 ImGuiContext* context = ImGui::GetCurrentContext();
392 ImGuiWindow* window = context ? ImGui::GetCurrentWindow() :
nullptr;
393 if (!context || !window || window->SkipItems || !label || !data ||
394 data_type < 0 || data_type >= ImGuiDataType_COUNT) {
398 const ImGuiDataTypeInfo* data_type_info = ImGui::DataTypeGetInfo(data_type);
399 const size_t data_size = data_type_info->Size;
400 if (data_size == 0 || data_size > kMaxDeferredScalarBytes) {
404 const int frame = context->FrameCount;
405 PrepareDeferredScalarStorage(context, frame);
407 const ImGuiID item_id = ImGui::GetID(label);
408 auto [it, inserted] = g_deferred_scalar_storage.
edits.try_emplace(item_id);
409 DeferredScalarEdit& edit = it->second;
410 const bool target_changed =
411 !inserted && !(edit.target_identity == target_identity);
412 const bool reset_before_draw = inserted || edit.data_type != data_type ||
413 (target_changed && !edit.editing);
414 if (reset_before_draw) {
415 ResetDeferredScalarEdit(edit, data_type, data, data_size, frame,
417 }
else if (target_changed) {
420 edit.target_identity = target_identity;
421 edit.commit_blocked =
true;
422 }
else if (edit.editing &&
423 std::memcmp(edit.source_model.data(), data, data_size) != 0) {
426 edit.commit_blocked =
true;
427 }
else if (!edit.editing) {
428 ResetDeferredScalarEdit(edit, data_type, data, data_size, frame,
431 edit.last_seen_frame = frame;
433 const ImGuiInputTextFlags scalar_flags =
434 flags & ~ImGuiInputTextFlags_EnterReturnsTrue;
435 ImGui::InputScalar(label, data_type, edit.candidate.data(),
nullptr,
nullptr,
436 format, scalar_flags);
438 if (ImGui::IsItemDeactivatedAfterEdit()) {
441 if (reset_before_draw || edit.commit_blocked) {
442 ResetDeferredScalarEdit(edit, data_type, data, data_size, frame,
447 std::memcmp(data, edit.candidate.data(), data_size) != 0;
449 std::memcpy(data, edit.candidate.data(), data_size);
451 ResetDeferredScalarEdit(edit, data_type, data, data_size, frame,
456 if (ImGui::IsItemActive()) {
458 }
else if (edit.editing) {
460 ResetDeferredScalarEdit(edit, data_type, data, data_size, frame,
467 return ImGui::InputScalar(label, ImGuiDataType_U64, data, &
kStepOneHex,
469 ImGuiInputTextFlags_CharsHexadecimal);
472bool InputHex(
const char* label,
int* data,
int num_digits,
float input_width) {
473 const std::string format =
"%0" + std::to_string(num_digits) +
"X";
476 ImGuiInputTextFlags_CharsHexadecimal);
480 return ImGui::InputScalar(label, ImGuiDataType_U32, data, &
kStepOneHex,
482 ImGuiInputTextFlags_CharsHexadecimal);
489 input_width, ImGuiInputTextFlags_CharsHexadecimal, no_step);
490 bool wheel_changed = ApplyHexMouseWheel<uint16_t>(
491 data, 0u, std::numeric_limits<uint16_t>::max());
492 return changed || wheel_changed;
499 input_width, ImGuiInputTextFlags_CharsHexadecimal, no_step);
501 ApplyHexMouseWheel<int16_t>(data, std::numeric_limits<int16_t>::min(),
502 std::numeric_limits<int16_t>::max());
503 return changed || wheel_changed;
510 input_width, ImGuiInputTextFlags_CharsHexadecimal, no_step);
511 bool wheel_changed = ApplyHexMouseWheel<uint8_t>(
512 data, 0u, std::numeric_limits<uint8_t>::max());
513 return changed || wheel_changed;
517 float input_width,
bool no_step) {
520 input_width, ImGuiInputTextFlags_CharsHexadecimal, no_step);
521 if (changed && *data > max_value) {
524 bool wheel_changed = ApplyHexMouseWheel<uint8_t>(data, 0u, max_value);
525 return changed || wheel_changed;
530 float input_width,
bool no_step) {
533 input_width, ImGuiInputTextFlags_CharsHexadecimal, no_step);
535 hex_result.
changed = result.changed;
542 uint8_t max_value,
float input_width,
546 input_width, ImGuiInputTextFlags_CharsHexadecimal, no_step);
547 if (result.changed && *data > max_value) {
551 hex_result.
changed = result.changed;
558 float input_width,
bool no_step) {
561 input_width, ImGuiInputTextFlags_CharsHexadecimal, no_step);
563 hex_result.
changed = result.changed;
570 ImGui::TextWrapped(
"%s", text.c_str());
576 ImGui::PushID(text.c_str());
579 ImVec2 text_size = ImGui::CalcTextSize(text.c_str());
582 ImVec2 pos = ImGui::GetCursorScreenPos();
583 ImRect bb(pos, ImVec2(pos.x + text_size.x, pos.y + text_size.y));
586 const ImGuiID
id = ImGui::GetID(text.c_str());
588 if (ImGui::ItemAdd(bb,
id)) {
589 bool hovered = ImGui::IsItemHovered();
590 bool clicked = ImGui::IsItemClicked();
593 ImVec4 link_color = ImGui::GetStyleColorVec4(ImGuiCol_TextLink);
594 ImVec4 bg_color = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg);
597 float contrast_factor =
598 (bg_color.x + bg_color.y + bg_color.z) < 1.5f ? 1.0f : 0.3f;
603 color = ImVec4(std::min(1.0f, link_color.x + 0.3f),
604 std::min(1.0f, link_color.y + 0.3f),
605 std::min(1.0f, link_color.z + 0.3f), 1.0f);
608 color = ImVec4(std::max(contrast_factor, link_color.x),
609 std::max(contrast_factor, link_color.y),
610 std::max(contrast_factor, link_color.z), 1.0f);
613 ImGui::GetWindowDrawList()->AddText(
614 pos, ImGui::ColorConvertFloat4ToU32(color), text.c_str());
622 ImGui::Dummy(text_size);
629 ImGuiWindow* window = ImGui::GetCurrentWindow();
630 const ImVec2 lineStart = ImGui::GetCursorScreenPos();
631 const ImGuiStyle& style = ImGui::GetStyle();
632 float fullWidth = ImGui::GetContentRegionAvail().x;
633 float itemWidth = ImGui::CalcItemWidth() + style.ItemSpacing.x;
635 ImGui::CalcTextSize(title.data(), title.data() + title.size());
637 textRect.Min = ImGui::GetCursorScreenPos();
638 if (flags & ItemLabelFlag::Right)
639 textRect.Min.x = textRect.Min.x + itemWidth;
640 textRect.Max = textRect.Min;
641 textRect.Max.x += fullWidth - itemWidth;
642 textRect.Max.y += textSize.y;
644 ImGui::SetCursorScreenPos(textRect.Min);
646 ImGui::AlignTextToFramePadding();
649 textRect.Min.y += window->DC.CurrLineTextBaseOffset;
650 textRect.Max.y += window->DC.CurrLineTextBaseOffset;
652 ImGui::ItemSize(textRect);
654 textRect, window->GetID(title.data(), title.data() + title.size()))) {
655 ImGui::RenderTextEllipsis(ImGui::GetWindowDrawList(), textRect.Min,
656 textRect.Max, textRect.Max.x, title.data(),
657 title.data() + title.size(), &textSize);
659 if (textRect.GetWidth() < textSize.x && ImGui::IsItemHovered())
660 ImGui::SetTooltip(
"%.*s", (
int)title.size(), title.data());
662 if (flags & ItemLabelFlag::Left) {
664 auto other = ImVec2{0, textSize.y + window->DC.CurrLineTextBaseOffset};
665 result.x = textRect.Max.x - other.x;
666 result.y = textRect.Max.y - other.y;
667 ImGui::SetCursorScreenPos(result);
669 }
else if (flags & ItemLabelFlag::Right)
670 ImGui::SetCursorScreenPos(lineStart);
673bool ListBox(
const char* label,
int* current_item,
674 const std::vector<std::string>& items,
int height_in_items) {
675 std::vector<const char*> items_ptr;
676 items_ptr.reserve(items.size());
677 for (
const auto& item : items) {
678 items_ptr.push_back(item.c_str());
680 int items_count =
static_cast<int>(items.size());
681 return ImGui::ListBox(label, current_item, items_ptr.data(), items_count,
686 ImGui::PushID(label);
688 bool changed =
false;
691 changed |= ImGui::Checkbox(tr(
"Priority"), &tile_info->
over_);
692 changed |= ImGui::Checkbox(tr(
"Vertical Flip"), &tile_info->
vertical_mirror_);
700ImGuiID
GetID(
const std::string&
id) {
701 return ImGui::GetID(
id.c_str());
759 return ImGuiKey_Slash;
761 return ImGuiKey_Minus;
763 return ImGuiKey_COUNT;
780 ImGui::TableNextColumn();
797#if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
801 return system((
"open " + url).c_str()) == 0;
806 return system((
"xdg-open " + url).c_str()) == 0;
810 return system((
"start " + url).c_str()) == 0;
817 static bool open =
false;
819 if (ImGui::Button(tr(
"View Data"))) {
823 ImGui::Begin(label.c_str(), &open);
831 ImGui::PushID(label);
835 snprintf(buf,
sizeof(buf),
"%02X", *data);
837 ImGui::SetNextItemWidth(input_width);
838 bool changed = ImGui::InputText(
839 label, buf,
sizeof(buf),
840 ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_AutoSelectAll);
844 if (sscanf(buf,
"%X", &temp) == 1) {
845 *data =
static_cast<uint8_t
>(temp & 0xFF);
854 ImGui::PushID(label);
858 snprintf(buf,
sizeof(buf),
"%04X", *data);
860 ImGui::SetNextItemWidth(input_width);
861 bool changed = ImGui::InputText(
862 label, buf,
sizeof(buf),
863 ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_AutoSelectAll);
867 if (sscanf(buf,
"%X", &temp) == 1) {
868 *data =
static_cast<uint16_t
>(temp & 0xFFFF);
877 const char* format,
float wheel_step,
878 ImGuiSliderFlags flags) {
879 bool changed = ImGui::SliderFloat(label, v, v_min, v_max, format, flags);
883 if (IsValueWheelAdjustmentAllowedForCurrentItem()) {
884 float wheel = ImGui::GetIO().MouseWheel;
886 *v = std::clamp(*v + wheel * wheel_step, v_min, v_max);
894 const char* format,
int wheel_step,
895 ImGuiSliderFlags flags) {
896 bool changed = ImGui::SliderInt(label, v, v_min, v_max, format, flags);
898 if (IsValueWheelAdjustmentAllowedForCurrentItem()) {
899 float wheel = ImGui::GetIO().MouseWheel;
901 int delta =
static_cast<int>(wheel) * wheel_step;
902 *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