- Updated the testing guide to clarify the testing framework's organization and execution methods, improving user understanding. - Refactored CMakeLists to include new platform-specific files, ensuring proper integration of the rendering backend. - Modified main application files to utilize the new IRenderer interface, enhancing flexibility in rendering operations. - Implemented deferred texture management in various components, allowing for more efficient graphics handling and improved performance. - Introduced new methods for texture creation and updates, streamlining the rendering process across the application. - Enhanced logging and error handling in the rendering pipeline to facilitate better debugging and diagnostics.
517 lines
16 KiB
C++
517 lines
16 KiB
C++
#include "input.h"
|
|
|
|
#include <functional>
|
|
#include <string>
|
|
#include <variant>
|
|
|
|
#include "absl/strings/string_view.h"
|
|
#include "app/gfx/snes_tile.h"
|
|
#include "imgui/imgui.h"
|
|
#include "imgui/imgui_internal.h"
|
|
#include "imgui_memory_editor.h"
|
|
|
|
template <class... Ts>
|
|
struct overloaded : Ts... {
|
|
using Ts::operator()...;
|
|
};
|
|
template <class... Ts>
|
|
overloaded(Ts...) -> overloaded<Ts...>;
|
|
|
|
namespace ImGui {
|
|
|
|
static inline ImGuiInputTextFlags InputScalar_DefaultCharsFilter(
|
|
ImGuiDataType data_type, const char* format) {
|
|
if (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double)
|
|
return ImGuiInputTextFlags_CharsScientific;
|
|
const char format_last_char = format[0] ? format[strlen(format) - 1] : 0;
|
|
return (format_last_char == 'x' || format_last_char == 'X')
|
|
? ImGuiInputTextFlags_CharsHexadecimal
|
|
: ImGuiInputTextFlags_CharsDecimal;
|
|
}
|
|
|
|
// Helper: returns true if label is "invisible" (starts with "##")
|
|
static inline bool IsInvisibleLabel(const char* label) {
|
|
return label && label[0] == '#' && label[1] == '#';
|
|
}
|
|
|
|
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) {
|
|
ImGuiWindow* window = ImGui::GetCurrentWindow();
|
|
if (window->SkipItems)
|
|
return false;
|
|
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiStyle& style = g.Style;
|
|
|
|
if (format == NULL)
|
|
format = DataTypeGetInfo(data_type)->PrintFmt;
|
|
|
|
char buf[64];
|
|
DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, p_data, format);
|
|
|
|
if (g.ActiveId == 0 && (flags & (ImGuiInputTextFlags_CharsDecimal |
|
|
ImGuiInputTextFlags_CharsHexadecimal |
|
|
ImGuiInputTextFlags_CharsScientific)) == 0)
|
|
flags |= InputScalar_DefaultCharsFilter(data_type, format);
|
|
flags |= ImGuiInputTextFlags_AutoSelectAll;
|
|
|
|
bool value_changed = false;
|
|
const float button_size = GetFrameHeight();
|
|
|
|
// Support invisible labels (##) by not rendering the label, but still using it for ID
|
|
bool invisible_label = IsInvisibleLabel(label);
|
|
|
|
if (!invisible_label) {
|
|
AlignTextToFramePadding();
|
|
Text("%s", label);
|
|
SameLine();
|
|
}
|
|
|
|
BeginGroup(); // The only purpose of the group here is to allow the caller
|
|
// to query item data e.g. IsItemActive()
|
|
PushID(label);
|
|
SetNextItemWidth(ImMax(
|
|
1.0f, CalcItemWidth() - (button_size + style.ItemInnerSpacing.x) * 2));
|
|
|
|
// Place the label on the left of the input field, unless invisible
|
|
PushStyleVar(ImGuiStyleVar_ItemSpacing,
|
|
ImVec2{style.ItemSpacing.x, style.ItemSpacing.y});
|
|
PushStyleVar(ImGuiStyleVar_FramePadding,
|
|
ImVec2{style.FramePadding.x, style.FramePadding.y});
|
|
|
|
SetNextItemWidth(input_width);
|
|
if (InputText("", buf, IM_ARRAYSIZE(buf),
|
|
flags)) // PushId(label) + "" gives us the expected ID
|
|
// from outside point of view
|
|
value_changed = DataTypeApplyFromText(buf, data_type, p_data, format);
|
|
IMGUI_TEST_ENGINE_ITEM_INFO(
|
|
g.LastItemData.ID, label,
|
|
g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Inputable);
|
|
|
|
// Mouse wheel support
|
|
if (IsItemHovered() && g.IO.MouseWheel != 0.0f) {
|
|
float scroll_amount = g.IO.MouseWheel;
|
|
float scroll_speed = 0.25f; // Adjust the scroll speed as needed
|
|
|
|
if (g.IO.KeyCtrl && p_step_fast)
|
|
scroll_amount *= *(const float*)p_step_fast;
|
|
else
|
|
scroll_amount *= *(const float*)p_step;
|
|
|
|
if (scroll_amount > 0.0f) {
|
|
scroll_amount *= scroll_speed; // Adjust the scroll speed as needed
|
|
DataTypeApplyOp(data_type, '+', p_data, p_data, &scroll_amount);
|
|
value_changed = true;
|
|
} else if (scroll_amount < 0.0f) {
|
|
scroll_amount *= -scroll_speed; // Adjust the scroll speed as needed
|
|
DataTypeApplyOp(data_type, '-', p_data, p_data, &scroll_amount);
|
|
value_changed = true;
|
|
}
|
|
}
|
|
|
|
// Step buttons
|
|
if (!no_step) {
|
|
const ImVec2 backup_frame_padding = style.FramePadding;
|
|
style.FramePadding.x = style.FramePadding.y;
|
|
ImGuiButtonFlags button_flags = ImGuiButtonFlags_PressedOnClick;
|
|
if (flags & ImGuiInputTextFlags_ReadOnly)
|
|
BeginDisabled();
|
|
SameLine(0, style.ItemInnerSpacing.x);
|
|
if (ButtonEx("-", ImVec2(button_size, button_size), button_flags)) {
|
|
DataTypeApplyOp(data_type, '-', p_data, p_data,
|
|
g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step);
|
|
value_changed = true;
|
|
}
|
|
SameLine(0, style.ItemInnerSpacing.x);
|
|
if (ButtonEx("+", ImVec2(button_size, button_size), button_flags)) {
|
|
DataTypeApplyOp(data_type, '+', p_data, p_data,
|
|
g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step);
|
|
value_changed = true;
|
|
}
|
|
|
|
if (flags & ImGuiInputTextFlags_ReadOnly)
|
|
EndDisabled();
|
|
|
|
style.FramePadding = backup_frame_padding;
|
|
}
|
|
PopID();
|
|
EndGroup();
|
|
ImGui::PopStyleVar(2);
|
|
|
|
if (value_changed)
|
|
MarkItemEdited(g.LastItemData.ID);
|
|
|
|
return value_changed;
|
|
}
|
|
} // namespace ImGui
|
|
|
|
namespace yaze {
|
|
namespace gui {
|
|
|
|
const int kStepOneHex = 0x01;
|
|
const int kStepFastHex = 0x0F;
|
|
|
|
bool InputHex(const char* label, uint64_t* data) {
|
|
return ImGui::InputScalar(label, ImGuiDataType_U64, data, &kStepOneHex,
|
|
&kStepFastHex, "%06X",
|
|
ImGuiInputTextFlags_CharsHexadecimal);
|
|
}
|
|
|
|
bool InputHex(const char* label, int* data, int num_digits, float input_width) {
|
|
const std::string format = "%0" + std::to_string(num_digits) + "X";
|
|
return ImGui::InputScalarLeft(label, ImGuiDataType_S32, data, &kStepOneHex,
|
|
&kStepFastHex, format.c_str(), input_width,
|
|
ImGuiInputTextFlags_CharsHexadecimal);
|
|
}
|
|
|
|
bool InputHexShort(const char* label, uint32_t* data) {
|
|
return ImGui::InputScalar(label, ImGuiDataType_U32, data, &kStepOneHex,
|
|
&kStepFastHex, "%06X",
|
|
ImGuiInputTextFlags_CharsHexadecimal);
|
|
}
|
|
|
|
bool InputHexWord(const char* label, uint16_t* data, float input_width,
|
|
bool no_step) {
|
|
return ImGui::InputScalarLeft(label, ImGuiDataType_U16, data, &kStepOneHex,
|
|
&kStepFastHex, "%04X", input_width,
|
|
ImGuiInputTextFlags_CharsHexadecimal, no_step);
|
|
}
|
|
|
|
bool InputHexWord(const char* label, int16_t* data, float input_width,
|
|
bool no_step) {
|
|
return ImGui::InputScalarLeft(label, ImGuiDataType_S16, data, &kStepOneHex,
|
|
&kStepFastHex, "%04X", input_width,
|
|
ImGuiInputTextFlags_CharsHexadecimal, no_step);
|
|
}
|
|
|
|
bool InputHexByte(const char* label, uint8_t* data, float input_width,
|
|
bool no_step) {
|
|
return ImGui::InputScalarLeft(label, ImGuiDataType_U8, data, &kStepOneHex,
|
|
&kStepFastHex, "%02X", input_width,
|
|
ImGuiInputTextFlags_CharsHexadecimal, no_step);
|
|
}
|
|
|
|
bool InputHexByte(const char* label, uint8_t* data, uint8_t max_value,
|
|
float input_width, bool no_step) {
|
|
if (ImGui::InputScalarLeft(label, ImGuiDataType_U8, data, &kStepOneHex,
|
|
&kStepFastHex, "%02X", input_width,
|
|
ImGuiInputTextFlags_CharsHexadecimal, no_step)) {
|
|
if (*data > max_value) {
|
|
*data = max_value;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Paragraph(const std::string& text) {
|
|
ImGui::TextWrapped("%s", text.c_str());
|
|
}
|
|
|
|
// TODO: Setup themes and text/clickable colors
|
|
bool ClickableText(const std::string& text) {
|
|
ImGui::BeginGroup();
|
|
ImGui::PushID(text.c_str());
|
|
|
|
// Calculate text size
|
|
ImVec2 text_size = ImGui::CalcTextSize(text.c_str());
|
|
|
|
// Get cursor position for hover detection
|
|
ImVec2 pos = ImGui::GetCursorScreenPos();
|
|
ImRect bb(pos, ImVec2(pos.x + text_size.x, pos.y + text_size.y));
|
|
|
|
// Add item
|
|
const ImGuiID id = ImGui::GetID(text.c_str());
|
|
bool result = false;
|
|
if (ImGui::ItemAdd(bb, id)) {
|
|
bool hovered = ImGui::IsItemHovered();
|
|
bool clicked = ImGui::IsItemClicked();
|
|
|
|
// Render text with high-contrast appropriate color
|
|
ImVec4 link_color = ImGui::GetStyleColorVec4(ImGuiCol_TextLink);
|
|
ImVec4 bg_color = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg);
|
|
|
|
// Ensure good contrast against background
|
|
float contrast_factor =
|
|
(bg_color.x + bg_color.y + bg_color.z) < 1.5f ? 1.0f : 0.3f;
|
|
|
|
ImVec4 color;
|
|
if (hovered) {
|
|
// Brighter color on hover for better visibility
|
|
color = ImVec4(std::min(1.0f, link_color.x + 0.3f),
|
|
std::min(1.0f, link_color.y + 0.3f),
|
|
std::min(1.0f, link_color.z + 0.3f), 1.0f);
|
|
} else {
|
|
// Ensure link color has good contrast
|
|
color = ImVec4(std::max(contrast_factor, link_color.x),
|
|
std::max(contrast_factor, link_color.y),
|
|
std::max(contrast_factor, link_color.z), 1.0f);
|
|
}
|
|
|
|
ImGui::GetWindowDrawList()->AddText(
|
|
pos, ImGui::ColorConvertFloat4ToU32(color), text.c_str());
|
|
|
|
result = clicked;
|
|
}
|
|
|
|
ImGui::PopID();
|
|
|
|
// Advance cursor past the text
|
|
ImGui::Dummy(text_size);
|
|
ImGui::EndGroup();
|
|
|
|
return result;
|
|
}
|
|
|
|
void ItemLabel(absl::string_view title, ItemLabelFlags flags) {
|
|
ImGuiWindow* window = ImGui::GetCurrentWindow();
|
|
const ImVec2 lineStart = ImGui::GetCursorScreenPos();
|
|
const ImGuiStyle& style = ImGui::GetStyle();
|
|
float fullWidth = ImGui::GetContentRegionAvail().x;
|
|
float itemWidth = ImGui::CalcItemWidth() + style.ItemSpacing.x;
|
|
ImVec2 textSize =
|
|
ImGui::CalcTextSize(title.data(), title.data() + title.size());
|
|
ImRect textRect;
|
|
textRect.Min = ImGui::GetCursorScreenPos();
|
|
if (flags & ItemLabelFlag::Right)
|
|
textRect.Min.x = textRect.Min.x + itemWidth;
|
|
textRect.Max = textRect.Min;
|
|
textRect.Max.x += fullWidth - itemWidth;
|
|
textRect.Max.y += textSize.y;
|
|
|
|
ImGui::SetCursorScreenPos(textRect.Min);
|
|
|
|
ImGui::AlignTextToFramePadding();
|
|
// Adjust text rect manually because we render it directly into a drawlist
|
|
// instead of using public functions.
|
|
textRect.Min.y += window->DC.CurrLineTextBaseOffset;
|
|
textRect.Max.y += window->DC.CurrLineTextBaseOffset;
|
|
|
|
ImGui::ItemSize(textRect);
|
|
if (ImGui::ItemAdd(
|
|
textRect, window->GetID(title.data(), title.data() + title.size()))) {
|
|
ImGui::RenderTextEllipsis(ImGui::GetWindowDrawList(), textRect.Min,
|
|
textRect.Max, textRect.Max.x, title.data(),
|
|
title.data() + title.size(), &textSize);
|
|
|
|
if (textRect.GetWidth() < textSize.x && ImGui::IsItemHovered())
|
|
ImGui::SetTooltip("%.*s", (int)title.size(), title.data());
|
|
}
|
|
if (flags & ItemLabelFlag::Left) {
|
|
ImVec2 result;
|
|
auto other = ImVec2{0, textSize.y + window->DC.CurrLineTextBaseOffset};
|
|
result.x = textRect.Max.x - other.x;
|
|
result.y = textRect.Max.y - other.y;
|
|
ImGui::SetCursorScreenPos(result);
|
|
ImGui::SameLine();
|
|
} else if (flags & ItemLabelFlag::Right)
|
|
ImGui::SetCursorScreenPos(lineStart);
|
|
}
|
|
|
|
bool ListBox(const char* label, int* current_item,
|
|
const std::vector<std::string>& items, int height_in_items) {
|
|
std::vector<const char*> items_ptr;
|
|
items_ptr.reserve(items.size());
|
|
for (const auto& item : items) {
|
|
items_ptr.push_back(item.c_str());
|
|
}
|
|
int items_count = static_cast<int>(items.size());
|
|
return ImGui::ListBox(label, current_item, items_ptr.data(), items_count,
|
|
height_in_items);
|
|
}
|
|
|
|
bool InputTileInfo(const char* label, gfx::TileInfo* tile_info) {
|
|
ImGui::PushID(label);
|
|
ImGui::BeginGroup();
|
|
bool changed = false;
|
|
changed |= InputHexWord(label, &tile_info->id_);
|
|
changed |= InputHexByte("Palette", &tile_info->palette_);
|
|
changed |= ImGui::Checkbox("Priority", &tile_info->over_);
|
|
changed |= ImGui::Checkbox("Vertical Flip", &tile_info->vertical_mirror_);
|
|
changed |= ImGui::Checkbox("Horizontal Flip", &tile_info->horizontal_mirror_);
|
|
ImGui::EndGroup();
|
|
ImGui::PopID();
|
|
return changed;
|
|
}
|
|
|
|
ImGuiID GetID(const std::string& id) {
|
|
return ImGui::GetID(id.c_str());
|
|
}
|
|
|
|
ImGuiKey MapKeyToImGuiKey(char key) {
|
|
switch (key) {
|
|
case 'A':
|
|
return ImGuiKey_A;
|
|
case 'B':
|
|
return ImGuiKey_B;
|
|
case 'C':
|
|
return ImGuiKey_C;
|
|
case 'D':
|
|
return ImGuiKey_D;
|
|
case 'E':
|
|
return ImGuiKey_E;
|
|
case 'F':
|
|
return ImGuiKey_F;
|
|
case 'G':
|
|
return ImGuiKey_G;
|
|
case 'H':
|
|
return ImGuiKey_H;
|
|
case 'I':
|
|
return ImGuiKey_I;
|
|
case 'J':
|
|
return ImGuiKey_J;
|
|
case 'K':
|
|
return ImGuiKey_K;
|
|
case 'L':
|
|
return ImGuiKey_L;
|
|
case 'M':
|
|
return ImGuiKey_M;
|
|
case 'N':
|
|
return ImGuiKey_N;
|
|
case 'O':
|
|
return ImGuiKey_O;
|
|
case 'P':
|
|
return ImGuiKey_P;
|
|
case 'Q':
|
|
return ImGuiKey_Q;
|
|
case 'R':
|
|
return ImGuiKey_R;
|
|
case 'S':
|
|
return ImGuiKey_S;
|
|
case 'T':
|
|
return ImGuiKey_T;
|
|
case 'U':
|
|
return ImGuiKey_U;
|
|
case 'V':
|
|
return ImGuiKey_V;
|
|
case 'W':
|
|
return ImGuiKey_W;
|
|
case 'X':
|
|
return ImGuiKey_X;
|
|
case 'Y':
|
|
return ImGuiKey_Y;
|
|
case 'Z':
|
|
return ImGuiKey_Z;
|
|
case '/':
|
|
return ImGuiKey_Slash;
|
|
case '-':
|
|
return ImGuiKey_Minus;
|
|
default:
|
|
return ImGuiKey_COUNT;
|
|
}
|
|
}
|
|
|
|
void AddTableColumn(Table& table, const std::string& label,
|
|
GuiElement element) {
|
|
table.column_labels.push_back(label);
|
|
table.column_contents.push_back(element);
|
|
}
|
|
|
|
void DrawTable(Table& params) {
|
|
if (ImGui::BeginTable(params.id, params.num_columns, params.flags,
|
|
params.size)) {
|
|
for (int i = 0; i < params.num_columns; ++i)
|
|
ImGui::TableSetupColumn(params.column_labels[i].c_str());
|
|
|
|
for (int i = 0; i < params.num_columns; ++i) {
|
|
ImGui::TableNextColumn();
|
|
switch (params.column_contents[i].index()) {
|
|
case 0:
|
|
std::get<0>(params.column_contents[i])();
|
|
break;
|
|
case 1:
|
|
ImGui::Text("%s", std::get<1>(params.column_contents[i]).c_str());
|
|
break;
|
|
}
|
|
}
|
|
ImGui::EndTable();
|
|
}
|
|
}
|
|
|
|
bool OpenUrl(const std::string& url) {
|
|
// if iOS
|
|
#ifdef __APPLE__
|
|
#if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
|
|
// no system call on iOS
|
|
return false;
|
|
#else
|
|
return system(("open " + url).c_str()) == 0;
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef __linux__
|
|
return system(("xdg-open " + url).c_str()) == 0;
|
|
#endif
|
|
|
|
#ifdef __windows__
|
|
return system(("start " + url).c_str()) == 0;
|
|
#endif
|
|
|
|
return false;
|
|
}
|
|
|
|
void MemoryEditorPopup(const std::string& label, std::span<uint8_t> memory) {
|
|
static bool open = false;
|
|
static MemoryEditor editor;
|
|
if (ImGui::Button("View Data")) {
|
|
open = true;
|
|
}
|
|
if (open) {
|
|
ImGui::Begin(label.c_str(), &open);
|
|
editor.DrawContents(memory.data(), memory.size());
|
|
ImGui::End();
|
|
}
|
|
}
|
|
|
|
// Custom hex input functions that properly respect width
|
|
bool InputHexByteCustom(const char* label, uint8_t* data, float input_width) {
|
|
ImGui::PushID(label);
|
|
|
|
// Create a simple hex input that respects width
|
|
char buf[8];
|
|
snprintf(buf, sizeof(buf), "%02X", *data);
|
|
|
|
ImGui::SetNextItemWidth(input_width);
|
|
bool changed = ImGui::InputText(
|
|
label, buf, sizeof(buf),
|
|
ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_AutoSelectAll);
|
|
|
|
if (changed) {
|
|
unsigned int temp;
|
|
if (sscanf(buf, "%X", &temp) == 1) {
|
|
*data = static_cast<uint8_t>(temp & 0xFF);
|
|
}
|
|
}
|
|
|
|
ImGui::PopID();
|
|
return changed;
|
|
}
|
|
|
|
bool InputHexWordCustom(const char* label, uint16_t* data, float input_width) {
|
|
ImGui::PushID(label);
|
|
|
|
// Create a simple hex input that respects width
|
|
char buf[8];
|
|
snprintf(buf, sizeof(buf), "%04X", *data);
|
|
|
|
ImGui::SetNextItemWidth(input_width);
|
|
bool changed = ImGui::InputText(
|
|
label, buf, sizeof(buf),
|
|
ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_AutoSelectAll);
|
|
|
|
if (changed) {
|
|
unsigned int temp;
|
|
if (sscanf(buf, "%X", &temp) == 1) {
|
|
*data = static_cast<uint16_t>(temp & 0xFFFF);
|
|
}
|
|
}
|
|
|
|
ImGui::PopID();
|
|
return changed;
|
|
}
|
|
|
|
} // namespace gui
|
|
} // namespace yaze
|