/* Copyright 2019 flyinghead This file is part of reicast. reicast is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. reicast is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with reicast. If not, see . */ #include "gui_util.h" #include "types.h" #include "stdclass.h" #include "oslib/oslib.h" #include "oslib/directory.h" #include "oslib/storage.h" #include "oslib/http_client.h" #include "oslib/i18n.h" #include "imgui_driver.h" #include "imgui.h" #include "imgui_internal.h" #include "imgui_stdlib.h" #include "stdclass.h" #include "rend/osd.h" #include #include #include #include #include #include #include using namespace i18n; static std::string select_current_directory = "**home**"; static std::vector subfolders; static std::vector folderFiles; bool subfolders_read; extern int insetLeft, insetRight, insetTop, insetBottom; void error_popup(); namespace hostfs { bool operator<(const FileInfo& a, const FileInfo& b) { return locale()(a.name, b.name); } } void select_file_popup(const char *prompt, StringCallback callback, bool selectFile, const std::string& selectExtension) { fullScreenWindow(true); ImguiStyleVar _(ImGuiStyleVar_WindowRounding, 0); ImguiStyleVar _1(ImGuiStyleVar_FramePadding, ImVec2(4, 3)); // default if (ImGui::BeginPopup(prompt, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize )) { static std::string error_message; if (select_current_directory == "**home**") select_current_directory = hostfs::storage().getDefaultDirectory(); if (!subfolders_read) { subfolders.clear(); folderFiles.clear(); error_message.clear(); try { for (const hostfs::FileInfo& entry : hostfs::storage().listContent(select_current_directory)) { if (entry.isDirectory) { subfolders.push_back(entry); } else { std::string extension = get_file_extension(entry.name); if (selectFile) { if (extension == selectExtension) folderFiles.push_back(entry); } else if (extension == "zip" || extension == "7z" || extension == "chd" || extension == "gdi" || extension == "cdi" || extension == "cue" || (!config::HideLegacyNaomiRoms && (extension == "bin" || extension == "dat"))) folderFiles.push_back(entry); } } } catch (const hostfs::StorageException& e) { error_message = e.what(); } std::sort(subfolders.begin(), subfolders.end()); std::sort(folderFiles.begin(), folderFiles.end()); subfolders_read = true; } if (prompt != nullptr) { ImguiStyleVar _(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.f, 0.5f)); // Left ImguiStyleVar _1(ImGuiStyleVar_DisabledAlpha, 1.0f); ImGui::BeginDisabled(); ImGui::PushFont(nullptr, uiLargeFontSize()); ImGui::ButtonEx(prompt, ImVec2(-1, 0)); ImGui::PopFont(); ImGui::EndDisabled(); } std::string title; if (!error_message.empty()) title = error_message; else if (select_current_directory.empty()) title = T("Storage"); else title = select_current_directory; ImGui::Text("%s", title.c_str()); ImGui::BeginChild(ImGui::GetID("dir_list"), ImVec2(0, - uiScaled(30) - ImGui::GetStyle().ItemSpacing.y), ImGuiChildFlags_Borders, ImGuiWindowFlags_DragScrolling | ImGuiChildFlags_NavFlattened); { ImguiStyleVar _(ImGuiStyleVar_ItemSpacing, ScaledVec2(8, 20)); if (!select_current_directory.empty() && select_current_directory != "/") { if (ImGui::Selectable(T(".. Up to Parent Folder"))) { subfolders_read = false; select_current_directory = hostfs::storage().getParentPath(select_current_directory); } } for (const auto& entry : subfolders) { if (ImGui::Selectable(entry.name.c_str())) { subfolders_read = false; select_current_directory = entry.path; } } ImguiStyleColor _1(ImGuiCol_Text, { 1, 1, 1, selectFile ? 1.f : 0.3f }); for (const auto& entry : folderFiles) { if (selectFile) { if (ImGui::Selectable(entry.name.c_str())) { subfolders_read = false; if (callback(false, entry.path)) ImGui::CloseCurrentPopup(); } } else { ImGui::Text("%s", entry.name.c_str()); } } scrollWhenDraggingOnVoid(); windowDragScroll(); } ImGui::EndChild(); if (!selectFile) { if (ImGui::Button(T("Select Current Folder"), ScaledVec2(0, 30))) { if (callback(false, select_current_directory)) { subfolders_read = false; ImGui::CloseCurrentPopup(); } } ImGui::SameLine(); } if (ImGui::Button(T("Cancel"), ScaledVec2(0, 30))) { subfolders_read = false; callback(true, ""); ImGui::CloseCurrentPopup(); } error_popup(); ImGui::EndPopup(); } } // See https://github.com/ocornut/imgui/issues/3379 void scrollWhenDraggingOnVoid(ImGuiMouseButton mouse_button) { ImGuiContext& g = *ImGui::GetCurrentContext(); ImGuiWindow* window = g.CurrentWindow; while (window != nullptr && (window->Flags & ImGuiWindowFlags_ChildWindow) && !(window->Flags & ImGuiWindowFlags_DragScrolling) && window->ScrollMax.x == 0.0f && window->ScrollMax.y == 0.0f) window = window->ParentWindow; if (window == nullptr || !(window->Flags & ImGuiWindowFlags_DragScrolling)) return; bool hovered = false; bool held = false; ImGuiButtonFlags button_flags = (mouse_button == ImGuiMouseButton_Left) ? ImGuiButtonFlags_MouseButtonLeft : (mouse_button == ImGuiMouseButton_Right) ? ImGuiButtonFlags_MouseButtonRight : ImGuiButtonFlags_MouseButtonMiddle; // If nothing hovered so far in the frame (not same as IsAnyItemHovered()!) or item is disabled if (g.HoveredId == 0 || g.HoveredIdIsDisabled) { bool hoveredAllowOverlap = g.HoveredIdAllowOverlap; g.HoveredIdAllowOverlap = true; ImGuiID overlayId = window->GetID("##scrolldraggingoverlay"); ImGui::ButtonBehavior(window->Rect(), overlayId, &hovered, &held, button_flags); ImGui::KeepAliveID(overlayId); g.HoveredIdAllowOverlap = hoveredAllowOverlap; } const ImVec2& delta = ImGui::GetIO().MouseDelta; if (held && delta != ImVec2()) { window->DragScrolling = true; window->ScrollSpeed = delta; } } // Helper to display a little (?) mark which shows a tooltip when hovered. void ShowHelpMarker(const char* desc) { ImGui::TextDisabled("%s", T("(?)")); if (ImGui::IsItemHovered()) { ImGui::BeginTooltip(); ImGui::PushTextWrapPos(ImGui::GetFontSize() * 25.0f); ImGui::TextUnformatted(desc); ImGui::PopTextWrapPos(); ImGui::EndTooltip(); } } template bool OptionCheckbox(const char *name, config::Option& option, const char *help) { bool pressed; { DisabledScope scope(option.isReadOnly()); bool b = option; pressed = ImGui::Checkbox(name, &b); if (pressed) option.set(b); } if (help != nullptr) { ImGui::SameLine(); ShowHelpMarker(help); } return pressed; } template bool OptionCheckbox(const char *name, config::Option& option, const char *help); template bool OptionCheckbox(const char *name, config::Option& option, const char *help); template bool OptionSlider(const char *name, config::Option& option, int min, int max, const char *help, const char *format) { bool valueChanged; { DisabledScope scope(option.isReadOnly()); int v = option; valueChanged = ImGui::SliderInt(name, &v, min, max, format); if (valueChanged) option.set(v); } if (help != nullptr) { ImGui::SameLine(); ShowHelpMarker(help); } return valueChanged; } template bool OptionSlider(const char *name, config::Option& option, int min, int max, const char *help, const char *format); template bool OptionSlider(const char *name, config::Option& option, int min, int max, const char *help, const char *format); bool OptionArrowButtons(const char *name, config::Option& option, int min, int max, const char *help, const char *format) { const float innerSpacing = ImGui::GetStyle().ItemInnerSpacing.x; const std::string id = "##" + std::string(name); { ImguiStyleVar _(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.f, 0.5f)); // Left ImguiStyleColor _1(ImGuiCol_Button, ImGui::GetStyle().Colors[ImGuiCol_FrameBg]); const float width = ImGui::CalcItemWidth() - innerSpacing * 2.0f - ImGui::GetFrameHeight() * 2.0f; ImguiStyleVar _2(ImGuiStyleVar_DisabledAlpha, 1.0f); ImGui::BeginDisabled(); std::string value = strprintf(format, (int)option); ImGui::ButtonEx((value + id).c_str(), ImVec2(width, 0)); ImGui::EndDisabled(); } ImGui::SameLine(0.0f, innerSpacing); ImGui::PushButtonRepeat(true); bool valueChanged = false; { DisabledScope scope(option.isReadOnly()); if (ImGui::ArrowButton((id + "left").c_str(), ImGuiDir_Left)) { option.set(std::max(min, option - 1)); valueChanged = true; } ImGui::SameLine(0.0f, innerSpacing); if (ImGui::ArrowButton((id + "right").c_str(), ImGuiDir_Right)) { option.set(std::min(max, option + 1)); valueChanged = true; } } ImGui::PopButtonRepeat(); ImGui::SameLine(0.0f, innerSpacing); ImGui::Text("%s", name); if (help != nullptr) { ImGui::SameLine(); ShowHelpMarker(help); } return valueChanged; } template bool OptionRadioButton(const char *name, config::Option& option, T value, const char *help) { bool pressed; { DisabledScope scope(option.isReadOnly()); int v = (int)option; pressed = ImGui::RadioButton(name, &v, (int)value); if (pressed) option.set((T)v); } if (help != nullptr) { ImGui::SameLine(); ShowHelpMarker(help); } return pressed; } template bool OptionRadioButton(const char *name, config::Option& option, bool value, const char *help); template bool OptionRadioButton(const char *name, config::Option& option, int value, const char *help); template void OptionComboBox(const char *name, config::Option& option, const char *values[], int count, const char *help) { { DisabledScope scope(option.isReadOnly()); const char *value = option >= 0 && option < count ? values[option] : "?"; if (ImGui::BeginCombo(name, value, ImGuiComboFlags_None)) { for (int i = 0; i < count; i++) { bool is_selected = option == i; if (ImGui::Selectable(values[i], &is_selected)) option = i; if (is_selected) ImGui::SetItemDefaultFocus(); } ImGui::EndCombo(); } } if (help != nullptr) { ImGui::SameLine(); ShowHelpMarker(help); } } // Explicit template instantiations template void OptionComboBox(const char *name, config::Option& option, const char *values[], int count, const char *help); template void OptionComboBox(const char *name, config::Option& option, const char *values[], int count, const char *help); void fullScreenWindow(bool modal) { if (!modal) { ImguiStyleVar _(ImGuiStyleVar_WindowRounding, 0); ImguiStyleVar _1(ImGuiStyleVar_WindowBorderSize, 0); if (insetLeft > 0) { ImGui::SetNextWindowPos(ImVec2(0, 0)); ImGui::SetNextWindowSize(ImVec2(insetLeft, ImGui::GetIO().DisplaySize.y)); ImGui::Begin("##insetLeft", nullptr, ImGuiWindowFlags_NoDecoration); ImGui::End(); } if (insetRight > 0) { ImGui::SetNextWindowPos(ImVec2(ImGui::GetIO().DisplaySize.x - insetRight, 0)); ImGui::SetNextWindowSize(ImVec2(insetRight, ImGui::GetIO().DisplaySize.y)); ImGui::Begin("##insetRight", nullptr, ImGuiWindowFlags_NoDecoration); ImGui::End(); } if (insetTop > 0) { ImGui::SetNextWindowPos(ImVec2(0, 0)); ImGui::SetNextWindowSize(ImVec2(ImGui::GetIO().DisplaySize.x, insetTop)); ImGui::Begin("##insetTop", nullptr, ImGuiWindowFlags_NoDecoration); ImGui::End(); } if (insetBottom > 0) { ImGui::SetNextWindowPos(ImVec2(0, ImGui::GetIO().DisplaySize.y - insetBottom)); ImGui::SetNextWindowSize(ImVec2(ImGui::GetIO().DisplaySize.x, insetBottom)); ImGui::Begin("##insetBottom", nullptr, ImGuiWindowFlags_NoDecoration); ImGui::End(); } } ImGui::SetNextWindowPos(ImVec2(insetLeft, insetTop)); ImGui::SetNextWindowSize(ImVec2(ImGui::GetIO().DisplaySize.x - insetLeft - insetRight, ImGui::GetIO().DisplaySize.y - insetTop - insetBottom)); } static void computeScrollSpeed(float &v) { constexpr float friction = 3.f; if (std::abs(v) > friction) { float sign = (v > 0.f) - (v < 0.f); v -= friction * sign; } else { v = 0.f; } } void windowDragScroll() { ImGuiWindow *window = ImGui::GetCurrentWindow(); if (window->DragScrolling) { if (!ImGui::GetIO().MouseDown[ImGuiMouseButton_Left]) { computeScrollSpeed(window->ScrollSpeed.x); computeScrollSpeed(window->ScrollSpeed.y); if (window->ScrollSpeed == ImVec2()) { window->DragScrolling = false; // FIXME we should really move the mouse off-screen after a touch up and this wouldn't be necessary // the only problem is tool tips gui_set_mouse_position(-1, -1, true); } } else { ImVec2 delta = ImGui::GetMouseDragDelta(ImGuiMouseButton_Left); if (delta != ImVec2()) ImGui::ResetMouseDragDelta(); window->ScrollSpeed = delta; } if (window->DragScrolling) { ImGui::SetScrollX(window, window->Scroll.x - window->ScrollSpeed.x); ImGui::SetScrollY(window, window->Scroll.y - window->ScrollSpeed.y); } } } static void setUV(float ar, ImVec2& uv0, ImVec2& uv1) { uv0 = { 0.f, 0.f }; uv1 = { 1.f, 1.f }; if (ar > 1) { uv0.y = -(ar - 1) / 2; uv1.y = 1 + (ar - 1) / 2; } else if (ar != 0) { ar = 1 / ar; uv0.x = -(ar - 1) / 2; uv1.x = 1 + (ar - 1) / 2; } } void ImguiTexture::draw(const ImVec2& size, const ImVec4& tint_col, const ImVec4& border_col) { ImTextureID id = getId(); if (id == ImTextureID{}) ImGui::Dummy(size); else { const float ar = imguiDriver->getAspectRatio(id); ImVec2 drawSize(size); if (size.x == 0.f) drawSize.x = size.y * ar; else if (size.y == 0.f) drawSize.y = size.x / ar; ImVec2 uv0, uv1; setUV(ar / drawSize.x * drawSize.y, uv0, uv1); ImGui::Image(id, drawSize, uv0, uv1, tint_col, border_col); } } void ImguiTexture::draw(ImDrawList *drawList, const ImVec2& pos, const ImVec2& size, float alpha) { ImTextureID id = getId(); if (id == ImTextureID{}) return; const float ar = imguiDriver->getAspectRatio(id); ImVec2 uv0, uv1; setUV(ar / size.x * size.y, uv0, uv1); u32 col = alphaOverride(0xffffff, alpha); drawList->AddImage(id, pos, pos + size, uv0, uv1, col); } void ImguiTexture::draw(ImDrawList *drawList, const ImVec2& pos, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& color) { ImTextureID id = getId(); if (id == ImTextureID{}) return; u32 col = ImGui::ColorConvertFloat4ToU32(color); drawList->AddImage(id, pos, pos + size, uv0, uv1, col); } bool ImguiTexture::button(const char* str_id, const ImVec2& image_size, const std::string& title, const ImVec4& bg_col, const ImVec4& tint_col) { const std::string label = title + str_id; if (ImGui::IsRectVisible(image_size)) { ImTextureID id = getId(); if (id != ImTextureID{}) { const float ar = imguiDriver->getAspectRatio(id); const ImVec2 size = image_size - ImGui::GetStyle().FramePadding * 2; ImVec2 uv0, uv1; setUV(ar / size.x * size.y, uv0, uv1); return ImGui::ImageButton(label.c_str(), id, size, uv0, uv1, bg_col, tint_col); } } return ImGui::Button(label.c_str(), image_size); } static u8 *loadImage(const std::string& path, int& width, int& height) { FILE *file = nowide::fopen(path.c_str(), "rb"); if (file == nullptr) return nullptr; int channels; stbi_set_flip_vertically_on_load_thread(0); u8 *imgData = stbi_load_from_file(file, &width, &height, &channels, STBI_rgb_alpha); std::fclose(file); return imgData; } int ImguiFileTexture::textureLoadCount; ImTextureID ImguiFileTexture::getId() { if (path.empty()) return {}; ImTextureID id = imguiDriver->getTexture(path); if (id == ImTextureID() && textureLoadCount < 10) { textureLoadCount++; int width, height; u8 *imgData = loadImage(path, width, height); if (imgData != nullptr) { try { id = imguiDriver->updateTextureAndAspectRatio(path, imgData, width, height, nearestSampling); } catch (...) { // vulkan can throw during resizing } free(imgData); } } return id; } std::future ImguiStateTexture::asyncLoad; bool ImguiStateTexture::exists() { std::string path = hostfs::getSavestatePath(config::SavestateSlot, false); return hostfs::storage().exists(path); } ImTextureID ImguiStateTexture::getId() { std::string path = hostfs::getSavestatePath(config::SavestateSlot, false); ImTextureID texid = imguiDriver->getTexture(path); if (texid != ImTextureID()) return texid; if (asyncLoad.valid()) { if (asyncLoad.wait_for(std::chrono::seconds::zero()) == std::future_status::timeout) return {}; LoadedPic loadedPic = asyncLoad.get(); if (loadedPic.data != nullptr) { try { texid = imguiDriver->updateTextureAndAspectRatio(path, loadedPic.data, loadedPic.width, loadedPic.height, nearestSampling); } catch (...) { // vulkan can throw during resizing } free(loadedPic.data); } return texid; } asyncLoad = std::async(std::launch::async, []() { LoadedPic loadedPic{}; // load savestate info std::vector pngData; dc_getStateScreenshot(config::SavestateSlot, pngData); if (pngData.empty()) return loadedPic; int channels; stbi_set_flip_vertically_on_load_thread(0); loadedPic.data = stbi_load_from_memory(&pngData[0], pngData.size(), &loadedPic.width, &loadedPic.height, &channels, STBI_rgb_alpha); return loadedPic; }); return {}; } void ImguiStateTexture::invalidate() { if (imguiDriver) { std::string path = hostfs::getSavestatePath(config::SavestateSlot, false); imguiDriver->deleteTexture(path); } } std::array ImguiVmuTexture::Vmus { 0, 1, 2, 3, 4, 5, 6, 7 }; constexpr float VMU_WIDTH = 96.f; constexpr float VMU_HEIGHT = 64.f; constexpr float VMU_PADDING = 8.f; ImTextureID ImguiVmuTexture::getId() { if (!vmu_lcd_status[index]) return {}; if (idPath.empty()) idPath = ":vmu:" + std::to_string(index); ImTextureID texid = imguiDriver->getTexture(idPath); if (texid == ImTextureID() || vmuLastChanged != ::vmuLastChanged[index]) { try { texid = imguiDriver->updateTexture(idPath, (const u8 *)vmu_lcd_data[index], 48, 32, true); vmuLastChanged = ::vmuLastChanged[index]; } catch (...) { } } return texid; } void ImguiVmuTexture::displayVmus(const ImVec2& pos) { const ScaledVec2 size(VMU_WIDTH, VMU_HEIGHT); const float padding = uiScaled(VMU_PADDING); ImDrawList *dl = ImGui::GetForegroundDrawList(); ImVec2 cpos(pos + ScaledVec2(2.f, 0)); // 96 pixels wide + 2 * 2 -> 100 for (int i = 0; i < 8; i++) { if (!vmu_lcd_status[i]) continue; ImTextureID texid = Vmus[i].getId(); if (texid == ImTextureID()) continue; ImVec2 pos_b = cpos + size; dl->AddImage(texid, cpos, pos_b, ImVec2(0, 1), ImVec2(1, 0), 0x80ffffff); cpos.y += size.y + padding; } } void Toast::show(const std::string& title, const std::string& message, u32 durationMs) { const u64 now = getTimeMs(); std::lock_guard _{mutex}; // no start anim if still visible if (now > endTime + END_ANIM_TIME) startTime = getTimeMs(); endTime = now + durationMs; this->title = title; this->message = message; } bool Toast::draw() { const u64 now = getTimeMs(); std::lock_guard _{mutex}; if (now > endTime + END_ANIM_TIME) { title.clear(); message.clear(); } if (title.empty() && message.empty()) return false; float alpha = 1.f; if (now > endTime) // Fade out alpha = (std::cos((now - endTime) / (float)END_ANIM_TIME * (float)M_PI) + 1.f) / 2.f; const ImVec2 displaySize(ImGui::GetIO().DisplaySize); const float maxW = std::min(uiScaled(640.f), displaySize.x); ImFont *regularFont = ImGui::GetFont(); const ImVec2 titleSize = title.empty() ? ImVec2() : ImGui::GetFont()->CalcTextSizeA(uiLargeFontSize(), FLT_MAX, maxW, &title.front(), &title.back() + 1); const float regularFontSize = ImGui::GetStyle().FontSizeBase; const ImVec2 msgSize = message.empty() ? ImVec2() : regularFont->CalcTextSizeA(regularFontSize, FLT_MAX, maxW, &message.front(), &message.back() + 1); const ScaledVec2 padding(5.f, 4.f); const ScaledVec2 spacing(0.f, 2.f); ImVec2 totalSize(std::max(titleSize.x, msgSize.x), titleSize.y + msgSize.y); totalSize += padding * 2.f + spacing * (float)(!title.empty() && !message.empty()); ImVec2 pos(insetLeft, displaySize.y - totalSize.y); if (now - startTime < START_ANIM_TIME) // Slide up pos.y += totalSize.y * (std::cos((now - startTime) / (float)START_ANIM_TIME * (float)M_PI) + 1.f) / 2.f; ImDrawList *dl = ImGui::GetForegroundDrawList(); const ImU32 bg_col = alphaOverride(ImGui::GetColorU32(ImGuiCol_WindowBg), alpha / 2.f); dl->AddRectFilled(pos, pos + totalSize, bg_col, 0.f); const ImU32 col = alphaOverride(ImGui::GetColorU32(ImGuiCol_Border), alpha); dl->AddRect(pos, pos + totalSize, col, 0.f); pos += padding; if (!title.empty()) { const ImU32 col = alphaOverride(ImGui::GetColorU32(ImGuiCol_Text), alpha); dl->AddText(nullptr, uiLargeFontSize(), pos, col, &title.front(), &title.back() + 1, maxW); pos.y += spacing.y + titleSize.y; } if (!message.empty()) { const ImU32 col = alphaOverride(0xFF00FFFF, alpha); // yellow dl->AddText(regularFont, regularFontSize, pos, col, &message.front(), &message.back() + 1, maxW); } return true; } std::string middleEllipsis(const std::string& s, float width) { float tw = ImGui::CalcTextSize(s.c_str()).x; if (tw <= width) return s; char buf[5]; ImTextCharToUtf8(buf, ImGui::GetFont()->EllipsisChar); std::string ellipsis = buf; int l = s.length() / 2; int d = l; while (true) { std::string ss = s.substr(0, l / 2) + ellipsis + s.substr(s.length() - l / 2 - (l & 1)); tw = ImGui::CalcTextSize(ss.c_str()).x; if (tw == width) return ss; d /= 2; if (d == 0) return ss; if (tw > width) l -= d; else l += d; } } bool beginFrame(const char *label, const ImVec2& size_arg, ImVec2 *out_size) { using namespace ImGui; ImGuiContext& g = *GImGui; ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; const ImGuiStyle& style = g.Style; const ImVec2 label_size = CalcTextSize(label, nullptr, true); ImVec2 size = ImTrunc(CalcItemSize(size_arg, CalcItemWidth(), GetTextLineHeightWithSpacing() * 7.25f + style.FramePadding.y * 2.0f)); ImVec2 frame_size = ImVec2(size.x, ImMax(size.y, label_size.y)); ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size); ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, bb.Max); BeginGroup(); if (label_size.x > 0.0f) { ImVec2 label_pos = ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y); RenderText(label_pos, label); window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, label_pos + label_size); } const ImU32 bg_col = GetColorU32(ImGuiCol_FrameBg); window->DrawList->AddRectFilled(frame_bb.Min, frame_bb.Max, bg_col, g.Style.FrameRounding, 0); window->DC.CursorPos += style.FramePadding; PushClipRect(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding, false); if (out_size != nullptr) *out_size = frame_size - style.FramePadding * 2.f; BeginGroup(); return true; } void endFrame() { using namespace ImGui; EndGroup(); PopClipRect(); EndGroup(); } #ifdef __SWITCH__ static constexpr unsigned Flags_Multiline = 1 << 31; bool switchEditText(char *value, size_t capacity, ImGuiInputTextFlags flags, bool multiline); static int switchInputTextCallback(ImGuiInputTextCallbackData *data) { if (data->EventFlag == ImGuiInputTextFlags_CallbackAlways && (data->Flags & ImGuiInputTextFlags_ReadOnly) == 0) { data->Buf[data->BufTextLen] = '\0'; if (switchEditText(data->Buf, data->BufSize, data->Flags, (data->Flags & Flags_Multiline) != 0)) { data->BufDirty = true; data->BufTextLen = strlen(data->Buf); ImGui::ClearActiveID(); return 1; } ImGui::ClearActiveID(); } return 0; } #endif bool InputText(const char *label, std::string *str, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data) { #ifdef __SWITCH__ if ((flags & ImGuiInputTextFlags_ReadOnly) == 0) { // TODO This doesn't handle growing the string capacity dynamically str->reserve(512); return ImGui::InputText(label, str, flags | ImGuiInputTextFlags_CallbackAlways, switchInputTextCallback); } #endif return ImGui::InputText(label, str, flags, callback, user_data); } bool InputText(const char *label, char *str, size_t size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data) { #ifdef __SWITCH__ if ((flags & ImGuiInputTextFlags_ReadOnly) == 0) return ImGui::InputText(label, str, size, flags | ImGuiInputTextFlags_CallbackAlways, switchInputTextCallback); #endif return ImGui::InputText(label, str, size, flags, callback, user_data); } bool InputTextMultiline(const char* label, char* buf, size_t buf_size, const ImVec2& size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data) { #ifdef __SWITCH__ if ((flags & ImGuiInputTextFlags_ReadOnly) == 0) return ImGui::InputTextMultiline(label, buf, buf_size, size, flags | ImGuiInputTextFlags_CallbackAlways | Flags_Multiline, switchInputTextCallback); #endif return ImGui::InputTextMultiline(label, buf, buf_size, size, flags, callback, user_data); } bool TextFilter::Draw(const char* label, float width) { if (width != 0.0f) ImGui::SetNextItemWidth(width); bool value_changed = InputText(label, InputBuf, IM_COUNTOF(InputBuf)); if (value_changed) Build(); return value_changed; }