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