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
19template <class... Ts>
20struct overloaded : Ts... {
21 using Ts::operator()...;
22};
23template <class... Ts>
24overloaded(Ts...) -> overloaded<Ts...>;
25
26namespace ImGui {
27
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;
36}
37
38// Helper: returns true if label is "invisible" (starts with "##")
39static inline bool IsInvisibleLabel(const char* label) {
40 return label && label[0] == '#' && label[1] == '#';
41}
42
43// Result struct for extended input functions
45 bool changed; // Any change occurred
46 bool immediate; // Change was from button/wheel (apply immediately)
47 bool text_changed; // Change was from text input
48 bool text_committed; // Text input was committed (deactivated after edit)
49};
50
51bool InputScalarLeft(const char* label, ImGuiDataType data_type, void* p_data,
52 const void* p_step, const void* p_step_fast,
53 const char* format, float input_width,
54 ImGuiInputTextFlags flags, bool no_step = false) {
55 InputScalarResult result = {};
56 // Call extended version and return simple bool
57 // (implementation below handles both)
58
59 ImGuiWindow* window = ImGui::GetCurrentWindow();
60 if (window->SkipItems)
61 return false;
62
63 ImGuiContext& g = *GImGui;
64 ImGuiStyle& style = g.Style;
65
66 if (format == NULL)
67 format = DataTypeGetInfo(data_type)->PrintFmt;
68
69 char buf[64];
70 DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, p_data, format);
71
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;
77
78 bool value_changed = false;
79 const float button_size = GetFrameHeight();
80
81 // Support invisible labels (##) by not rendering the label, but still using
82 // it for ID
83 bool invisible_label = IsInvisibleLabel(label);
84
85 if (!invisible_label) {
86 AlignTextToFramePadding();
87 Text("%s", label);
88 SameLine();
89 }
90
91 BeginGroup(); // The only purpose of the group here is to allow the caller
92 // to query item data e.g. IsItemActive()
93 PushID(label);
94 SetNextItemWidth(ImMax(
95 1.0f, CalcItemWidth() - (button_size + style.ItemInnerSpacing.x) * 2));
96
97 // Place the label on the left of the input field, unless invisible
98 PushStyleVar(ImGuiStyleVar_ItemSpacing,
99 ImVec2{style.ItemSpacing.x, style.ItemSpacing.y});
100 PushStyleVar(ImGuiStyleVar_FramePadding,
101 ImVec2{style.FramePadding.x, style.FramePadding.y});
102
103 SetNextItemWidth(input_width);
104 if (InputText("", buf, IM_ARRAYSIZE(buf),
105 flags)) // PushId(label) + "" gives us the expected ID
106 // from outside point of view
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);
111
112 // Mouse wheel support
113 if (IsItemHovered() && g.IO.MouseWheel != 0.0f) {
114 float scroll_amount = g.IO.MouseWheel;
115 float scroll_speed = 0.25f; // Adjust the scroll speed as needed
116
117 if (g.IO.KeyCtrl && p_step_fast)
118 scroll_amount *= *(const float*)p_step_fast;
119 else
120 scroll_amount *= *(const float*)p_step;
121
122 if (scroll_amount > 0.0f) {
123 scroll_amount *= scroll_speed; // Adjust the scroll speed as needed
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; // Adjust the scroll speed as needed
128 DataTypeApplyOp(data_type, '-', p_data, p_data, &scroll_amount);
129 value_changed = true;
130 }
131 }
132
133 // Step buttons
134 if (!no_step) {
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)
139 BeginDisabled();
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;
145 }
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;
151 }
152
153 if (flags & ImGuiInputTextFlags_ReadOnly)
154 EndDisabled();
155
156 style.FramePadding = backup_frame_padding;
157 }
158 PopID();
159 EndGroup();
160 ImGui::PopStyleVar(2);
161
162 if (value_changed)
163 MarkItemEdited(g.LastItemData.ID);
164
165 return value_changed;
166}
167
168// Extended version that tracks change source
169InputScalarResult InputScalarLeftEx(const char* label, ImGuiDataType data_type,
170 void* p_data, const void* p_step,
171 const void* p_step_fast, const char* format,
172 float input_width,
173 ImGuiInputTextFlags flags,
174 bool no_step = false) {
175 InputScalarResult result = {false, false, false, false};
176
177 ImGuiWindow* window = ImGui::GetCurrentWindow();
178 if (window->SkipItems)
179 return result;
180
181 ImGuiContext& g = *GImGui;
182 ImGuiStyle& style = g.Style;
183
184 if (format == NULL)
185 format = DataTypeGetInfo(data_type)->PrintFmt;
186
187 char buf[64];
188 DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, p_data, format);
189
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;
195
196 const float button_size = GetFrameHeight();
197
198 // Support invisible labels (##) by not rendering the label, but still using
199 // it for ID
200 bool invisible_label = IsInvisibleLabel(label);
201
202 if (!invisible_label) {
203 AlignTextToFramePadding();
204 Text("%s", label);
205 SameLine();
206 }
207
208 BeginGroup();
209 PushID(label);
210 SetNextItemWidth(ImMax(
211 1.0f, CalcItemWidth() - (button_size + style.ItemInnerSpacing.x) * 2));
212
213 PushStyleVar(ImGuiStyleVar_ItemSpacing,
214 ImVec2{style.ItemSpacing.x, style.ItemSpacing.y});
215 PushStyleVar(ImGuiStyleVar_FramePadding,
216 ImVec2{style.FramePadding.x, style.FramePadding.y});
217
218 SetNextItemWidth(input_width);
219 if (InputText("", buf, IM_ARRAYSIZE(buf), flags)) {
220 if (DataTypeApplyFromText(buf, data_type, p_data, format)) {
221 result.text_changed = true;
222 result.changed = true;
223 }
224 }
225
226 // Check if text input was committed (deactivated after edit)
227 if (IsItemDeactivatedAfterEdit()) {
228 result.text_committed = true;
229 }
230
231 IMGUI_TEST_ENGINE_ITEM_INFO(
232 g.LastItemData.ID, label,
233 g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Inputable);
234
235 // Mouse wheel support - immediate change
236 if (IsItemHovered() && g.IO.MouseWheel != 0.0f) {
237 float scroll_amount = g.IO.MouseWheel;
238 float scroll_speed = 0.25f;
239
240 if (g.IO.KeyCtrl && p_step_fast)
241 scroll_amount *= *(const float*)p_step_fast;
242 else
243 scroll_amount *= *(const float*)p_step;
244
245 if (scroll_amount > 0.0f) {
246 scroll_amount *= scroll_speed;
247 DataTypeApplyOp(data_type, '+', p_data, p_data, &scroll_amount);
248 result.changed = true;
249 result.immediate = true;
250 } else if (scroll_amount < 0.0f) {
251 scroll_amount *= -scroll_speed;
252 DataTypeApplyOp(data_type, '-', p_data, p_data, &scroll_amount);
253 result.changed = true;
254 result.immediate = true;
255 }
256 }
257
258 // Step buttons - immediate change
259 if (!no_step) {
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)
264 BeginDisabled();
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);
269 result.changed = true;
270 result.immediate = true;
271 }
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);
276 result.changed = true;
277 result.immediate = true;
278 }
279
280 if (flags & ImGuiInputTextFlags_ReadOnly)
281 EndDisabled();
282
283 style.FramePadding = backup_frame_padding;
284 }
285 PopID();
286 EndGroup();
287 ImGui::PopStyleVar(2);
288
289 if (result.changed)
290 MarkItemEdited(g.LastItemData.ID);
291
292 return result;
293}
294} // namespace ImGui
295
296namespace yaze {
297namespace gui {
298
299namespace {
300
301constexpr size_t kMaxDeferredScalarBytes = sizeof(uint64_t);
302
304 ImGuiDataType data_type = ImGuiDataType_COUNT;
306 alignas(uint64_t) std::array<uint8_t, kMaxDeferredScalarBytes> candidate = {};
307 alignas(
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;
312};
313
315 ImGuiContext* context = nullptr;
316 int last_frame = -1;
317 std::unordered_map<ImGuiID, DeferredScalarEdit> edits;
318};
319
321
322void ResetDeferredScalarEdit(DeferredScalarEdit& edit, ImGuiDataType data_type,
323 const void* data, size_t data_size, int frame,
324 InputScalarTargetIdentity target_identity) {
325 edit = {};
326 edit.data_type = data_type;
327 edit.target_identity = target_identity;
328 std::memcpy(edit.candidate.data(), data, data_size);
329 std::memcpy(edit.source_model.data(), data, data_size);
330 edit.last_seen_frame = frame;
331}
332
333void PrepareDeferredScalarStorage(ImGuiContext* context, int frame) {
334 auto& storage = g_deferred_scalar_storage;
335 if (storage.context != context || frame < storage.last_frame) {
336 storage.context = context;
337 storage.edits.clear();
338 }
339
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);
343 } else {
344 ++it;
345 }
346 }
347 storage.last_frame = frame;
348}
349
351 if (!ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) {
352 return false;
353 }
354
355 const ImGuiIO& io = ImGui::GetIO();
356 const bool platform_primary_held = io.KeyCtrl || io.KeySuper;
357 return ImGui::IsItemActive() || platform_primary_held;
358}
359
360template <typename T>
361bool ApplyHexMouseWheel(T* data, T min_value, T max_value) {
363 return false;
364 }
365
366 const float wheel = ImGui::GetIO().MouseWheel;
367 if (wheel == 0.0f) {
368 return false;
369 }
370
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();
378 return true;
379 }
380 return false;
381}
382
383} // namespace
384
385const int kStepOneHex = 0x01;
386const int kStepFastHex = 0x0F;
387
388bool InputScalarDeferred(const char* label, ImGuiDataType data_type, void* data,
389 const char* format, ImGuiInputTextFlags flags,
390 InputScalarTargetIdentity target_identity) {
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) {
395 return false;
396 }
397
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) {
401 return false;
402 }
403
404 const int frame = context->FrameCount;
405 PrepareDeferredScalarStorage(context, frame);
406
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,
416 target_identity);
417 } else if (target_changed) {
418 // The same ImGui item now refers to a different semantic entity. Keep the
419 // active text buffer alive for ImGui, but make this edit non-committable.
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) {
424 // Keep feeding ImGui the same candidate until the item naturally
425 // deactivates, but remember that it is no longer safe to commit.
426 edit.commit_blocked = true;
427 } else if (!edit.editing) {
428 ResetDeferredScalarEdit(edit, data_type, data, data_size, frame,
429 target_identity);
430 }
431 edit.last_seen_frame = frame;
432
433 const ImGuiInputTextFlags scalar_flags =
434 flags & ~ImGuiInputTextFlags_EnterReturnsTrue;
435 ImGui::InputScalar(label, data_type, edit.candidate.data(), nullptr, nullptr,
436 format, scalar_flags);
437
438 if (ImGui::IsItemDeactivatedAfterEdit()) {
439 // A newly restored widget may share an ID with ImGui's one-frame
440 // deactivation backup. Never let that stale buffer become a fresh commit.
441 if (reset_before_draw || edit.commit_blocked) {
442 ResetDeferredScalarEdit(edit, data_type, data, data_size, frame,
443 target_identity);
444 return false;
445 }
446 const bool changed =
447 std::memcmp(data, edit.candidate.data(), data_size) != 0;
448 if (changed) {
449 std::memcpy(data, edit.candidate.data(), data_size);
450 }
451 ResetDeferredScalarEdit(edit, data_type, data, data_size, frame,
452 target_identity);
453 return changed;
454 }
455
456 if (ImGui::IsItemActive()) {
457 edit.editing = true;
458 } else if (edit.editing) {
459 // Escape or another non-commit deactivation discards the pending value.
460 ResetDeferredScalarEdit(edit, data_type, data, data_size, frame,
461 target_identity);
462 }
463 return false;
464}
465
466bool InputHex(const char* label, uint64_t* data) {
467 return ImGui::InputScalar(label, ImGuiDataType_U64, data, &kStepOneHex,
468 &kStepFastHex, "%06X",
469 ImGuiInputTextFlags_CharsHexadecimal);
470}
471
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";
474 return ImGui::InputScalarLeft(label, ImGuiDataType_S32, data, &kStepOneHex,
475 &kStepFastHex, format.c_str(), input_width,
476 ImGuiInputTextFlags_CharsHexadecimal);
477}
478
479bool InputHexShort(const char* label, uint32_t* data) {
480 return ImGui::InputScalar(label, ImGuiDataType_U32, data, &kStepOneHex,
481 &kStepFastHex, "%06X",
482 ImGuiInputTextFlags_CharsHexadecimal);
483}
484
485bool InputHexWord(const char* label, uint16_t* data, float input_width,
486 bool no_step) {
487 bool changed = ImGui::InputScalarLeft(
488 label, ImGuiDataType_U16, data, &kStepOneHex, &kStepFastHex, "%04X",
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;
493}
494
495bool InputHexWord(const char* label, int16_t* data, float input_width,
496 bool no_step) {
497 bool changed = ImGui::InputScalarLeft(
498 label, ImGuiDataType_S16, data, &kStepOneHex, &kStepFastHex, "%04X",
499 input_width, ImGuiInputTextFlags_CharsHexadecimal, no_step);
500 bool wheel_changed =
501 ApplyHexMouseWheel<int16_t>(data, std::numeric_limits<int16_t>::min(),
502 std::numeric_limits<int16_t>::max());
503 return changed || wheel_changed;
504}
505
506bool InputHexByte(const char* label, uint8_t* data, float input_width,
507 bool no_step) {
508 bool changed = ImGui::InputScalarLeft(
509 label, ImGuiDataType_U8, data, &kStepOneHex, &kStepFastHex, "%02X",
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;
514}
515
516bool InputHexByte(const char* label, uint8_t* data, uint8_t max_value,
517 float input_width, bool no_step) {
518 bool changed = ImGui::InputScalarLeft(
519 label, ImGuiDataType_U8, data, &kStepOneHex, &kStepFastHex, "%02X",
520 input_width, ImGuiInputTextFlags_CharsHexadecimal, no_step);
521 if (changed && *data > max_value) {
522 *data = max_value;
523 }
524 bool wheel_changed = ApplyHexMouseWheel<uint8_t>(data, 0u, max_value);
525 return changed || wheel_changed;
526}
527
528// Extended versions that properly track change source
529InputHexResult InputHexByteEx(const char* label, uint8_t* data,
530 float input_width, bool no_step) {
531 auto result = ImGui::InputScalarLeftEx(
532 label, ImGuiDataType_U8, data, &kStepOneHex, &kStepFastHex, "%02X",
533 input_width, ImGuiInputTextFlags_CharsHexadecimal, no_step);
534 InputHexResult hex_result;
535 hex_result.changed = result.changed;
536 hex_result.immediate = result.immediate;
537 hex_result.text_committed = result.text_committed;
538 return hex_result;
539}
540
541InputHexResult InputHexByteEx(const char* label, uint8_t* data,
542 uint8_t max_value, float input_width,
543 bool no_step) {
544 auto result = ImGui::InputScalarLeftEx(
545 label, ImGuiDataType_U8, data, &kStepOneHex, &kStepFastHex, "%02X",
546 input_width, ImGuiInputTextFlags_CharsHexadecimal, no_step);
547 if (result.changed && *data > max_value) {
548 *data = max_value;
549 }
550 InputHexResult hex_result;
551 hex_result.changed = result.changed;
552 hex_result.immediate = result.immediate;
553 hex_result.text_committed = result.text_committed;
554 return hex_result;
555}
556
557InputHexResult InputHexWordEx(const char* label, uint16_t* data,
558 float input_width, bool no_step) {
559 auto result = ImGui::InputScalarLeftEx(
560 label, ImGuiDataType_U16, data, &kStepOneHex, &kStepFastHex, "%04X",
561 input_width, ImGuiInputTextFlags_CharsHexadecimal, no_step);
562 InputHexResult hex_result;
563 hex_result.changed = result.changed;
564 hex_result.immediate = result.immediate;
565 hex_result.text_committed = result.text_committed;
566 return hex_result;
567}
568
569void Paragraph(const std::string& text) {
570 ImGui::TextWrapped("%s", text.c_str());
571}
572
573// TODO: Setup themes and text/clickable colors
574bool ClickableText(const std::string& text) {
575 ImGui::BeginGroup();
576 ImGui::PushID(text.c_str());
577
578 // Calculate text size
579 ImVec2 text_size = ImGui::CalcTextSize(text.c_str());
580
581 // Get cursor position for hover detection
582 ImVec2 pos = ImGui::GetCursorScreenPos();
583 ImRect bb(pos, ImVec2(pos.x + text_size.x, pos.y + text_size.y));
584
585 // Add item
586 const ImGuiID id = ImGui::GetID(text.c_str());
587 bool result = false;
588 if (ImGui::ItemAdd(bb, id)) {
589 bool hovered = ImGui::IsItemHovered();
590 bool clicked = ImGui::IsItemClicked();
591
592 // Render text with high-contrast appropriate color
593 ImVec4 link_color = ImGui::GetStyleColorVec4(ImGuiCol_TextLink);
594 ImVec4 bg_color = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg);
595
596 // Ensure good contrast against background
597 float contrast_factor =
598 (bg_color.x + bg_color.y + bg_color.z) < 1.5f ? 1.0f : 0.3f;
599
600 ImVec4 color;
601 if (hovered) {
602 // Brighter color on hover for better visibility
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);
606 } else {
607 // Ensure link color has good contrast
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);
611 }
612
613 ImGui::GetWindowDrawList()->AddText(
614 pos, ImGui::ColorConvertFloat4ToU32(color), text.c_str());
615
616 result = clicked;
617 }
618
619 ImGui::PopID();
620
621 // Advance cursor past the text
622 ImGui::Dummy(text_size);
623 ImGui::EndGroup();
624
625 return result;
626}
627
628void ItemLabel(absl::string_view title, ItemLabelFlags flags) {
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;
634 ImVec2 textSize =
635 ImGui::CalcTextSize(title.data(), title.data() + title.size());
636 ImRect textRect;
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;
643
644 ImGui::SetCursorScreenPos(textRect.Min);
645
646 ImGui::AlignTextToFramePadding();
647 // Adjust text rect manually because we render it directly into a drawlist
648 // instead of using public functions.
649 textRect.Min.y += window->DC.CurrLineTextBaseOffset;
650 textRect.Max.y += window->DC.CurrLineTextBaseOffset;
651
652 ImGui::ItemSize(textRect);
653 if (ImGui::ItemAdd(
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);
658
659 if (textRect.GetWidth() < textSize.x && ImGui::IsItemHovered())
660 ImGui::SetTooltip("%.*s", (int)title.size(), title.data());
661 }
662 if (flags & ItemLabelFlag::Left) {
663 ImVec2 result;
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);
668 ImGui::SameLine();
669 } else if (flags & ItemLabelFlag::Right)
670 ImGui::SetCursorScreenPos(lineStart);
671}
672
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());
679 }
680 int items_count = static_cast<int>(items.size());
681 return ImGui::ListBox(label, current_item, items_ptr.data(), items_count,
682 height_in_items);
683}
684
685bool InputTileInfo(const char* label, gfx::TileInfo* tile_info) {
686 ImGui::PushID(label);
687 ImGui::BeginGroup();
688 bool changed = false;
689 changed |= InputHexWord(label, &tile_info->id_);
690 changed |= InputHexByte("Palette", &tile_info->palette_);
691 changed |= ImGui::Checkbox(tr("Priority"), &tile_info->over_);
692 changed |= ImGui::Checkbox(tr("Vertical Flip"), &tile_info->vertical_mirror_);
693 changed |=
694 ImGui::Checkbox(tr("Horizontal Flip"), &tile_info->horizontal_mirror_);
695 ImGui::EndGroup();
696 ImGui::PopID();
697 return changed;
698}
699
700ImGuiID GetID(const std::string& id) {
701 return ImGui::GetID(id.c_str());
702}
703
704ImGuiKey MapKeyToImGuiKey(char key) {
705 switch (key) {
706 case 'A':
707 return ImGuiKey_A;
708 case 'B':
709 return ImGuiKey_B;
710 case 'C':
711 return ImGuiKey_C;
712 case 'D':
713 return ImGuiKey_D;
714 case 'E':
715 return ImGuiKey_E;
716 case 'F':
717 return ImGuiKey_F;
718 case 'G':
719 return ImGuiKey_G;
720 case 'H':
721 return ImGuiKey_H;
722 case 'I':
723 return ImGuiKey_I;
724 case 'J':
725 return ImGuiKey_J;
726 case 'K':
727 return ImGuiKey_K;
728 case 'L':
729 return ImGuiKey_L;
730 case 'M':
731 return ImGuiKey_M;
732 case 'N':
733 return ImGuiKey_N;
734 case 'O':
735 return ImGuiKey_O;
736 case 'P':
737 return ImGuiKey_P;
738 case 'Q':
739 return ImGuiKey_Q;
740 case 'R':
741 return ImGuiKey_R;
742 case 'S':
743 return ImGuiKey_S;
744 case 'T':
745 return ImGuiKey_T;
746 case 'U':
747 return ImGuiKey_U;
748 case 'V':
749 return ImGuiKey_V;
750 case 'W':
751 return ImGuiKey_W;
752 case 'X':
753 return ImGuiKey_X;
754 case 'Y':
755 return ImGuiKey_Y;
756 case 'Z':
757 return ImGuiKey_Z;
758 case '/':
759 return ImGuiKey_Slash;
760 case '-':
761 return ImGuiKey_Minus;
762 default:
763 return ImGuiKey_COUNT;
764 }
765}
766
767void AddTableColumn(Table& table, const std::string& label,
768 GuiElement element) {
769 table.column_labels.push_back(label);
770 table.column_contents.push_back(element);
771}
772
773void DrawTable(Table& params) {
774 if (ImGui::BeginTable(params.id, params.num_columns, params.flags,
775 params.size)) {
776 for (int i = 0; i < params.num_columns; ++i)
777 ImGui::TableSetupColumn(params.column_labels[i].c_str());
778
779 for (int i = 0; i < params.num_columns; ++i) {
780 ImGui::TableNextColumn();
781 switch (params.column_contents[i].index()) {
782 case 0:
783 std::get<0>(params.column_contents[i])();
784 break;
785 case 1:
786 ImGui::Text("%s", std::get<1>(params.column_contents[i]).c_str());
787 break;
788 }
789 }
790 ImGui::EndTable();
791 }
792}
793
794bool OpenUrl(const std::string& url) {
795 // if iOS
796#ifdef __APPLE__
797#if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
798 // no system call on iOS
799 return false;
800#else
801 return system(("open " + url).c_str()) == 0;
802#endif
803#endif
804
805#ifdef __linux__
806 return system(("xdg-open " + url).c_str()) == 0;
807#endif
808
809#ifdef __windows__
810 return system(("start " + url).c_str()) == 0;
811#endif
812
813 return false;
814}
815
816void MemoryEditorPopup(const std::string& label, std::span<uint8_t> memory) {
817 static bool open = false;
818 static yaze::gui::MemoryEditorWidget editor;
819 if (ImGui::Button(tr("View Data"))) {
820 open = true;
821 }
822 if (open) {
823 ImGui::Begin(label.c_str(), &open);
824 editor.DrawContents(memory.data(), memory.size());
825 ImGui::End();
826 }
827}
828
829// Custom hex input functions that properly respect width
830bool InputHexByteCustom(const char* label, uint8_t* data, float input_width) {
831 ImGui::PushID(label);
832
833 // Create a simple hex input that respects width
834 char buf[8];
835 snprintf(buf, sizeof(buf), "%02X", *data);
836
837 ImGui::SetNextItemWidth(input_width);
838 bool changed = ImGui::InputText(
839 label, buf, sizeof(buf),
840 ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_AutoSelectAll);
841
842 if (changed) {
843 unsigned int temp;
844 if (sscanf(buf, "%X", &temp) == 1) {
845 *data = static_cast<uint8_t>(temp & 0xFF);
846 }
847 }
848
849 ImGui::PopID();
850 return changed;
851}
852
853bool InputHexWordCustom(const char* label, uint16_t* data, float input_width) {
854 ImGui::PushID(label);
855
856 // Create a simple hex input that respects width
857 char buf[8];
858 snprintf(buf, sizeof(buf), "%04X", *data);
859
860 ImGui::SetNextItemWidth(input_width);
861 bool changed = ImGui::InputText(
862 label, buf, sizeof(buf),
863 ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_AutoSelectAll);
864
865 if (changed) {
866 unsigned int temp;
867 if (sscanf(buf, "%X", &temp) == 1) {
868 *data = static_cast<uint16_t>(temp & 0xFFFF);
869 }
870 }
871
872 ImGui::PopID();
873 return changed;
874}
875
876bool SliderFloatWheel(const char* label, float* v, float v_min, float v_max,
877 const char* format, float wheel_step,
878 ImGuiSliderFlags flags) {
879 bool changed = ImGui::SliderFloat(label, v, v_min, v_max, format, flags);
880
881 // Require active focus or the platform primary modifier so hovering while
882 // scrolling a panel doesn't unexpectedly change the value.
883 if (IsValueWheelAdjustmentAllowedForCurrentItem()) {
884 float wheel = ImGui::GetIO().MouseWheel;
885 if (wheel != 0.0f) {
886 *v = std::clamp(*v + wheel * wheel_step, v_min, v_max);
887 changed = true;
888 }
889 }
890 return changed;
891}
892
893bool SliderIntWheel(const char* label, int* v, int v_min, int 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);
897
898 if (IsValueWheelAdjustmentAllowedForCurrentItem()) {
899 float wheel = ImGui::GetIO().MouseWheel;
900 if (wheel != 0.0f) {
901 int delta = static_cast<int>(wheel) * wheel_step;
902 *v = std::clamp(*v + delta, v_min, v_max);
903 changed = true;
904 }
905 }
906 return changed;
907}
908
909} // namespace gui
910} // namespace yaze
SNES 16-bit tile metadata container.
Definition snes_tile.h:52
overloaded(Ts...) -> overloaded< Ts... >
Definition input.cc:26
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:169
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:51
bool ApplyHexMouseWheel(T *data, T min_value, T max_value)
Definition input.cc:361
thread_local DeferredScalarStorage g_deferred_scalar_storage
Definition input.cc:320
void PrepareDeferredScalarStorage(ImGuiContext *context, int frame)
Definition input.cc:333
void ResetDeferredScalarEdit(DeferredScalarEdit &edit, ImGuiDataType data_type, const void *data, size_t data_size, int frame, InputScalarTargetIdentity target_identity)
Definition input.cc:322
bool InputHexByteCustom(const char *label, uint8_t *data, float input_width)
Definition input.cc:830
bool ClickableText(const std::string &text)
Definition input.cc:574
bool SliderIntWheel(const char *label, int *v, int v_min, int v_max, const char *format, int wheel_step, ImGuiSliderFlags flags)
Definition input.cc:893
void Paragraph(const std::string &text)
Definition input.cc:569
void ItemLabel(absl::string_view title, ItemLabelFlags flags)
Definition input.cc:628
bool InputHexWord(const char *label, uint16_t *data, float input_width, bool no_step)
Definition input.cc:485
bool ListBox(const char *label, int *current_item, const std::vector< std::string > &items, int height_in_items)
Definition input.cc:673
bool SliderFloatWheel(const char *label, float *v, float v_min, float v_max, const char *format, float wheel_step, ImGuiSliderFlags flags)
Definition input.cc:876
bool InputHexShort(const char *label, uint32_t *data)
Definition input.cc:479
void AddTableColumn(Table &table, const std::string &label, GuiElement element)
Definition input.cc:767
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:816
const int kStepOneHex
Definition input.cc:385
void DrawTable(Table &params)
Definition input.cc:773
bool OpenUrl(const std::string &url)
Definition input.cc:794
bool InputHexWordCustom(const char *label, uint16_t *data, float input_width)
Definition input.cc:853
bool InputHex(const char *label, uint64_t *data)
Definition input.cc:466
bool InputTileInfo(const char *label, gfx::TileInfo *tile_info)
Definition input.cc:685
bool InputScalarDeferred(const char *label, ImGuiDataType data_type, void *data, const char *format, ImGuiInputTextFlags flags, InputScalarTargetIdentity target_identity)
Definition input.cc:388
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:529
const int kStepFastHex
Definition input.cc:386
ImGuiID GetID(const std::string &id)
Definition input.cc:700
ImGuiKey MapKeyToImGuiKey(char key)
Definition input.cc:704
bool InputHexByte(const char *label, uint8_t *data, float input_width, bool no_step)
Definition input.cc:506
InputHexResult InputHexWordEx(const char *label, uint16_t *data, float input_width, bool no_step)
Definition input.cc:557
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:306
std::array< uint8_t, kMaxDeferredScalarBytes > source_model
Definition input.cc:308
std::unordered_map< ImGuiID, DeferredScalarEdit > edits
Definition input.cc:317