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