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 // Mouse wheel support
117 if (IsItemHovered() && g.IO.MouseWheel != 0.0f) {
118 float scroll_amount = g.IO.MouseWheel;
119 float scroll_speed = 0.25f; // Adjust the scroll speed as needed
120
121 if (g.IO.KeyCtrl && p_step_fast)
122 scroll_amount *= *(const float*)p_step_fast;
123 else
124 scroll_amount *= *(const float*)p_step;
125
126 if (scroll_amount > 0.0f) {
127 scroll_amount *= scroll_speed; // Adjust the scroll speed as needed
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; // Adjust the scroll speed as needed
132 DataTypeApplyOp(data_type, '-', p_data, p_data, &scroll_amount);
133 value_changed = true;
134 }
135 }
136
137 // Step buttons
138 if (!no_step) {
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)
143 BeginDisabled();
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;
149 }
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;
155 }
156
157 if (flags & ImGuiInputTextFlags_ReadOnly)
158 EndDisabled();
159
160 style.FramePadding = backup_frame_padding;
161 }
162 PopID();
163 EndGroup();
164 ImGui::PopStyleVar(2);
165
166 if (value_changed)
167 MarkItemEdited(g.LastItemData.ID);
168
169 return value_changed;
170}
171
172// Extended version that tracks change source
173InputScalarResult InputScalarLeftEx(const char* label, ImGuiDataType data_type,
174 void* p_data, const void* p_step,
175 const void* p_step_fast, const char* format,
176 float input_width,
177 ImGuiInputTextFlags flags,
178 bool no_step = false) {
179 InputScalarResult result = {false, false, false, false};
180
181 ImGuiWindow* window = ImGui::GetCurrentWindow();
182 if (window->SkipItems)
183 return result;
184
185 ImGuiContext& g = *GImGui;
186 ImGuiStyle& style = g.Style;
187
188 if (format == NULL)
189 format = DataTypeGetInfo(data_type)->PrintFmt;
190
191 char buf[64];
192 DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, p_data, format);
193
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;
199
200 const float button_size = GetFrameHeight();
201
202 // Support invisible labels (##) by not rendering the label, but still using
203 // it for ID
204 bool invisible_label = IsInvisibleLabel(label);
205
206 if (!invisible_label) {
207 AlignTextToFramePadding();
208 Text("%s", label);
209 SameLine();
210 }
211
212 BeginGroup();
213 PushID(label);
214 SetNextItemWidth(ImMax(
215 1.0f, CalcItemWidth() - (button_size + style.ItemInnerSpacing.x) * 2));
216
217 PushStyleVar(ImGuiStyleVar_ItemSpacing,
218 ImVec2{style.ItemSpacing.x, style.ItemSpacing.y});
219 PushStyleVar(ImGuiStyleVar_FramePadding,
220 ImVec2{style.FramePadding.x, style.FramePadding.y});
221
222 SetNextItemWidth(input_width);
223 if (InputText("", buf, IM_ARRAYSIZE(buf), flags)) {
224 if (DataTypeApplyFromText(buf, data_type, p_data, format)) {
225 result.text_changed = true;
226 result.changed = true;
227 }
228 }
229
230 // Check if text input was committed (deactivated after edit)
231 if (IsItemDeactivatedAfterEdit()) {
232 result.text_committed = true;
233 }
234
235 IMGUI_TEST_ENGINE_ITEM_INFO(
236 g.LastItemData.ID, label,
237 g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Inputable);
238
239 // Mouse wheel support - immediate change
240 if (IsItemHovered() && g.IO.MouseWheel != 0.0f) {
241 float scroll_amount = g.IO.MouseWheel;
242 float scroll_speed = 0.25f;
243
244 if (g.IO.KeyCtrl && p_step_fast)
245 scroll_amount *= *(const float*)p_step_fast;
246 else
247 scroll_amount *= *(const float*)p_step;
248
249 if (scroll_amount > 0.0f) {
250 scroll_amount *= scroll_speed;
251 DataTypeApplyOp(data_type, '+', p_data, p_data, &scroll_amount);
252 result.changed = true;
253 result.immediate = true;
254 } else if (scroll_amount < 0.0f) {
255 scroll_amount *= -scroll_speed;
256 DataTypeApplyOp(data_type, '-', p_data, p_data, &scroll_amount);
257 result.changed = true;
258 result.immediate = true;
259 }
260 }
261
262 // Step buttons - immediate change
263 if (!no_step) {
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)
268 BeginDisabled();
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);
273 result.changed = true;
274 result.immediate = true;
275 }
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);
280 result.changed = true;
281 result.immediate = true;
282 }
283
284 if (flags & ImGuiInputTextFlags_ReadOnly)
285 EndDisabled();
286
287 style.FramePadding = backup_frame_padding;
288 }
289 PopID();
290 EndGroup();
291 ImGui::PopStyleVar(2);
292
293 if (result.changed)
294 MarkItemEdited(g.LastItemData.ID);
295
296 return result;
297}
298} // namespace ImGui
299
300namespace yaze {
301namespace gui {
302
303namespace {
304
305constexpr size_t kMaxDeferredScalarBytes = sizeof(uint64_t);
306
308 ImGuiDataType data_type = ImGuiDataType_COUNT;
310 alignas(uint64_t) std::array<uint8_t, kMaxDeferredScalarBytes> candidate = {};
311 alignas(
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;
316};
317
319 ImGuiContext* context = nullptr;
320 int last_frame = -1;
321 std::unordered_map<ImGuiID, DeferredScalarEdit> edits;
322};
323
325
326void ResetDeferredScalarEdit(DeferredScalarEdit& edit, ImGuiDataType data_type,
327 const void* data, size_t data_size, int frame,
328 InputScalarTargetIdentity target_identity) {
329 edit = {};
330 edit.data_type = data_type;
331 edit.target_identity = target_identity;
332 std::memcpy(edit.candidate.data(), data, data_size);
333 std::memcpy(edit.source_model.data(), data, data_size);
334 edit.last_seen_frame = frame;
335}
336
337void PrepareDeferredScalarStorage(ImGuiContext* context, int frame) {
338 auto& storage = g_deferred_scalar_storage;
339 if (storage.context != context || frame < storage.last_frame) {
340 storage.context = context;
341 storage.edits.clear();
342 }
343
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);
347 } else {
348 ++it;
349 }
350 }
351 storage.last_frame = frame;
352}
353
355 if (!ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) {
356 return false;
357 }
358
359 const ImGuiIO& io = ImGui::GetIO();
360 const bool platform_primary_held = io.KeyCtrl || io.KeySuper;
361 return ImGui::IsItemActive() || platform_primary_held;
362}
363
364template <typename T>
365bool ApplyHexMouseWheel(T* data, T min_value, T max_value) {
367 return false;
368 }
369
370 const float wheel = ImGui::GetIO().MouseWheel;
371 if (wheel == 0.0f) {
372 return false;
373 }
374
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();
382 return true;
383 }
384 return false;
385}
386
387} // namespace
388
389const int kStepOneHex = 0x01;
390const int kStepFastHex = 0x0F;
391
392bool InputScalarDeferred(const char* label, ImGuiDataType data_type, void* data,
393 const char* format, ImGuiInputTextFlags flags,
394 InputScalarTargetIdentity target_identity) {
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) {
399 return false;
400 }
401
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) {
405 return false;
406 }
407
408 const int frame = context->FrameCount;
409 PrepareDeferredScalarStorage(context, frame);
410
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,
420 target_identity);
421 } else if (target_changed) {
422 // The same ImGui item now refers to a different semantic entity. Keep the
423 // active text buffer alive for ImGui, but make this edit non-committable.
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) {
428 // Keep feeding ImGui the same candidate until the item naturally
429 // deactivates, but remember that it is no longer safe to commit.
430 edit.commit_blocked = true;
431 } else if (!edit.editing) {
432 ResetDeferredScalarEdit(edit, data_type, data, data_size, frame,
433 target_identity);
434 }
435 edit.last_seen_frame = frame;
436
437 const ImGuiInputTextFlags scalar_flags =
438 flags & ~ImGuiInputTextFlags_EnterReturnsTrue;
439 ImGui::InputScalar(label, data_type, edit.candidate.data(), nullptr, nullptr,
440 format, scalar_flags);
441
442 if (ImGui::IsItemDeactivatedAfterEdit()) {
443 // A newly restored widget may share an ID with ImGui's one-frame
444 // deactivation backup. Never let that stale buffer become a fresh commit.
445 if (reset_before_draw || edit.commit_blocked) {
446 ResetDeferredScalarEdit(edit, data_type, data, data_size, frame,
447 target_identity);
448 return false;
449 }
450 const bool changed =
451 std::memcmp(data, edit.candidate.data(), data_size) != 0;
452 if (changed) {
453 std::memcpy(data, edit.candidate.data(), data_size);
454 }
455 ResetDeferredScalarEdit(edit, data_type, data, data_size, frame,
456 target_identity);
457 return changed;
458 }
459
460 if (ImGui::IsItemActive()) {
461 edit.editing = true;
462 } else if (edit.editing) {
463 // Escape or another non-commit deactivation discards the pending value.
464 ResetDeferredScalarEdit(edit, data_type, data, data_size, frame,
465 target_identity);
466 }
467 return false;
468}
469
470bool InputHex(const char* label, uint64_t* data) {
471 return ImGui::InputScalar(label, ImGuiDataType_U64, data, &kStepOneHex,
472 &kStepFastHex, "%06X",
473 ImGuiInputTextFlags_CharsHexadecimal);
474}
475
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";
478 return ImGui::InputScalarLeft(label, ImGuiDataType_S32, data, &kStepOneHex,
479 &kStepFastHex, format.c_str(), input_width,
480 ImGuiInputTextFlags_CharsHexadecimal);
481}
482
483bool InputHexShort(const char* label, uint32_t* data) {
484 return ImGui::InputScalar(label, ImGuiDataType_U32, data, &kStepOneHex,
485 &kStepFastHex, "%06X",
486 ImGuiInputTextFlags_CharsHexadecimal);
487}
488
489bool InputHexWord(const char* label, uint16_t* data, float input_width,
490 bool no_step) {
491 bool changed = ImGui::InputScalarLeft(
492 label, ImGuiDataType_U16, data, &kStepOneHex, &kStepFastHex, "%04X",
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;
497}
498
499bool InputHexWord(const char* label, int16_t* data, float input_width,
500 bool no_step) {
501 bool changed = ImGui::InputScalarLeft(
502 label, ImGuiDataType_S16, data, &kStepOneHex, &kStepFastHex, "%04X",
503 input_width, ImGuiInputTextFlags_CharsHexadecimal, no_step);
504 bool wheel_changed =
505 ApplyHexMouseWheel<int16_t>(data, std::numeric_limits<int16_t>::min(),
506 std::numeric_limits<int16_t>::max());
507 return changed || wheel_changed;
508}
509
510bool InputHexByte(const char* label, uint8_t* data, float input_width,
511 bool no_step) {
512 bool changed = ImGui::InputScalarLeft(
513 label, ImGuiDataType_U8, data, &kStepOneHex, &kStepFastHex, "%02X",
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;
518}
519
520bool InputHexByte(const char* label, uint8_t* data, uint8_t max_value,
521 float input_width, bool no_step) {
522 bool changed = ImGui::InputScalarLeft(
523 label, ImGuiDataType_U8, data, &kStepOneHex, &kStepFastHex, "%02X",
524 input_width, ImGuiInputTextFlags_CharsHexadecimal, no_step);
525 if (changed && *data > max_value) {
526 *data = max_value;
527 }
528 bool wheel_changed = ApplyHexMouseWheel<uint8_t>(data, 0u, max_value);
529 return changed || wheel_changed;
530}
531
532// Extended versions that properly track change source
533InputHexResult InputHexByteEx(const char* label, uint8_t* data,
534 float input_width, bool no_step) {
535 auto result = ImGui::InputScalarLeftEx(
536 label, ImGuiDataType_U8, data, &kStepOneHex, &kStepFastHex, "%02X",
537 input_width, ImGuiInputTextFlags_CharsHexadecimal, no_step);
538 InputHexResult hex_result;
539 hex_result.changed = result.changed;
540 hex_result.immediate = result.immediate;
541 hex_result.text_committed = result.text_committed;
542 return hex_result;
543}
544
545InputHexResult InputHexByteEx(const char* label, uint8_t* data,
546 uint8_t max_value, float input_width,
547 bool no_step) {
548 auto result = ImGui::InputScalarLeftEx(
549 label, ImGuiDataType_U8, data, &kStepOneHex, &kStepFastHex, "%02X",
550 input_width, ImGuiInputTextFlags_CharsHexadecimal, no_step);
551 if (result.changed && *data > max_value) {
552 *data = max_value;
553 }
554 InputHexResult hex_result;
555 hex_result.changed = result.changed;
556 hex_result.immediate = result.immediate;
557 hex_result.text_committed = result.text_committed;
558 return hex_result;
559}
560
561InputHexResult InputHexWordEx(const char* label, uint16_t* data,
562 float input_width, bool no_step) {
563 auto result = ImGui::InputScalarLeftEx(
564 label, ImGuiDataType_U16, data, &kStepOneHex, &kStepFastHex, "%04X",
565 input_width, ImGuiInputTextFlags_CharsHexadecimal, no_step);
566 InputHexResult hex_result;
567 hex_result.changed = result.changed;
568 hex_result.immediate = result.immediate;
569 hex_result.text_committed = result.text_committed;
570 return hex_result;
571}
572
573void Paragraph(const std::string& text) {
574 ImGui::TextWrapped("%s", text.c_str());
575}
576
577// TODO: Setup themes and text/clickable colors
578bool ClickableText(const std::string& text) {
579 ImGui::BeginGroup();
580 ImGui::PushID(text.c_str());
581
582 // Calculate text size
583 ImVec2 text_size = ImGui::CalcTextSize(text.c_str());
584
585 // Get cursor position for hover detection
586 ImVec2 pos = ImGui::GetCursorScreenPos();
587 ImRect bb(pos, ImVec2(pos.x + text_size.x, pos.y + text_size.y));
588
589 // Add item
590 const ImGuiID id = ImGui::GetID(text.c_str());
591 bool result = false;
592 if (ImGui::ItemAdd(bb, id)) {
593 bool hovered = ImGui::IsItemHovered();
594 bool clicked = ImGui::IsItemClicked();
595
596 // Render text with high-contrast appropriate color
597 ImVec4 link_color = ImGui::GetStyleColorVec4(ImGuiCol_TextLink);
598 ImVec4 bg_color = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg);
599
600 // Ensure good contrast against background
601 float contrast_factor =
602 (bg_color.x + bg_color.y + bg_color.z) < 1.5f ? 1.0f : 0.3f;
603
604 ImVec4 color;
605 if (hovered) {
606 // Brighter color on hover for better visibility
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);
610 } else {
611 // Ensure link color has good contrast
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);
615 }
616
617 ImGui::GetWindowDrawList()->AddText(
618 pos, ImGui::ColorConvertFloat4ToU32(color), text.c_str());
619
620 result = clicked;
621 }
622
623 ImGui::PopID();
624
625 // Advance cursor past the text
626 ImGui::Dummy(text_size);
627 ImGui::EndGroup();
628
629 return result;
630}
631
632void ItemLabel(absl::string_view title, ItemLabelFlags flags) {
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;
638 ImVec2 textSize =
639 ImGui::CalcTextSize(title.data(), title.data() + title.size());
640 ImRect textRect;
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;
647
648 ImGui::SetCursorScreenPos(textRect.Min);
649
650 ImGui::AlignTextToFramePadding();
651 // Adjust text rect manually because we render it directly into a drawlist
652 // instead of using public functions.
653 textRect.Min.y += window->DC.CurrLineTextBaseOffset;
654 textRect.Max.y += window->DC.CurrLineTextBaseOffset;
655
656 ImGui::ItemSize(textRect);
657 if (ImGui::ItemAdd(
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);
662
663 if (textRect.GetWidth() < textSize.x && ImGui::IsItemHovered())
664 ImGui::SetTooltip("%.*s", (int)title.size(), title.data());
665 }
666 if (flags & ItemLabelFlag::Left) {
667 ImVec2 result;
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);
672 ImGui::SameLine();
673 } else if (flags & ItemLabelFlag::Right)
674 ImGui::SetCursorScreenPos(lineStart);
675}
676
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());
683 }
684 int items_count = static_cast<int>(items.size());
685 return ImGui::ListBox(label, current_item, items_ptr.data(), items_count,
686 height_in_items);
687}
688
689bool InputTileInfo(const char* label, gfx::TileInfo* tile_info) {
690 ImGui::PushID(label);
691 ImGui::BeginGroup();
692 bool changed = false;
693 changed |= InputHexWord(label, &tile_info->id_);
694 changed |= InputHexByte("Palette", &tile_info->palette_);
695 changed |= ImGui::Checkbox(tr("Priority"), &tile_info->over_);
696 changed |= ImGui::Checkbox(tr("Vertical Flip"), &tile_info->vertical_mirror_);
697 changed |=
698 ImGui::Checkbox(tr("Horizontal Flip"), &tile_info->horizontal_mirror_);
699 ImGui::EndGroup();
700 ImGui::PopID();
701 return changed;
702}
703
704ImGuiID GetID(const std::string& id) {
705 return ImGui::GetID(id.c_str());
706}
707
708ImGuiKey MapKeyToImGuiKey(char key) {
709 switch (key) {
710 case 'A':
711 return ImGuiKey_A;
712 case 'B':
713 return ImGuiKey_B;
714 case 'C':
715 return ImGuiKey_C;
716 case 'D':
717 return ImGuiKey_D;
718 case 'E':
719 return ImGuiKey_E;
720 case 'F':
721 return ImGuiKey_F;
722 case 'G':
723 return ImGuiKey_G;
724 case 'H':
725 return ImGuiKey_H;
726 case 'I':
727 return ImGuiKey_I;
728 case 'J':
729 return ImGuiKey_J;
730 case 'K':
731 return ImGuiKey_K;
732 case 'L':
733 return ImGuiKey_L;
734 case 'M':
735 return ImGuiKey_M;
736 case 'N':
737 return ImGuiKey_N;
738 case 'O':
739 return ImGuiKey_O;
740 case 'P':
741 return ImGuiKey_P;
742 case 'Q':
743 return ImGuiKey_Q;
744 case 'R':
745 return ImGuiKey_R;
746 case 'S':
747 return ImGuiKey_S;
748 case 'T':
749 return ImGuiKey_T;
750 case 'U':
751 return ImGuiKey_U;
752 case 'V':
753 return ImGuiKey_V;
754 case 'W':
755 return ImGuiKey_W;
756 case 'X':
757 return ImGuiKey_X;
758 case 'Y':
759 return ImGuiKey_Y;
760 case 'Z':
761 return ImGuiKey_Z;
762 case '/':
763 return ImGuiKey_Slash;
764 case '-':
765 return ImGuiKey_Minus;
766 default:
767 return ImGuiKey_COUNT;
768 }
769}
770
771void AddTableColumn(Table& table, const std::string& label,
772 GuiElement element) {
773 table.column_labels.push_back(label);
774 table.column_contents.push_back(element);
775}
776
777void DrawTable(Table& params) {
778 if (ImGui::BeginTable(params.id, params.num_columns, params.flags,
779 params.size)) {
780 for (int i = 0; i < params.num_columns; ++i)
781 ImGui::TableSetupColumn(params.column_labels[i].c_str());
782
783 for (int i = 0; i < params.num_columns; ++i) {
784 ImGui::TableNextColumn();
785 switch (params.column_contents[i].index()) {
786 case 0:
787 std::get<0>(params.column_contents[i])();
788 break;
789 case 1:
790 ImGui::Text("%s", std::get<1>(params.column_contents[i]).c_str());
791 break;
792 }
793 }
794 ImGui::EndTable();
795 }
796}
797
798bool OpenUrl(const std::string& url) {
799 ImGuiContext* context = ImGui::GetCurrentContext();
800 if (context == nullptr || url.empty())
801 return false;
802
803#ifdef __EMSCRIPTEN__
804 // ImGui's default shell opener uses native process APIs, not the browser.
805 // Open synchronously from the click, and detach before navigating so the
806 // destination cannot access this editor through window.opener.
807 return EM_ASM_INT(
808 {
809 const opened = window.open('about:blank', '_blank');
810 if (!opened)
811 return 0;
812 opened.opener = null;
813 opened.location.href = UTF8ToString($0);
814 return 1;
815 },
816 url.c_str()) != 0;
817#else
818 ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
819 if (platform_io.Platform_OpenInShellFn == nullptr)
820 return false;
821
822 return platform_io.Platform_OpenInShellFn(context, url.c_str());
823#endif
824}
825
826void MemoryEditorPopup(const std::string& label, std::span<uint8_t> memory) {
827 static bool open = false;
828 static yaze::gui::MemoryEditorWidget editor;
829 if (ImGui::Button(tr("View Data"))) {
830 open = true;
831 }
832 if (open) {
833 ImGui::Begin(label.c_str(), &open);
834 editor.DrawContents(memory.data(), memory.size());
835 ImGui::End();
836 }
837}
838
839// Custom hex input functions that properly respect width
840bool InputHexByteCustom(const char* label, uint8_t* data, float input_width) {
841 ImGui::PushID(label);
842
843 // Create a simple hex input that respects width
844 char buf[8];
845 snprintf(buf, sizeof(buf), "%02X", *data);
846
847 ImGui::SetNextItemWidth(input_width);
848 bool changed = ImGui::InputText(
849 label, buf, sizeof(buf),
850 ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_AutoSelectAll);
851
852 if (changed) {
853 unsigned int temp;
854 if (sscanf(buf, "%X", &temp) == 1) {
855 *data = static_cast<uint8_t>(temp & 0xFF);
856 }
857 }
858
859 ImGui::PopID();
860 return changed;
861}
862
863bool InputHexWordCustom(const char* label, uint16_t* data, float input_width) {
864 ImGui::PushID(label);
865
866 // Create a simple hex input that respects width
867 char buf[8];
868 snprintf(buf, sizeof(buf), "%04X", *data);
869
870 ImGui::SetNextItemWidth(input_width);
871 bool changed = ImGui::InputText(
872 label, buf, sizeof(buf),
873 ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_AutoSelectAll);
874
875 if (changed) {
876 unsigned int temp;
877 if (sscanf(buf, "%X", &temp) == 1) {
878 *data = static_cast<uint16_t>(temp & 0xFFFF);
879 }
880 }
881
882 ImGui::PopID();
883 return changed;
884}
885
886bool SliderFloatWheel(const char* label, float* v, float v_min, float v_max,
887 const char* format, float wheel_step,
888 ImGuiSliderFlags flags) {
889 bool changed = ImGui::SliderFloat(label, v, v_min, v_max, format, flags);
890
891 // Require active focus or the platform primary modifier so hovering while
892 // scrolling a panel doesn't unexpectedly change the value.
893 if (IsValueWheelAdjustmentAllowedForCurrentItem()) {
894 float wheel = ImGui::GetIO().MouseWheel;
895 if (wheel != 0.0f) {
896 *v = std::clamp(*v + wheel * wheel_step, v_min, v_max);
897 changed = true;
898 }
899 }
900 return changed;
901}
902
903bool SliderIntWheel(const char* label, int* v, int v_min, int 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);
907
908 if (IsValueWheelAdjustmentAllowedForCurrentItem()) {
909 float wheel = ImGui::GetIO().MouseWheel;
910 if (wheel != 0.0f) {
911 int delta = static_cast<int>(wheel) * wheel_step;
912 *v = std::clamp(*v + delta, v_min, v_max);
913 changed = true;
914 }
915 }
916 return changed;
917}
918
919} // namespace gui
920} // 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:173
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 ApplyHexMouseWheel(T *data, T min_value, T max_value)
Definition input.cc:365
thread_local DeferredScalarStorage g_deferred_scalar_storage
Definition input.cc:324
void PrepareDeferredScalarStorage(ImGuiContext *context, int frame)
Definition input.cc:337
void ResetDeferredScalarEdit(DeferredScalarEdit &edit, ImGuiDataType data_type, const void *data, size_t data_size, int frame, InputScalarTargetIdentity target_identity)
Definition input.cc:326
bool InputHexByteCustom(const char *label, uint8_t *data, float input_width)
Definition input.cc:840
bool ClickableText(const std::string &text)
Definition input.cc:578
bool SliderIntWheel(const char *label, int *v, int v_min, int v_max, const char *format, int wheel_step, ImGuiSliderFlags flags)
Definition input.cc:903
void Paragraph(const std::string &text)
Definition input.cc:573
void ItemLabel(absl::string_view title, ItemLabelFlags flags)
Definition input.cc:632
bool InputHexWord(const char *label, uint16_t *data, float input_width, bool no_step)
Definition input.cc:489
bool ListBox(const char *label, int *current_item, const std::vector< std::string > &items, int height_in_items)
Definition input.cc:677
bool SliderFloatWheel(const char *label, float *v, float v_min, float v_max, const char *format, float wheel_step, ImGuiSliderFlags flags)
Definition input.cc:886
bool InputHexShort(const char *label, uint32_t *data)
Definition input.cc:483
void AddTableColumn(Table &table, const std::string &label, GuiElement element)
Definition input.cc:771
enum ItemLabelFlag { Left=1u<< 0u, Right=1u<< 1u, Default=Left, } ItemLabelFlags
Definition input.h:116
void MemoryEditorPopup(const std::string &label, std::span< uint8_t > memory)
Definition input.cc:826
const int kStepOneHex
Definition input.cc:389
void DrawTable(Table &params)
Definition input.cc:777
bool OpenUrl(const std::string &url)
Definition input.cc:798
bool InputHexWordCustom(const char *label, uint16_t *data, float input_width)
Definition input.cc:863
bool InputHex(const char *label, uint64_t *data)
Definition input.cc:470
bool InputTileInfo(const char *label, gfx::TileInfo *tile_info)
Definition input.cc:689
bool InputScalarDeferred(const char *label, ImGuiDataType data_type, void *data, const char *format, ImGuiInputTextFlags flags, InputScalarTargetIdentity target_identity)
Definition input.cc:392
std::variant< std::function< void()>, std::string > GuiElement
Definition input.h:128
InputHexResult InputHexByteEx(const char *label, uint8_t *data, float input_width, bool no_step)
Definition input.cc:533
const int kStepFastHex
Definition input.cc:390
ImGuiID GetID(const std::string &id)
Definition input.cc:704
ImGuiKey MapKeyToImGuiKey(char key)
Definition input.cc:708
bool InputHexByte(const char *label, uint8_t *data, float input_width, bool no_step)
Definition input.cc:510
InputHexResult InputHexWordEx(const char *label, uint16_t *data, float input_width, bool no_step)
Definition input.cc:561
Draw a scalar editor that commits the model value atomically.
Definition input.h:56
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:135
std::vector< GuiElement > column_contents
Definition input.h:136
ImVec2 size
Definition input.h:134
const char * id
Definition input.h:131
ImGuiTableFlags flags
Definition input.h:133
std::array< uint8_t, kMaxDeferredScalarBytes > candidate
Definition input.cc:310
std::array< uint8_t, kMaxDeferredScalarBytes > source_model
Definition input.cc:312
std::unordered_map< ImGuiID, DeferredScalarEdit > edits
Definition input.cc:321