yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
input.cc
Go to the documentation of this file.
1#include "input.h"
2#include "util/i18n/tr.h"
3
4#include <algorithm>
5#include <array>
6#include <cstring>
7#include <functional>
8#include <limits>
9#include <string>
10#include <unordered_map>
11#include <variant>
12
13#include "absl/strings/string_view.h"
16#include "imgui/imgui.h"
17#include "imgui/imgui_internal.h"
18
19#ifdef __EMSCRIPTEN__
20#include <emscripten.h>
21#endif
22
23template <class... Ts>
24struct overloaded : Ts... {
25 using Ts::operator()...;
26};
27template <class... Ts>
28overloaded(Ts...) -> overloaded<Ts...>;
29
30namespace ImGui {
31
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;
40}
41
42// Helper: returns true if label is "invisible" (starts with "##")
43static inline bool IsInvisibleLabel(const char* label) {
44 return label && label[0] == '#' && label[1] == '#';
45}
46
47// Result struct for extended input functions
49 bool changed; // Any change occurred
50 bool immediate; // Change was from button/wheel (apply immediately)
51 bool text_changed; // Change was from text input
52 bool text_committed; // Text input was committed (deactivated after edit)
53};
54
55bool InputScalarLeft(const char* label, ImGuiDataType data_type, void* p_data,
56 const void* p_step, const void* p_step_fast,
57 const char* format, float input_width,
58 ImGuiInputTextFlags flags, bool no_step = false) {
59 InputScalarResult result = {};
60 // Call extended version and return simple bool
61 // (implementation below handles both)
62
63 ImGuiWindow* window = ImGui::GetCurrentWindow();
64 if (window->SkipItems)
65 return false;
66
67 ImGuiContext& g = *GImGui;
68 ImGuiStyle& style = g.Style;
69
70 if (format == NULL)
71 format = DataTypeGetInfo(data_type)->PrintFmt;
72
73 char buf[64];
74 DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, p_data, format);
75
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;
81
82 bool value_changed = false;
83 const float button_size = GetFrameHeight();
84
85 // Support invisible labels (##) by not rendering the label, but still using
86 // it for ID
87 bool invisible_label = IsInvisibleLabel(label);
88
89 if (!invisible_label) {
90 AlignTextToFramePadding();
91 Text("%s", label);
92 SameLine();
93 }
94
95 BeginGroup(); // The only purpose of the group here is to allow the caller
96 // to query item data e.g. IsItemActive()
97 PushID(label);
98 SetNextItemWidth(ImMax(
99 1.0f, CalcItemWidth() - (button_size + style.ItemInnerSpacing.x) * 2));
100
101 // Place the label on the left of the input field, unless invisible
102 PushStyleVar(ImGuiStyleVar_ItemSpacing,
103 ImVec2{style.ItemSpacing.x, style.ItemSpacing.y});
104 PushStyleVar(ImGuiStyleVar_FramePadding,
105 ImVec2{style.FramePadding.x, style.FramePadding.y});
106
107 SetNextItemWidth(input_width);
108 if (InputText("", buf, IM_ARRAYSIZE(buf),
109 flags)) // PushId(label) + "" gives us the expected ID
110 // from outside point of view
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);
115
116 // The mouse wheel is applied by the typed yaze::gui wrappers
117 // (ApplyHexMouseWheel), which know the value's type and range.
118
119 // Step buttons
120 if (!no_step) {
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)
125 BeginDisabled();
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;
131 }
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;
137 }
138
139 if (flags & ImGuiInputTextFlags_ReadOnly)
140 EndDisabled();
141
142 style.FramePadding = backup_frame_padding;
143 }
144 PopID();
145 EndGroup();
146 ImGui::PopStyleVar(2);
147
148 if (value_changed)
149 MarkItemEdited(g.LastItemData.ID);
150
151 return value_changed;
152}
153
154// Extended version that tracks change source
155InputScalarResult InputScalarLeftEx(const char* label, ImGuiDataType data_type,
156 void* p_data, const void* p_step,
157 const void* p_step_fast, const char* format,
158 float input_width,
159 ImGuiInputTextFlags flags,
160 bool no_step = false) {
161 InputScalarResult result = {false, false, false, false};
162
163 ImGuiWindow* window = ImGui::GetCurrentWindow();
164 if (window->SkipItems)
165 return result;
166
167 ImGuiContext& g = *GImGui;
168 ImGuiStyle& style = g.Style;
169
170 if (format == NULL)
171 format = DataTypeGetInfo(data_type)->PrintFmt;
172
173 char buf[64];
174 DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, p_data, format);
175
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;
181
182 const float button_size = GetFrameHeight();
183
184 // Support invisible labels (##) by not rendering the label, but still using
185 // it for ID
186 bool invisible_label = IsInvisibleLabel(label);
187
188 if (!invisible_label) {
189 AlignTextToFramePadding();
190 Text("%s", label);
191 SameLine();
192 }
193
194 BeginGroup();
195 PushID(label);
196 SetNextItemWidth(ImMax(
197 1.0f, CalcItemWidth() - (button_size + style.ItemInnerSpacing.x) * 2));
198
199 PushStyleVar(ImGuiStyleVar_ItemSpacing,
200 ImVec2{style.ItemSpacing.x, style.ItemSpacing.y});
201 PushStyleVar(ImGuiStyleVar_FramePadding,
202 ImVec2{style.FramePadding.x, style.FramePadding.y});
203
204 SetNextItemWidth(input_width);
205 if (InputText("", buf, IM_ARRAYSIZE(buf), flags)) {
206 if (DataTypeApplyFromText(buf, data_type, p_data, format)) {
207 result.text_changed = true;
208 result.changed = true;
209 }
210 }
211
212 // Check if text input was committed (deactivated after edit)
213 if (IsItemDeactivatedAfterEdit()) {
214 result.text_committed = true;
215 }
216
217 IMGUI_TEST_ENGINE_ITEM_INFO(
218 g.LastItemData.ID, label,
219 g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Inputable);
220
221 // The mouse wheel is applied by the typed yaze::gui wrappers
222 // (ApplyHexMouseWheel), which report it as an immediate change.
223
224 // Step buttons - immediate change
225 if (!no_step) {
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)
230 BeginDisabled();
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);
235 result.changed = true;
236 result.immediate = true;
237 }
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);
242 result.changed = true;
243 result.immediate = true;
244 }
245
246 if (flags & ImGuiInputTextFlags_ReadOnly)
247 EndDisabled();
248
249 style.FramePadding = backup_frame_padding;
250 }
251 PopID();
252 EndGroup();
253 ImGui::PopStyleVar(2);
254
255 if (result.changed)
256 MarkItemEdited(g.LastItemData.ID);
257
258 return result;
259}
260} // namespace ImGui
261
262namespace yaze {
263namespace gui {
264
265namespace {
266
267constexpr size_t kMaxDeferredScalarBytes = sizeof(uint64_t);
268
270 ImGuiDataType data_type = ImGuiDataType_COUNT;
272 alignas(uint64_t) std::array<uint8_t, kMaxDeferredScalarBytes> candidate = {};
273 alignas(
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;
278};
279
281 ImGuiContext* context = nullptr;
282 int last_frame = -1;
283 std::unordered_map<ImGuiID, DeferredScalarEdit> edits;
284};
285
287
288void ResetDeferredScalarEdit(DeferredScalarEdit& edit, ImGuiDataType data_type,
289 const void* data, size_t data_size, int frame,
290 InputScalarTargetIdentity target_identity) {
291 edit = {};
292 edit.data_type = data_type;
293 edit.target_identity = target_identity;
294 std::memcpy(edit.candidate.data(), data, data_size);
295 std::memcpy(edit.source_model.data(), data, data_size);
296 edit.last_seen_frame = frame;
297}
298
299void PrepareDeferredScalarStorage(ImGuiContext* context, int frame) {
300 auto& storage = g_deferred_scalar_storage;
301 if (storage.context != context || frame < storage.last_frame) {
302 storage.context = context;
303 storage.edits.clear();
304 }
305
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);
309 } else {
310 ++it;
311 }
312 }
313 storage.last_frame = frame;
314}
315
317 ImGuiHoveredFlags hover_flags = ImGuiHoveredFlags_AllowWhenBlockedByPopup) {
318 if (!ImGui::IsItemHovered(hover_flags)) {
319 return false;
320 }
321
322 const ImGuiIO& io = ImGui::GetIO();
323 const bool platform_primary_held = io.KeyCtrl || io.KeySuper;
324 return ImGui::IsItemActive() || platform_primary_held;
325}
326
327template <typename T>
329 T* data, T min_value, T max_value,
330 ImGuiHoveredFlags hover_flags = ImGuiHoveredFlags_AllowWhenBlockedByPopup) {
332 return false;
333 }
334
335 const float wheel = ImGui::GetIO().MouseWheel;
336 if (wheel == 0.0f) {
337 return false;
338 }
339
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();
347 return true;
348 }
349 return false;
350}
351
352} // namespace
353
354const int kStepOneHex = 0x01;
355const int kStepFastHex = 0x0F;
356// ImGuiDataType_U64 reads its step through an 8-byte pointer.
357const uint64_t kStepOneHex64 = 0x01;
358const uint64_t kStepFastHex64 = 0x0F;
359
360bool InputScalarDeferred(const char* label, ImGuiDataType data_type, void* data,
361 const char* format, ImGuiInputTextFlags flags,
362 InputScalarTargetIdentity target_identity) {
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) {
367 return false;
368 }
369
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) {
373 return false;
374 }
375
376 const int frame = context->FrameCount;
377 PrepareDeferredScalarStorage(context, frame);
378
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,
388 target_identity);
389 } else if (target_changed) {
390 // The same ImGui item now refers to a different semantic entity. Keep the
391 // active text buffer alive for ImGui, but make this edit non-committable.
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) {
396 // Keep feeding ImGui the same candidate until the item naturally
397 // deactivates, but remember that it is no longer safe to commit.
398 edit.commit_blocked = true;
399 } else if (!edit.editing) {
400 ResetDeferredScalarEdit(edit, data_type, data, data_size, frame,
401 target_identity);
402 }
403 edit.last_seen_frame = frame;
404
405 const ImGuiInputTextFlags scalar_flags =
406 flags & ~ImGuiInputTextFlags_EnterReturnsTrue;
407 ImGui::InputScalar(label, data_type, edit.candidate.data(), nullptr, nullptr,
408 format, scalar_flags);
409
410 if (ImGui::IsItemDeactivatedAfterEdit()) {
411 // A newly restored widget may share an ID with ImGui's one-frame
412 // deactivation backup. Never let that stale buffer become a fresh commit.
413 if (reset_before_draw || edit.commit_blocked) {
414 ResetDeferredScalarEdit(edit, data_type, data, data_size, frame,
415 target_identity);
416 return false;
417 }
418 const bool changed =
419 std::memcmp(data, edit.candidate.data(), data_size) != 0;
420 if (changed) {
421 std::memcpy(data, edit.candidate.data(), data_size);
422 }
423 ResetDeferredScalarEdit(edit, data_type, data, data_size, frame,
424 target_identity);
425 return changed;
426 }
427
428 if (ImGui::IsItemActive()) {
429 edit.editing = true;
430 } else if (edit.editing) {
431 // Escape or another non-commit deactivation discards the pending value.
432 ResetDeferredScalarEdit(edit, data_type, data, data_size, frame,
433 target_identity);
434 }
435 return false;
436}
437
438bool InputHex(const char* label, uint64_t* data) {
439 return ImGui::InputScalar(label, ImGuiDataType_U64, data, &kStepOneHex64,
440 &kStepFastHex64, "%06llX",
441 ImGuiInputTextFlags_CharsHexadecimal);
442}
443
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";
446 bool changed = ImGui::InputScalarLeft(
447 label, ImGuiDataType_S32, data, &kStepOneHex, &kStepFastHex,
448 format.c_str(), input_width, ImGuiInputTextFlags_CharsHexadecimal);
449 // Hex fields hold addresses and IDs; stepping below zero would wrap to
450 // FFFFFFFF.
451 bool wheel_changed =
452 ApplyHexMouseWheel<int>(data, 0, std::numeric_limits<int>::max());
453 return changed || wheel_changed;
454}
455
456bool InputHexShort(const char* label, uint32_t* data) {
457 return ImGui::InputScalar(label, ImGuiDataType_U32, data, &kStepOneHex,
458 &kStepFastHex, "%06X",
459 ImGuiInputTextFlags_CharsHexadecimal);
460}
461
462bool InputHexWord(const char* label, uint16_t* data, float input_width,
463 bool no_step) {
464 bool changed = ImGui::InputScalarLeft(
465 label, ImGuiDataType_U16, data, &kStepOneHex, &kStepFastHex, "%04X",
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;
470}
471
472bool InputHexWord(const char* label, int16_t* data, float input_width,
473 bool no_step) {
474 bool changed = ImGui::InputScalarLeft(
475 label, ImGuiDataType_S16, data, &kStepOneHex, &kStepFastHex, "%04X",
476 input_width, ImGuiInputTextFlags_CharsHexadecimal, no_step);
477 bool wheel_changed =
478 ApplyHexMouseWheel<int16_t>(data, std::numeric_limits<int16_t>::min(),
479 std::numeric_limits<int16_t>::max());
480 return changed || wheel_changed;
481}
482
483bool InputHexByte(const char* label, uint8_t* data, float input_width,
484 bool no_step) {
485 bool changed = ImGui::InputScalarLeft(
486 label, ImGuiDataType_U8, data, &kStepOneHex, &kStepFastHex, "%02X",
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;
491}
492
493bool InputHexByte(const char* label, uint8_t* data, uint8_t max_value,
494 float input_width, bool no_step) {
495 bool changed = ImGui::InputScalarLeft(
496 label, ImGuiDataType_U8, data, &kStepOneHex, &kStepFastHex, "%02X",
497 input_width, ImGuiInputTextFlags_CharsHexadecimal, no_step);
498 if (changed && *data > max_value) {
499 *data = max_value;
500 }
501 bool wheel_changed = ApplyHexMouseWheel<uint8_t>(data, 0u, max_value);
502 return changed || wheel_changed;
503}
504
505// Extended versions that properly track change source
506InputHexResult InputHexByteEx(const char* label, uint8_t* data,
507 float input_width, bool no_step) {
508 auto result = ImGui::InputScalarLeftEx(
509 label, ImGuiDataType_U8, data, &kStepOneHex, &kStepFastHex, "%02X",
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;
515 }
516 InputHexResult hex_result;
517 hex_result.changed = result.changed;
518 hex_result.immediate = result.immediate;
519 hex_result.text_committed = result.text_committed;
520 return hex_result;
521}
522
523InputHexResult InputHexByteEx(const char* label, uint8_t* data,
524 uint8_t max_value, float input_width,
525 bool no_step) {
526 auto result = ImGui::InputScalarLeftEx(
527 label, ImGuiDataType_U8, data, &kStepOneHex, &kStepFastHex, "%02X",
528 input_width, ImGuiInputTextFlags_CharsHexadecimal, no_step);
529 if (result.changed && *data > max_value) {
530 *data = max_value;
531 }
532 if (ApplyHexMouseWheel<uint8_t>(data, 0u, max_value,
533 ImGuiHoveredFlags_None)) {
534 result.changed = true;
535 result.immediate = true;
536 }
537 InputHexResult hex_result;
538 hex_result.changed = result.changed;
539 hex_result.immediate = result.immediate;
540 hex_result.text_committed = result.text_committed;
541 return hex_result;
542}
543
544InputHexResult InputHexWordEx(const char* label, uint16_t* data,
545 float input_width, bool no_step) {
546 auto result = ImGui::InputScalarLeftEx(
547 label, ImGuiDataType_U16, data, &kStepOneHex, &kStepFastHex, "%04X",
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;
554 }
555 InputHexResult hex_result;
556 hex_result.changed = result.changed;
557 hex_result.immediate = result.immediate;
558 hex_result.text_committed = result.text_committed;
559 return hex_result;
560}
561
562void Paragraph(const std::string& text) {
563 ImGui::TextWrapped("%s", text.c_str());
564}
565
566// TODO: Setup themes and text/clickable colors
567bool ClickableText(const std::string& text) {
568 ImGui::BeginGroup();
569 ImGui::PushID(text.c_str());
570
571 // Calculate text size
572 ImVec2 text_size = ImGui::CalcTextSize(text.c_str());
573
574 // Get cursor position for hover detection
575 ImVec2 pos = ImGui::GetCursorScreenPos();
576 ImRect bb(pos, ImVec2(pos.x + text_size.x, pos.y + text_size.y));
577
578 // Add item
579 const ImGuiID id = ImGui::GetID(text.c_str());
580 bool result = false;
581 if (ImGui::ItemAdd(bb, id)) {
582 bool hovered = ImGui::IsItemHovered();
583 bool clicked = ImGui::IsItemClicked();
584
585 // Render text with high-contrast appropriate color
586 ImVec4 link_color = ImGui::GetStyleColorVec4(ImGuiCol_TextLink);
587 ImVec4 bg_color = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg);
588
589 // Ensure good contrast against background
590 float contrast_factor =
591 (bg_color.x + bg_color.y + bg_color.z) < 1.5f ? 1.0f : 0.3f;
592
593 ImVec4 color;
594 if (hovered) {
595 // Brighter color on hover for better visibility
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);
599 } else {
600 // Ensure link color has good contrast
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);
604 }
605
606 ImGui::GetWindowDrawList()->AddText(
607 pos, ImGui::ColorConvertFloat4ToU32(color), text.c_str());
608
609 result = clicked;
610 }
611
612 ImGui::PopID();
613
614 // Advance cursor past the text
615 ImGui::Dummy(text_size);
616 ImGui::EndGroup();
617
618 return result;
619}
620
621void ItemLabel(absl::string_view title, ItemLabelFlags flags) {
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;
627 ImVec2 textSize =
628 ImGui::CalcTextSize(title.data(), title.data() + title.size());
629 ImRect textRect;
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;
636
637 ImGui::SetCursorScreenPos(textRect.Min);
638
639 ImGui::AlignTextToFramePadding();
640 // Adjust text rect manually because we render it directly into a drawlist
641 // instead of using public functions.
642 textRect.Min.y += window->DC.CurrLineTextBaseOffset;
643 textRect.Max.y += window->DC.CurrLineTextBaseOffset;
644
645 ImGui::ItemSize(textRect);
646 if (ImGui::ItemAdd(
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);
651
652 if (textRect.GetWidth() < textSize.x && ImGui::IsItemHovered())
653 ImGui::SetTooltip("%.*s", (int)title.size(), title.data());
654 }
655 if (flags & ItemLabelFlag::Left) {
656 ImVec2 result;
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);
661 ImGui::SameLine();
662 } else if (flags & ItemLabelFlag::Right)
663 ImGui::SetCursorScreenPos(lineStart);
664}
665
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());
672 }
673 int items_count = static_cast<int>(items.size());
674 return ImGui::ListBox(label, current_item, items_ptr.data(), items_count,
675 height_in_items);
676}
677
678bool InputTileInfo(const char* label, gfx::TileInfo* tile_info) {
679 ImGui::PushID(label);
680 ImGui::BeginGroup();
681 bool changed = false;
682 changed |= InputHexWord(label, &tile_info->id_);
683 changed |= InputHexByte("Palette", &tile_info->palette_);
684 changed |= ImGui::Checkbox(tr("Priority"), &tile_info->over_);
685 changed |= ImGui::Checkbox(tr("Vertical Flip"), &tile_info->vertical_mirror_);
686 changed |=
687 ImGui::Checkbox(tr("Horizontal Flip"), &tile_info->horizontal_mirror_);
688 ImGui::EndGroup();
689 ImGui::PopID();
690 return changed;
691}
692
693ImGuiID GetID(const std::string& id) {
694 return ImGui::GetID(id.c_str());
695}
696
697ImGuiKey MapKeyToImGuiKey(char key) {
698 switch (key) {
699 case 'A':
700 return ImGuiKey_A;
701 case 'B':
702 return ImGuiKey_B;
703 case 'C':
704 return ImGuiKey_C;
705 case 'D':
706 return ImGuiKey_D;
707 case 'E':
708 return ImGuiKey_E;
709 case 'F':
710 return ImGuiKey_F;
711 case 'G':
712 return ImGuiKey_G;
713 case 'H':
714 return ImGuiKey_H;
715 case 'I':
716 return ImGuiKey_I;
717 case 'J':
718 return ImGuiKey_J;
719 case 'K':
720 return ImGuiKey_K;
721 case 'L':
722 return ImGuiKey_L;
723 case 'M':
724 return ImGuiKey_M;
725 case 'N':
726 return ImGuiKey_N;
727 case 'O':
728 return ImGuiKey_O;
729 case 'P':
730 return ImGuiKey_P;
731 case 'Q':
732 return ImGuiKey_Q;
733 case 'R':
734 return ImGuiKey_R;
735 case 'S':
736 return ImGuiKey_S;
737 case 'T':
738 return ImGuiKey_T;
739 case 'U':
740 return ImGuiKey_U;
741 case 'V':
742 return ImGuiKey_V;
743 case 'W':
744 return ImGuiKey_W;
745 case 'X':
746 return ImGuiKey_X;
747 case 'Y':
748 return ImGuiKey_Y;
749 case 'Z':
750 return ImGuiKey_Z;
751 case '/':
752 return ImGuiKey_Slash;
753 case '-':
754 return ImGuiKey_Minus;
755 default:
756 return ImGuiKey_COUNT;
757 }
758}
759
760void AddTableColumn(Table& table, const std::string& label,
761 GuiElement element) {
762 table.column_labels.push_back(label);
763 table.column_contents.push_back(element);
764}
765
766void DrawTable(Table& params) {
767 if (ImGui::BeginTable(params.id, params.num_columns, params.flags,
768 params.size)) {
769 for (int i = 0; i < params.num_columns; ++i)
770 ImGui::TableSetupColumn(params.column_labels[i].c_str());
771
772 for (int i = 0; i < params.num_columns; ++i) {
773 ImGui::TableNextColumn();
774 switch (params.column_contents[i].index()) {
775 case 0:
776 std::get<0>(params.column_contents[i])();
777 break;
778 case 1:
779 ImGui::Text("%s", std::get<1>(params.column_contents[i]).c_str());
780 break;
781 }
782 }
783 ImGui::EndTable();
784 }
785}
786
787bool OpenUrl(const std::string& url) {
788 ImGuiContext* context = ImGui::GetCurrentContext();
789 if (context == nullptr || url.empty())
790 return false;
791
792#ifdef __EMSCRIPTEN__
793 // ImGui's default shell opener uses native process APIs, not the browser.
794 // Open synchronously from the click, and detach before navigating so the
795 // destination cannot access this editor through window.opener.
796 return EM_ASM_INT(
797 {
798 const opened = window.open('about:blank', '_blank');
799 if (!opened)
800 return 0;
801 opened.opener = null;
802 opened.location.href = UTF8ToString($0);
803 return 1;
804 },
805 url.c_str()) != 0;
806#else
807 ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
808 if (platform_io.Platform_OpenInShellFn == nullptr)
809 return false;
810
811 return platform_io.Platform_OpenInShellFn(context, url.c_str());
812#endif
813}
814
815void MemoryEditorPopup(const std::string& label, std::span<uint8_t> memory) {
816 static bool open = false;
817 static yaze::gui::MemoryEditorWidget editor;
818 if (ImGui::Button(tr("View Data"))) {
819 open = true;
820 }
821 if (open) {
822 ImGui::Begin(label.c_str(), &open);
823 editor.DrawContents(memory.data(), memory.size());
824 ImGui::End();
825 }
826}
827
828// Custom hex input functions that properly respect width
829bool InputHexByteCustom(const char* label, uint8_t* data, float input_width) {
830 ImGui::PushID(label);
831
832 // Create a simple hex input that respects width
833 char buf[8];
834 snprintf(buf, sizeof(buf), "%02X", *data);
835
836 ImGui::SetNextItemWidth(input_width);
837 bool changed = ImGui::InputText(
838 label, buf, sizeof(buf),
839 ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_AutoSelectAll);
840
841 if (changed) {
842 unsigned int temp;
843 if (sscanf(buf, "%X", &temp) == 1) {
844 *data = static_cast<uint8_t>(temp & 0xFF);
845 }
846 }
847
848 ImGui::PopID();
849 return changed;
850}
851
852bool InputHexWordCustom(const char* label, uint16_t* data, float input_width) {
853 ImGui::PushID(label);
854
855 // Create a simple hex input that respects width
856 char buf[8];
857 snprintf(buf, sizeof(buf), "%04X", *data);
858
859 ImGui::SetNextItemWidth(input_width);
860 bool changed = ImGui::InputText(
861 label, buf, sizeof(buf),
862 ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_AutoSelectAll);
863
864 if (changed) {
865 unsigned int temp;
866 if (sscanf(buf, "%X", &temp) == 1) {
867 *data = static_cast<uint16_t>(temp & 0xFFFF);
868 }
869 }
870
871 ImGui::PopID();
872 return changed;
873}
874
875bool SliderFloatWheel(const char* label, float* v, float v_min, float v_max,
876 const char* format, float wheel_step,
877 ImGuiSliderFlags flags) {
878 bool changed = ImGui::SliderFloat(label, v, v_min, v_max, format, flags);
879
880 // Require active focus or the platform primary modifier so hovering while
881 // scrolling a panel doesn't unexpectedly change the value.
882 if (IsValueWheelAdjustmentAllowedForCurrentItem()) {
883 float wheel = ImGui::GetIO().MouseWheel;
884 if (wheel != 0.0f) {
885 *v = std::clamp(*v + wheel * wheel_step, v_min, v_max);
886 changed = true;
887 }
888 }
889 return changed;
890}
891
892bool SliderIntWheel(const char* label, int* v, int v_min, int 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);
896
897 if (IsValueWheelAdjustmentAllowedForCurrentItem()) {
898 float wheel = ImGui::GetIO().MouseWheel;
899 if (wheel != 0.0f) {
900 int delta = static_cast<int>(wheel) * wheel_step;
901 *v = std::clamp(*v + delta, v_min, v_max);
902 changed = true;
903 }
904 }
905 return changed;
906}
907
908} // namespace gui
909} // namespace yaze
SNES 16-bit tile metadata container.
Definition snes_tile.h:52
overloaded(Ts...) -> overloaded< Ts... >
Definition input.cc:30
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)
Definition input.cc:155
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)
Definition input.cc:55
bool IsValueWheelAdjustmentAllowedForCurrentItem(ImGuiHoveredFlags hover_flags=ImGuiHoveredFlags_AllowWhenBlockedByPopup)
Definition input.cc:316
thread_local DeferredScalarStorage g_deferred_scalar_storage
Definition input.cc:286
bool ApplyHexMouseWheel(T *data, T min_value, T max_value, ImGuiHoveredFlags hover_flags=ImGuiHoveredFlags_AllowWhenBlockedByPopup)
Definition input.cc:328
void PrepareDeferredScalarStorage(ImGuiContext *context, int frame)
Definition input.cc:299
void ResetDeferredScalarEdit(DeferredScalarEdit &edit, ImGuiDataType data_type, const void *data, size_t data_size, int frame, InputScalarTargetIdentity target_identity)
Definition input.cc:288
bool InputHexByteCustom(const char *label, uint8_t *data, float input_width)
Definition input.cc:829
bool ClickableText(const std::string &text)
Definition input.cc:567
bool SliderIntWheel(const char *label, int *v, int v_min, int v_max, const char *format, int wheel_step, ImGuiSliderFlags flags)
Definition input.cc:892
ImGuiID GetID(const std::string &id)
Definition input.cc:693
void Paragraph(const std::string &text)
Definition input.cc:562
bool InputHexWord(const char *label, uint16_t *data, float input_width, bool no_step)
Definition input.cc:462
bool ListBox(const char *label, int *current_item, const std::vector< std::string > &items, int height_in_items)
Definition input.cc:666
bool SliderFloatWheel(const char *label, float *v, float v_min, float v_max, const char *format, float wheel_step, ImGuiSliderFlags flags)
Definition input.cc:875
bool InputHexShort(const char *label, uint32_t *data)
Definition input.cc:456
void AddTableColumn(Table &table, const std::string &label, GuiElement element)
Definition input.cc:760
enum ItemLabelFlag { Left=1u<< 0u, Right=1u<< 1u, Default=Left, } ItemLabelFlags
Definition input.h:121
void MemoryEditorPopup(const std::string &label, std::span< uint8_t > memory)
Definition input.cc:815
const int kStepOneHex
Definition input.cc:354
void DrawTable(Table &params)
Definition input.cc:766
bool OpenUrl(const std::string &url)
Definition input.cc:787
bool InputHexWordCustom(const char *label, uint16_t *data, float input_width)
Definition input.cc:852
bool InputHex(const char *label, uint64_t *data)
Definition input.cc:438
const uint64_t kStepFastHex64
Definition input.cc:358
bool InputTileInfo(const char *label, gfx::TileInfo *tile_info)
Definition input.cc:678
bool InputScalarDeferred(const char *label, ImGuiDataType data_type, void *data, const char *format, ImGuiInputTextFlags flags, InputScalarTargetIdentity target_identity)
Definition input.cc:360
std::variant< std::function< void()>, std::string > GuiElement
Definition input.h:133
InputHexResult InputHexByteEx(const char *label, uint8_t *data, float input_width, bool no_step)
Definition input.cc:506
const int kStepFastHex
Definition input.cc:355
ImGuiKey MapKeyToImGuiKey(char key)
Definition input.cc:697
bool InputHexByte(const char *label, uint8_t *data, float input_width, bool no_step)
Definition input.cc:483
void ItemLabel(absl::string_view title, ItemLabelFlags flags)
Definition input.cc:621
const uint64_t kStepOneHex64
Definition input.cc:357
InputHexResult InputHexWordEx(const char *label, uint16_t *data, float input_width, bool no_step)
Definition input.cc:544
Draw a scalar editor that commits the model value atomically.
Definition input.h:61
void DrawContents(void *mem_data_void, size_t mem_size, size_t base_display_addr=0x0000)
std::vector< std::string > column_labels
Definition input.h:140
std::vector< GuiElement > column_contents
Definition input.h:141
ImVec2 size
Definition input.h:139
const char * id
Definition input.h:136
ImGuiTableFlags flags
Definition input.h:138
std::array< uint8_t, kMaxDeferredScalarBytes > candidate
Definition input.cc:272
std::array< uint8_t, kMaxDeferredScalarBytes > source_model
Definition input.cc:274
std::unordered_map< ImGuiID, DeferredScalarEdit > edits
Definition input.cc:283