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
3#include <functional>
4#include <string>
5#include <variant>
6
7#include "absl/strings/string_view.h"
8#include "app/gfx/snes_tile.h"
9#include "imgui/imgui.h"
10#include "imgui/imgui_internal.h"
11#include "imgui_memory_editor.h"
12
13template <class... Ts>
14struct overloaded : Ts... {
15 using Ts::operator()...;
16};
17template <class... Ts>
18overloaded(Ts...) -> overloaded<Ts...>;
19
20namespace ImGui {
21
22static inline ImGuiInputTextFlags InputScalar_DefaultCharsFilter(
23 ImGuiDataType data_type, const char* format) {
24 if (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double)
25 return ImGuiInputTextFlags_CharsScientific;
26 const char format_last_char = format[0] ? format[strlen(format) - 1] : 0;
27 return (format_last_char == 'x' || format_last_char == 'X')
28 ? ImGuiInputTextFlags_CharsHexadecimal
29 : ImGuiInputTextFlags_CharsDecimal;
30}
31
32// Helper: returns true if label is "invisible" (starts with "##")
33static inline bool IsInvisibleLabel(const char* label) {
34 return label && label[0] == '#' && label[1] == '#';
35}
36
37bool InputScalarLeft(const char* label, ImGuiDataType data_type, void* p_data,
38 const void* p_step, const void* p_step_fast,
39 const char* format, float input_width,
40 ImGuiInputTextFlags flags, bool no_step = false) {
41 ImGuiWindow* window = ImGui::GetCurrentWindow();
42 if (window->SkipItems)
43 return false;
44
45 ImGuiContext& g = *GImGui;
46 ImGuiStyle& style = g.Style;
47
48 if (format == NULL)
49 format = DataTypeGetInfo(data_type)->PrintFmt;
50
51 char buf[64];
52 DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, p_data, format);
53
54 if (g.ActiveId == 0 && (flags & (ImGuiInputTextFlags_CharsDecimal |
55 ImGuiInputTextFlags_CharsHexadecimal |
56 ImGuiInputTextFlags_CharsScientific)) == 0)
57 flags |= InputScalar_DefaultCharsFilter(data_type, format);
58 flags |= ImGuiInputTextFlags_AutoSelectAll;
59
60 bool value_changed = false;
61 const float button_size = GetFrameHeight();
62
63 // Support invisible labels (##) by not rendering the label, but still using it for ID
64 bool invisible_label = IsInvisibleLabel(label);
65
66 if (!invisible_label) {
67 AlignTextToFramePadding();
68 Text("%s", label);
69 SameLine();
70 }
71
72 BeginGroup(); // The only purpose of the group here is to allow the caller
73 // to query item data e.g. IsItemActive()
74 PushID(label);
75 SetNextItemWidth(ImMax(
76 1.0f, CalcItemWidth() - (button_size + style.ItemInnerSpacing.x) * 2));
77
78 // Place the label on the left of the input field, unless invisible
79 PushStyleVar(ImGuiStyleVar_ItemSpacing,
80 ImVec2{style.ItemSpacing.x, style.ItemSpacing.y});
81 PushStyleVar(ImGuiStyleVar_FramePadding,
82 ImVec2{style.FramePadding.x, style.FramePadding.y});
83
84 SetNextItemWidth(input_width);
85 if (InputText("", buf, IM_ARRAYSIZE(buf),
86 flags)) // PushId(label) + "" gives us the expected ID
87 // from outside point of view
88 value_changed = DataTypeApplyFromText(buf, data_type, p_data, format);
89 IMGUI_TEST_ENGINE_ITEM_INFO(
90 g.LastItemData.ID, label,
91 g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Inputable);
92
93 // Mouse wheel support
94 if (IsItemHovered() && g.IO.MouseWheel != 0.0f) {
95 float scroll_amount = g.IO.MouseWheel;
96 float scroll_speed = 0.25f; // Adjust the scroll speed as needed
97
98 if (g.IO.KeyCtrl && p_step_fast)
99 scroll_amount *= *(const float*)p_step_fast;
100 else
101 scroll_amount *= *(const float*)p_step;
102
103 if (scroll_amount > 0.0f) {
104 scroll_amount *= scroll_speed; // Adjust the scroll speed as needed
105 DataTypeApplyOp(data_type, '+', p_data, p_data, &scroll_amount);
106 value_changed = true;
107 } else if (scroll_amount < 0.0f) {
108 scroll_amount *= -scroll_speed; // Adjust the scroll speed as needed
109 DataTypeApplyOp(data_type, '-', p_data, p_data, &scroll_amount);
110 value_changed = true;
111 }
112 }
113
114 // Step buttons
115 if (!no_step) {
116 const ImVec2 backup_frame_padding = style.FramePadding;
117 style.FramePadding.x = style.FramePadding.y;
118 ImGuiButtonFlags button_flags = ImGuiButtonFlags_PressedOnClick;
119 if (flags & ImGuiInputTextFlags_ReadOnly)
120 BeginDisabled();
121 SameLine(0, style.ItemInnerSpacing.x);
122 if (ButtonEx("-", ImVec2(button_size, button_size), button_flags)) {
123 DataTypeApplyOp(data_type, '-', p_data, p_data,
124 g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step);
125 value_changed = true;
126 }
127 SameLine(0, style.ItemInnerSpacing.x);
128 if (ButtonEx("+", ImVec2(button_size, button_size), button_flags)) {
129 DataTypeApplyOp(data_type, '+', p_data, p_data,
130 g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step);
131 value_changed = true;
132 }
133
134 if (flags & ImGuiInputTextFlags_ReadOnly)
135 EndDisabled();
136
137 style.FramePadding = backup_frame_padding;
138 }
139 PopID();
140 EndGroup();
141 ImGui::PopStyleVar(2);
142
143 if (value_changed)
144 MarkItemEdited(g.LastItemData.ID);
145
146 return value_changed;
147}
148} // namespace ImGui
149
150namespace yaze {
151namespace gui {
152
153const int kStepOneHex = 0x01;
154const int kStepFastHex = 0x0F;
155
156bool InputHex(const char* label, uint64_t* data) {
157 return ImGui::InputScalar(label, ImGuiDataType_U64, data, &kStepOneHex,
158 &kStepFastHex, "%06X",
159 ImGuiInputTextFlags_CharsHexadecimal);
160}
161
162bool InputHex(const char* label, int* data, int num_digits, float input_width) {
163 const std::string format = "%0" + std::to_string(num_digits) + "X";
164 return ImGui::InputScalarLeft(label, ImGuiDataType_S32, data, &kStepOneHex,
165 &kStepFastHex, format.c_str(), input_width,
166 ImGuiInputTextFlags_CharsHexadecimal);
167}
168
169bool InputHexShort(const char* label, uint32_t* data) {
170 return ImGui::InputScalar(label, ImGuiDataType_U32, data, &kStepOneHex,
171 &kStepFastHex, "%06X",
172 ImGuiInputTextFlags_CharsHexadecimal);
173}
174
175bool InputHexWord(const char* label, uint16_t* data, float input_width,
176 bool no_step) {
177 return ImGui::InputScalarLeft(label, ImGuiDataType_U16, data, &kStepOneHex,
178 &kStepFastHex, "%04X", input_width,
179 ImGuiInputTextFlags_CharsHexadecimal, no_step);
180}
181
182bool InputHexWord(const char* label, int16_t* data, float input_width,
183 bool no_step) {
184 return ImGui::InputScalarLeft(label, ImGuiDataType_S16, data, &kStepOneHex,
185 &kStepFastHex, "%04X", input_width,
186 ImGuiInputTextFlags_CharsHexadecimal, no_step);
187}
188
189bool InputHexByte(const char* label, uint8_t* data, float input_width,
190 bool no_step) {
191 return ImGui::InputScalarLeft(label, ImGuiDataType_U8, data, &kStepOneHex,
192 &kStepFastHex, "%02X", input_width,
193 ImGuiInputTextFlags_CharsHexadecimal, no_step);
194}
195
196bool InputHexByte(const char* label, uint8_t* data, uint8_t max_value,
197 float input_width, bool no_step) {
198 if (ImGui::InputScalarLeft(label, ImGuiDataType_U8, data, &kStepOneHex,
199 &kStepFastHex, "%02X", input_width,
200 ImGuiInputTextFlags_CharsHexadecimal, no_step)) {
201 if (*data > max_value) {
202 *data = max_value;
203 }
204 return true;
205 }
206 return false;
207}
208
209void Paragraph(const std::string& text) {
210 ImGui::TextWrapped("%s", text.c_str());
211}
212
213// TODO: Setup themes and text/clickable colors
214bool ClickableText(const std::string& text) {
215 ImGui::BeginGroup();
216 ImGui::PushID(text.c_str());
217
218 // Calculate text size
219 ImVec2 text_size = ImGui::CalcTextSize(text.c_str());
220
221 // Get cursor position for hover detection
222 ImVec2 pos = ImGui::GetCursorScreenPos();
223 ImRect bb(pos, ImVec2(pos.x + text_size.x, pos.y + text_size.y));
224
225 // Add item
226 const ImGuiID id = ImGui::GetID(text.c_str());
227 bool result = false;
228 if (ImGui::ItemAdd(bb, id)) {
229 bool hovered = ImGui::IsItemHovered();
230 bool clicked = ImGui::IsItemClicked();
231
232 // Render text with high-contrast appropriate color
233 ImVec4 link_color = ImGui::GetStyleColorVec4(ImGuiCol_TextLink);
234 ImVec4 bg_color = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg);
235
236 // Ensure good contrast against background
237 float contrast_factor =
238 (bg_color.x + bg_color.y + bg_color.z) < 1.5f ? 1.0f : 0.3f;
239
240 ImVec4 color;
241 if (hovered) {
242 // Brighter color on hover for better visibility
243 color = ImVec4(std::min(1.0f, link_color.x + 0.3f),
244 std::min(1.0f, link_color.y + 0.3f),
245 std::min(1.0f, link_color.z + 0.3f), 1.0f);
246 } else {
247 // Ensure link color has good contrast
248 color = ImVec4(std::max(contrast_factor, link_color.x),
249 std::max(contrast_factor, link_color.y),
250 std::max(contrast_factor, link_color.z), 1.0f);
251 }
252
253 ImGui::GetWindowDrawList()->AddText(
254 pos, ImGui::ColorConvertFloat4ToU32(color), text.c_str());
255
256 result = clicked;
257 }
258
259 ImGui::PopID();
260
261 // Advance cursor past the text
262 ImGui::Dummy(text_size);
263 ImGui::EndGroup();
264
265 return result;
266}
267
268void ItemLabel(absl::string_view title, ItemLabelFlags flags) {
269 ImGuiWindow* window = ImGui::GetCurrentWindow();
270 const ImVec2 lineStart = ImGui::GetCursorScreenPos();
271 const ImGuiStyle& style = ImGui::GetStyle();
272 float fullWidth = ImGui::GetContentRegionAvail().x;
273 float itemWidth = ImGui::CalcItemWidth() + style.ItemSpacing.x;
274 ImVec2 textSize =
275 ImGui::CalcTextSize(title.data(), title.data() + title.size());
276 ImRect textRect;
277 textRect.Min = ImGui::GetCursorScreenPos();
278 if (flags & ItemLabelFlag::Right)
279 textRect.Min.x = textRect.Min.x + itemWidth;
280 textRect.Max = textRect.Min;
281 textRect.Max.x += fullWidth - itemWidth;
282 textRect.Max.y += textSize.y;
283
284 ImGui::SetCursorScreenPos(textRect.Min);
285
286 ImGui::AlignTextToFramePadding();
287 // Adjust text rect manually because we render it directly into a drawlist
288 // instead of using public functions.
289 textRect.Min.y += window->DC.CurrLineTextBaseOffset;
290 textRect.Max.y += window->DC.CurrLineTextBaseOffset;
291
292 ImGui::ItemSize(textRect);
293 if (ImGui::ItemAdd(
294 textRect, window->GetID(title.data(), title.data() + title.size()))) {
295 ImGui::RenderTextEllipsis(ImGui::GetWindowDrawList(), textRect.Min,
296 textRect.Max, textRect.Max.x, title.data(),
297 title.data() + title.size(), &textSize);
298
299 if (textRect.GetWidth() < textSize.x && ImGui::IsItemHovered())
300 ImGui::SetTooltip("%.*s", (int)title.size(), title.data());
301 }
302 if (flags & ItemLabelFlag::Left) {
303 ImVec2 result;
304 auto other = ImVec2{0, textSize.y + window->DC.CurrLineTextBaseOffset};
305 result.x = textRect.Max.x - other.x;
306 result.y = textRect.Max.y - other.y;
307 ImGui::SetCursorScreenPos(result);
308 ImGui::SameLine();
309 } else if (flags & ItemLabelFlag::Right)
310 ImGui::SetCursorScreenPos(lineStart);
311}
312
313bool ListBox(const char* label, int* current_item,
314 const std::vector<std::string>& items, int height_in_items) {
315 std::vector<const char*> items_ptr;
316 items_ptr.reserve(items.size());
317 for (const auto& item : items) {
318 items_ptr.push_back(item.c_str());
319 }
320 int items_count = static_cast<int>(items.size());
321 return ImGui::ListBox(label, current_item, items_ptr.data(), items_count,
322 height_in_items);
323}
324
325bool InputTileInfo(const char* label, gfx::TileInfo* tile_info) {
326 ImGui::PushID(label);
327 ImGui::BeginGroup();
328 bool changed = false;
329 changed |= InputHexWord(label, &tile_info->id_);
330 changed |= InputHexByte("Palette", &tile_info->palette_);
331 changed |= ImGui::Checkbox("Priority", &tile_info->over_);
332 changed |= ImGui::Checkbox("Vertical Flip", &tile_info->vertical_mirror_);
333 changed |= ImGui::Checkbox("Horizontal Flip", &tile_info->horizontal_mirror_);
334 ImGui::EndGroup();
335 ImGui::PopID();
336 return changed;
337}
338
339ImGuiID GetID(const std::string& id) {
340 return ImGui::GetID(id.c_str());
341}
342
343ImGuiKey MapKeyToImGuiKey(char key) {
344 switch (key) {
345 case 'A':
346 return ImGuiKey_A;
347 case 'B':
348 return ImGuiKey_B;
349 case 'C':
350 return ImGuiKey_C;
351 case 'D':
352 return ImGuiKey_D;
353 case 'E':
354 return ImGuiKey_E;
355 case 'F':
356 return ImGuiKey_F;
357 case 'G':
358 return ImGuiKey_G;
359 case 'H':
360 return ImGuiKey_H;
361 case 'I':
362 return ImGuiKey_I;
363 case 'J':
364 return ImGuiKey_J;
365 case 'K':
366 return ImGuiKey_K;
367 case 'L':
368 return ImGuiKey_L;
369 case 'M':
370 return ImGuiKey_M;
371 case 'N':
372 return ImGuiKey_N;
373 case 'O':
374 return ImGuiKey_O;
375 case 'P':
376 return ImGuiKey_P;
377 case 'Q':
378 return ImGuiKey_Q;
379 case 'R':
380 return ImGuiKey_R;
381 case 'S':
382 return ImGuiKey_S;
383 case 'T':
384 return ImGuiKey_T;
385 case 'U':
386 return ImGuiKey_U;
387 case 'V':
388 return ImGuiKey_V;
389 case 'W':
390 return ImGuiKey_W;
391 case 'X':
392 return ImGuiKey_X;
393 case 'Y':
394 return ImGuiKey_Y;
395 case 'Z':
396 return ImGuiKey_Z;
397 case '/':
398 return ImGuiKey_Slash;
399 case '-':
400 return ImGuiKey_Minus;
401 default:
402 return ImGuiKey_COUNT;
403 }
404}
405
406void AddTableColumn(Table& table, const std::string& label,
407 GuiElement element) {
408 table.column_labels.push_back(label);
409 table.column_contents.push_back(element);
410}
411
412void DrawTable(Table& params) {
413 if (ImGui::BeginTable(params.id, params.num_columns, params.flags,
414 params.size)) {
415 for (int i = 0; i < params.num_columns; ++i)
416 ImGui::TableSetupColumn(params.column_labels[i].c_str());
417
418 for (int i = 0; i < params.num_columns; ++i) {
419 ImGui::TableNextColumn();
420 switch (params.column_contents[i].index()) {
421 case 0:
422 std::get<0>(params.column_contents[i])();
423 break;
424 case 1:
425 ImGui::Text("%s", std::get<1>(params.column_contents[i]).c_str());
426 break;
427 }
428 }
429 ImGui::EndTable();
430 }
431}
432
433bool OpenUrl(const std::string& url) {
434 // if iOS
435#ifdef __APPLE__
436#if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
437// no system call on iOS
438 return false;
439#else
440 return system(("open " + url).c_str()) == 0;
441#endif
442#endif
443
444#ifdef __linux__
445 return system(("xdg-open " + url).c_str()) == 0;
446#endif
447
448#ifdef __windows__
449 return system(("start " + url).c_str()) == 0;
450#endif
451
452 return false;
453}
454
455void MemoryEditorPopup(const std::string& label, std::span<uint8_t> memory) {
456 static bool open = false;
457 static MemoryEditor editor;
458 if (ImGui::Button("View Data")) {
459 open = true;
460 }
461 if (open) {
462 ImGui::Begin(label.c_str(), &open);
463 editor.DrawContents(memory.data(), memory.size());
464 ImGui::End();
465 }
466}
467
468// Custom hex input functions that properly respect width
469bool InputHexByteCustom(const char* label, uint8_t* data, float input_width) {
470 ImGui::PushID(label);
471
472 // Create a simple hex input that respects width
473 char buf[8];
474 snprintf(buf, sizeof(buf), "%02X", *data);
475
476 ImGui::SetNextItemWidth(input_width);
477 bool changed = ImGui::InputText(
478 label, buf, sizeof(buf),
479 ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_AutoSelectAll);
480
481 if (changed) {
482 unsigned int temp;
483 if (sscanf(buf, "%X", &temp) == 1) {
484 *data = static_cast<uint8_t>(temp & 0xFF);
485 }
486 }
487
488 ImGui::PopID();
489 return changed;
490}
491
492bool InputHexWordCustom(const char* label, uint16_t* data, float input_width) {
493 ImGui::PushID(label);
494
495 // Create a simple hex input that respects width
496 char buf[8];
497 snprintf(buf, sizeof(buf), "%04X", *data);
498
499 ImGui::SetNextItemWidth(input_width);
500 bool changed = ImGui::InputText(
501 label, buf, sizeof(buf),
502 ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_AutoSelectAll);
503
504 if (changed) {
505 unsigned int temp;
506 if (sscanf(buf, "%X", &temp) == 1) {
507 *data = static_cast<uint16_t>(temp & 0xFFFF);
508 }
509 }
510
511 ImGui::PopID();
512 return changed;
513}
514
515} // namespace gui
516} // namespace yaze
SNES 16-bit tile metadata container.
Definition snes_tile.h:50
Definition input.cc:20
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:37
bool InputHexByteCustom(const char *label, uint8_t *data, float input_width)
Definition input.cc:469
bool ClickableText(const std::string &text)
Definition input.cc:214
void Paragraph(const std::string &text)
Definition input.cc:209
void ItemLabel(absl::string_view title, ItemLabelFlags flags)
Definition input.cc:268
enum ItemLabelFlag { Left=1u<< 0u, Right=1u<< 1u, Default=Left, } ItemLabelFlags
Definition input.h:59
bool InputHexWord(const char *label, uint16_t *data, float input_width, bool no_step)
Definition input.cc:175
bool ListBox(const char *label, int *current_item, const std::vector< std::string > &items, int height_in_items)
Definition input.cc:313
bool InputHexShort(const char *label, uint32_t *data)
Definition input.cc:169
void AddTableColumn(Table &table, const std::string &label, GuiElement element)
Definition input.cc:406
void MemoryEditorPopup(const std::string &label, std::span< uint8_t > memory)
Definition input.cc:455
const int kStepOneHex
Definition input.cc:153
void DrawTable(Table &params)
Definition input.cc:412
bool OpenUrl(const std::string &url)
Definition input.cc:433
bool InputHexWordCustom(const char *label, uint16_t *data, float input_width)
Definition input.cc:492
bool InputHex(const char *label, uint64_t *data)
Definition input.cc:156
bool InputTileInfo(const char *label, gfx::TileInfo *tile_info)
Definition input.cc:325
std::variant< std::function< void()>, std::string > GuiElement
Definition input.h:67
const int kStepFastHex
Definition input.cc:154
ImGuiID GetID(const std::string &id)
Definition input.cc:339
ImGuiKey MapKeyToImGuiKey(char key)
Definition input.cc:343
bool InputHexByte(const char *label, uint8_t *data, float input_width, bool no_step)
Definition input.cc:189
Main namespace for the application.
std::vector< std::string > column_labels
Definition input.h:74
std::vector< GuiElement > column_contents
Definition input.h:75
ImVec2 size
Definition input.h:73
int num_columns
Definition input.h:71
const char * id
Definition input.h:70
ImGuiTableFlags flags
Definition input.h:72