duckstation

duckstation, but archived from the revision just before upstream changed it to a proprietary software project, this version is the libre one
git clone https://git.neptards.moe/u3shit/duckstation.git
Log | Files | Refs | README | LICENSE

imgui_manager.cpp (42007B)


      1 // SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
      2 // SPDX-License-Identifier: (GPL-3.0 OR PolyForm-Strict-1.0.0)
      3 
      4 #include "imgui_manager.h"
      5 #include "gpu_device.h"
      6 #include "host.h"
      7 #include "image.h"
      8 #include "imgui_fullscreen.h"
      9 #include "imgui_glyph_ranges.inl"
     10 #include "input_manager.h"
     11 
     12 #include "common/assert.h"
     13 #include "common/easing.h"
     14 #include "common/error.h"
     15 #include "common/file_system.h"
     16 #include "common/log.h"
     17 #include "common/string_util.h"
     18 #include "common/timer.h"
     19 
     20 #include "IconsFontAwesome5.h"
     21 #include "fmt/format.h"
     22 #include "imgui.h"
     23 #include "imgui_freetype.h"
     24 #include "imgui_internal.h"
     25 
     26 #include <atomic>
     27 #include <chrono>
     28 #include <cmath>
     29 #include <deque>
     30 #include <mutex>
     31 #include <type_traits>
     32 #include <unordered_map>
     33 
     34 Log_SetChannel(ImGuiManager);
     35 
     36 namespace ImGuiManager {
     37 namespace {
     38 
     39 struct SoftwareCursor
     40 {
     41   std::string image_path;
     42   std::unique_ptr<GPUTexture> texture;
     43   u32 color;
     44   float scale;
     45   float extent_x;
     46   float extent_y;
     47   std::pair<float, float> pos;
     48 };
     49 
     50 struct OSDMessage
     51 {
     52   std::string key;
     53   std::string text;
     54   Common::Timer::Value start_time;
     55   Common::Timer::Value move_time;
     56   float duration;
     57   float target_y;
     58   float last_y;
     59 };
     60 
     61 } // namespace
     62 
     63 static_assert(std::is_same_v<WCharType, ImWchar>);
     64 
     65 static void UpdateScale();
     66 static void SetStyle();
     67 static void SetKeyMap();
     68 static bool LoadFontData();
     69 static void ReloadFontDataIfActive();
     70 static bool AddImGuiFonts(bool fullscreen_fonts);
     71 static ImFont* AddTextFont(float size);
     72 static ImFont* AddFixedFont(float size);
     73 static bool AddIconFonts(float size);
     74 static void AcquirePendingOSDMessages(Common::Timer::Value current_time);
     75 static void DrawOSDMessages(Common::Timer::Value current_time);
     76 static void CreateSoftwareCursorTextures();
     77 static void UpdateSoftwareCursorTexture(u32 index);
     78 static void DestroySoftwareCursorTextures();
     79 static void DrawSoftwareCursor(const SoftwareCursor& sc, const std::pair<float, float>& pos);
     80 
     81 static float s_global_prescale = 1.0f; // before window scale
     82 static float s_global_scale = 1.0f;
     83 
     84 static std::string s_font_path;
     85 static std::vector<WCharType> s_font_range;
     86 static std::vector<WCharType> s_emoji_range;
     87 
     88 static ImFont* s_standard_font;
     89 static ImFont* s_fixed_font;
     90 static ImFont* s_medium_font;
     91 static ImFont* s_large_font;
     92 
     93 static DynamicHeapArray<u8> s_standard_font_data;
     94 static DynamicHeapArray<u8> s_fixed_font_data;
     95 static DynamicHeapArray<u8> s_icon_fa_font_data;
     96 static DynamicHeapArray<u8> s_icon_pf_font_data;
     97 static DynamicHeapArray<u8> s_emoji_font_data;
     98 
     99 static float s_window_width;
    100 static float s_window_height;
    101 static Common::Timer s_last_render_time;
    102 
    103 // cached copies of WantCaptureKeyboard/Mouse, used to know when to dispatch events
    104 static std::atomic_bool s_imgui_wants_keyboard{false};
    105 static std::atomic_bool s_imgui_wants_mouse{false};
    106 
    107 // mapping of host key -> imgui key
    108 static std::unordered_map<u32, ImGuiKey> s_imgui_key_map;
    109 
    110 static constexpr float OSD_FADE_IN_TIME = 0.1f;
    111 static constexpr float OSD_FADE_OUT_TIME = 0.4f;
    112 
    113 static std::deque<OSDMessage> s_osd_active_messages;
    114 static std::deque<OSDMessage> s_osd_posted_messages;
    115 static std::mutex s_osd_messages_lock;
    116 static bool s_show_osd_messages = true;
    117 static bool s_scale_changed = false;
    118 
    119 static std::array<ImGuiManager::SoftwareCursor, InputManager::MAX_SOFTWARE_CURSORS> s_software_cursors = {};
    120 } // namespace ImGuiManager
    121 
    122 void ImGuiManager::SetFontPathAndRange(std::string path, std::vector<WCharType> range)
    123 {
    124   if (s_font_path == path && s_font_range == range)
    125     return;
    126 
    127   s_font_path = std::move(path);
    128   s_font_range = std::move(range);
    129   s_standard_font_data = {};
    130   ReloadFontDataIfActive();
    131 }
    132 
    133 void ImGuiManager::SetEmojiFontRange(std::vector<WCharType> range)
    134 {
    135   static constexpr size_t builtin_size = std::size(EMOJI_ICON_RANGE);
    136   const size_t runtime_size = range.size();
    137 
    138   if (runtime_size == 0)
    139   {
    140     if (s_emoji_range.empty())
    141       return;
    142 
    143     s_emoji_range = {};
    144   }
    145   else
    146   {
    147     if (!s_emoji_range.empty() && (s_emoji_range.size() - builtin_size) == range.size() &&
    148         std::memcmp(s_emoji_range.data(), range.data(), range.size() * sizeof(ImWchar)) == 0)
    149     {
    150       // no change
    151       return;
    152     }
    153 
    154     s_emoji_range = std::move(range);
    155     s_emoji_range.resize(s_emoji_range.size() + builtin_size);
    156     std::memcpy(&s_emoji_range[runtime_size], EMOJI_ICON_RANGE, sizeof(EMOJI_ICON_RANGE));
    157   }
    158 
    159   ReloadFontDataIfActive();
    160 }
    161 
    162 std::vector<ImGuiManager::WCharType> ImGuiManager::CompactFontRange(std::span<const WCharType> range)
    163 {
    164   std::vector<ImWchar> ret;
    165 
    166   for (auto it = range.begin(); it != range.end();)
    167   {
    168     auto next_it = it;
    169     ++next_it;
    170 
    171     // Combine sequential ranges.
    172     const ImWchar start_codepoint = *it;
    173     ImWchar end_codepoint = start_codepoint;
    174     while (next_it != range.end())
    175     {
    176       const ImWchar next_codepoint = *next_it;
    177       if (next_codepoint != (end_codepoint + 1))
    178         break;
    179 
    180       // Yep, include it.
    181       end_codepoint = next_codepoint;
    182       ++next_it;
    183     }
    184 
    185     ret.push_back(start_codepoint);
    186     ret.push_back(end_codepoint);
    187 
    188     it = next_it;
    189   }
    190 
    191   return ret;
    192 }
    193 
    194 void ImGuiManager::SetGlobalScale(float global_scale)
    195 {
    196   if (s_global_prescale == global_scale)
    197     return;
    198 
    199   s_global_prescale = global_scale;
    200   s_scale_changed = true;
    201 }
    202 
    203 void ImGuiManager::SetShowOSDMessages(bool enable)
    204 {
    205   if (s_show_osd_messages == enable)
    206     return;
    207 
    208   s_show_osd_messages = enable;
    209   if (!enable)
    210     Host::ClearOSDMessages();
    211 }
    212 
    213 bool ImGuiManager::Initialize(float global_scale, bool show_osd_messages, Error* error)
    214 {
    215   if (!LoadFontData())
    216   {
    217     Error::SetString(error, "Failed to load font data");
    218     return false;
    219   }
    220 
    221   s_global_prescale = global_scale;
    222   s_global_scale = std::max(g_gpu_device->GetWindowScale() * global_scale, 1.0f);
    223   s_scale_changed = false;
    224   s_show_osd_messages = show_osd_messages;
    225 
    226   ImGui::CreateContext();
    227 
    228   ImGuiIO& io = ImGui::GetIO();
    229   io.IniFilename = nullptr;
    230   io.BackendFlags |= ImGuiBackendFlags_HasGamepad | ImGuiBackendFlags_RendererHasVtxOffset;
    231   io.BackendUsingLegacyKeyArrays = 0;
    232   io.BackendUsingLegacyNavInputArray = 0;
    233   io.KeyRepeatDelay = 0.5f;
    234 #ifndef __ANDROID__
    235   // Android has no keyboard, nor are we using ImGui for any actual user-interactable windows.
    236   io.ConfigFlags |=
    237     ImGuiConfigFlags_NavEnableKeyboard | ImGuiConfigFlags_NavEnableGamepad | ImGuiConfigFlags_NoMouseCursorChange;
    238 #else
    239   io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard | ImGuiConfigFlags_NavEnableGamepad;
    240 #endif
    241 
    242   s_window_width = static_cast<float>(g_gpu_device->GetWindowWidth());
    243   s_window_height = static_cast<float>(g_gpu_device->GetWindowHeight());
    244   io.DisplayFramebufferScale = ImVec2(1, 1); // We already scale things ourselves, this would double-apply scaling
    245   io.DisplaySize = ImVec2(s_window_width, s_window_height);
    246 
    247   SetKeyMap();
    248   SetStyle();
    249 
    250   if (!AddImGuiFonts(false) || !g_gpu_device->UpdateImGuiFontTexture())
    251   {
    252     Error::SetString(error, "Failed to create ImGui font text");
    253     ImGui::DestroyContext();
    254     return false;
    255   }
    256 
    257   // don't need the font data anymore, save some memory
    258   ImGui::GetIO().Fonts->ClearTexData();
    259 
    260   NewFrame();
    261 
    262   CreateSoftwareCursorTextures();
    263   return true;
    264 }
    265 
    266 void ImGuiManager::Shutdown()
    267 {
    268   DestroySoftwareCursorTextures();
    269 
    270   if (ImGui::GetCurrentContext())
    271     ImGui::DestroyContext();
    272 
    273   s_standard_font = nullptr;
    274   s_fixed_font = nullptr;
    275   s_medium_font = nullptr;
    276   s_large_font = nullptr;
    277   ImGuiFullscreen::SetFonts(nullptr, nullptr, nullptr);
    278 }
    279 
    280 float ImGuiManager::GetWindowWidth()
    281 {
    282   return s_window_width;
    283 }
    284 
    285 float ImGuiManager::GetWindowHeight()
    286 {
    287   return s_window_height;
    288 }
    289 
    290 void ImGuiManager::WindowResized(float width, float height)
    291 {
    292   s_window_width = width;
    293   s_window_height = height;
    294   ImGui::GetIO().DisplaySize = ImVec2(width, height);
    295 
    296   // Scale might have changed as a result of window resize.
    297   RequestScaleUpdate();
    298 }
    299 
    300 void ImGuiManager::RequestScaleUpdate()
    301 {
    302   // Might need to update the scale.
    303   s_scale_changed = true;
    304 }
    305 
    306 void ImGuiManager::UpdateScale()
    307 {
    308   const float window_scale = g_gpu_device ? g_gpu_device->GetWindowScale() : 1.0f;
    309   const float scale = std::max(window_scale * s_global_prescale, 1.0f);
    310 
    311   if ((!HasFullscreenFonts() || !ImGuiFullscreen::UpdateLayoutScale()) && scale == s_global_scale)
    312     return;
    313 
    314   s_global_scale = scale;
    315 
    316   ImGui::GetStyle() = ImGuiStyle();
    317   ImGui::GetStyle().WindowMinSize = ImVec2(1.0f, 1.0f);
    318   SetStyle();
    319   ImGui::GetStyle().ScaleAllSizes(scale);
    320 
    321   if (!AddImGuiFonts(HasFullscreenFonts()))
    322     Panic("Failed to create ImGui font text");
    323 
    324   if (!g_gpu_device->UpdateImGuiFontTexture())
    325     Panic("Failed to recreate font texture after scale+resize");
    326 }
    327 
    328 void ImGuiManager::NewFrame()
    329 {
    330   ImGuiIO& io = ImGui::GetIO();
    331   io.DeltaTime = static_cast<float>(s_last_render_time.GetTimeSecondsAndReset());
    332 
    333   if (s_scale_changed)
    334   {
    335     s_scale_changed = false;
    336     UpdateScale();
    337   }
    338 
    339   ImGui::NewFrame();
    340 
    341   // Disable nav input on the implicit (Debug##Default) window. Otherwise we end up requesting keyboard
    342   // focus when there's nothing there. We use GetCurrentWindowRead() because otherwise it'll make it visible.
    343   ImGui::GetCurrentWindowRead()->Flags |= ImGuiWindowFlags_NoNavInputs;
    344   s_imgui_wants_keyboard.store(io.WantCaptureKeyboard, std::memory_order_relaxed);
    345   s_imgui_wants_mouse.store(io.WantCaptureMouse, std::memory_order_release);
    346 }
    347 
    348 void ImGuiManager::SetStyle()
    349 {
    350   ImGuiStyle& style = ImGui::GetStyle();
    351   style = ImGuiStyle();
    352   style.WindowMinSize = ImVec2(1.0f, 1.0f);
    353 
    354   ImVec4* colors = style.Colors;
    355   colors[ImGuiCol_Text] = ImVec4(0.95f, 0.96f, 0.98f, 1.00f);
    356   colors[ImGuiCol_TextDisabled] = ImVec4(0.36f, 0.42f, 0.47f, 1.00f);
    357   colors[ImGuiCol_WindowBg] = ImVec4(0.11f, 0.15f, 0.17f, 1.00f);
    358   colors[ImGuiCol_ChildBg] = ImVec4(0.15f, 0.18f, 0.22f, 1.00f);
    359   colors[ImGuiCol_PopupBg] = ImVec4(0.08f, 0.08f, 0.08f, 0.94f);
    360   colors[ImGuiCol_Border] = ImVec4(0.08f, 0.10f, 0.12f, 1.00f);
    361   colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
    362   colors[ImGuiCol_FrameBg] = ImVec4(0.20f, 0.25f, 0.29f, 1.00f);
    363   colors[ImGuiCol_FrameBgHovered] = ImVec4(0.12f, 0.20f, 0.28f, 1.00f);
    364   colors[ImGuiCol_FrameBgActive] = ImVec4(0.09f, 0.12f, 0.14f, 1.00f);
    365   colors[ImGuiCol_TitleBg] = ImVec4(0.09f, 0.12f, 0.14f, 0.65f);
    366   colors[ImGuiCol_TitleBgActive] = ImVec4(0.08f, 0.10f, 0.12f, 1.00f);
    367   colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.00f, 0.00f, 0.00f, 0.51f);
    368   colors[ImGuiCol_MenuBarBg] = ImVec4(0.15f, 0.18f, 0.22f, 1.00f);
    369   colors[ImGuiCol_ScrollbarBg] = ImVec4(0.02f, 0.02f, 0.02f, 0.39f);
    370   colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.20f, 0.25f, 0.29f, 1.00f);
    371   colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.18f, 0.22f, 0.25f, 1.00f);
    372   colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.09f, 0.21f, 0.31f, 1.00f);
    373   colors[ImGuiCol_CheckMark] = ImVec4(0.28f, 0.56f, 1.00f, 1.00f);
    374   colors[ImGuiCol_SliderGrab] = ImVec4(0.28f, 0.56f, 1.00f, 1.00f);
    375   colors[ImGuiCol_SliderGrabActive] = ImVec4(0.37f, 0.61f, 1.00f, 1.00f);
    376   colors[ImGuiCol_Button] = ImVec4(0.20f, 0.25f, 0.29f, 1.00f);
    377   colors[ImGuiCol_ButtonHovered] = ImVec4(0.33f, 0.38f, 0.46f, 1.00f);
    378   colors[ImGuiCol_ButtonActive] = ImVec4(0.27f, 0.32f, 0.38f, 1.00f);
    379   colors[ImGuiCol_Header] = ImVec4(0.20f, 0.25f, 0.29f, 0.55f);
    380   colors[ImGuiCol_HeaderHovered] = ImVec4(0.33f, 0.38f, 0.46f, 1.00f);
    381   colors[ImGuiCol_HeaderActive] = ImVec4(0.27f, 0.32f, 0.38f, 1.00f);
    382   colors[ImGuiCol_Separator] = ImVec4(0.20f, 0.25f, 0.29f, 1.00f);
    383   colors[ImGuiCol_SeparatorHovered] = ImVec4(0.33f, 0.38f, 0.46f, 1.00f);
    384   colors[ImGuiCol_SeparatorActive] = ImVec4(0.27f, 0.32f, 0.38f, 1.00f);
    385   colors[ImGuiCol_ResizeGrip] = ImVec4(0.26f, 0.59f, 0.98f, 0.25f);
    386   colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.33f, 0.38f, 0.46f, 1.00f);
    387   colors[ImGuiCol_ResizeGripActive] = ImVec4(0.27f, 0.32f, 0.38f, 1.00f);
    388   colors[ImGuiCol_Tab] = ImVec4(0.11f, 0.15f, 0.17f, 1.00f);
    389   colors[ImGuiCol_TabHovered] = ImVec4(0.33f, 0.38f, 0.46f, 1.00f);
    390   colors[ImGuiCol_TabActive] = ImVec4(0.27f, 0.32f, 0.38f, 1.00f);
    391   colors[ImGuiCol_TabUnfocused] = ImVec4(0.11f, 0.15f, 0.17f, 1.00f);
    392   colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.11f, 0.15f, 0.17f, 1.00f);
    393   colors[ImGuiCol_PlotLines] = ImVec4(0.61f, 0.61f, 0.61f, 1.00f);
    394   colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f);
    395   colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f);
    396   colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f);
    397   colors[ImGuiCol_TextSelectedBg] = ImVec4(0.26f, 0.59f, 0.98f, 0.35f);
    398   colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f);
    399   colors[ImGuiCol_NavHighlight] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);
    400   colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f);
    401   colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f);
    402   colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f);
    403 
    404   style.ScaleAllSizes(s_global_scale);
    405 }
    406 
    407 void ImGuiManager::SetKeyMap()
    408 {
    409   struct KeyMapping
    410   {
    411     ImGuiKey index;
    412     const char* name;
    413     const char* alt_name;
    414   };
    415 
    416   static constexpr KeyMapping mapping[] = {{ImGuiKey_LeftArrow, "Left", nullptr},
    417                                            {ImGuiKey_RightArrow, "Right", nullptr},
    418                                            {ImGuiKey_UpArrow, "Up", nullptr},
    419                                            {ImGuiKey_DownArrow, "Down", nullptr},
    420                                            {ImGuiKey_PageUp, "PageUp", nullptr},
    421                                            {ImGuiKey_PageDown, "PageDown", nullptr},
    422                                            {ImGuiKey_Home, "Home", nullptr},
    423                                            {ImGuiKey_End, "End", nullptr},
    424                                            {ImGuiKey_Insert, "Insert", nullptr},
    425                                            {ImGuiKey_Delete, "Delete", nullptr},
    426                                            {ImGuiKey_Backspace, "Backspace", nullptr},
    427                                            {ImGuiKey_Space, "Space", nullptr},
    428                                            {ImGuiKey_Enter, "Return", nullptr},
    429                                            {ImGuiKey_Escape, "Escape", nullptr},
    430                                            {ImGuiKey_LeftCtrl, "LeftCtrl", "Ctrl"},
    431                                            {ImGuiKey_LeftShift, "LeftShift", "Shift"},
    432                                            {ImGuiKey_LeftAlt, "LeftAlt", "Alt"},
    433                                            {ImGuiKey_LeftSuper, "LeftSuper", "Super"},
    434                                            {ImGuiKey_RightCtrl, "RightCtrl", nullptr},
    435                                            {ImGuiKey_RightShift, "RightShift", nullptr},
    436                                            {ImGuiKey_RightAlt, "RightAlt", nullptr},
    437                                            {ImGuiKey_RightSuper, "RightSuper", nullptr},
    438                                            {ImGuiKey_Menu, "Menu", nullptr},
    439                                            {ImGuiKey_0, "0", nullptr},
    440                                            {ImGuiKey_1, "1", nullptr},
    441                                            {ImGuiKey_2, "2", nullptr},
    442                                            {ImGuiKey_3, "3", nullptr},
    443                                            {ImGuiKey_4, "4", nullptr},
    444                                            {ImGuiKey_5, "5", nullptr},
    445                                            {ImGuiKey_6, "6", nullptr},
    446                                            {ImGuiKey_7, "7", nullptr},
    447                                            {ImGuiKey_8, "8", nullptr},
    448                                            {ImGuiKey_9, "9", nullptr},
    449                                            {ImGuiKey_A, "A", nullptr},
    450                                            {ImGuiKey_B, "B", nullptr},
    451                                            {ImGuiKey_C, "C", nullptr},
    452                                            {ImGuiKey_D, "D", nullptr},
    453                                            {ImGuiKey_E, "E", nullptr},
    454                                            {ImGuiKey_F, "F", nullptr},
    455                                            {ImGuiKey_G, "G", nullptr},
    456                                            {ImGuiKey_H, "H", nullptr},
    457                                            {ImGuiKey_I, "I", nullptr},
    458                                            {ImGuiKey_J, "J", nullptr},
    459                                            {ImGuiKey_K, "K", nullptr},
    460                                            {ImGuiKey_L, "L", nullptr},
    461                                            {ImGuiKey_M, "M", nullptr},
    462                                            {ImGuiKey_N, "N", nullptr},
    463                                            {ImGuiKey_O, "O", nullptr},
    464                                            {ImGuiKey_P, "P", nullptr},
    465                                            {ImGuiKey_Q, "Q", nullptr},
    466                                            {ImGuiKey_R, "R", nullptr},
    467                                            {ImGuiKey_S, "S", nullptr},
    468                                            {ImGuiKey_T, "T", nullptr},
    469                                            {ImGuiKey_U, "U", nullptr},
    470                                            {ImGuiKey_V, "V", nullptr},
    471                                            {ImGuiKey_W, "W", nullptr},
    472                                            {ImGuiKey_X, "X", nullptr},
    473                                            {ImGuiKey_Y, "Y", nullptr},
    474                                            {ImGuiKey_Z, "Z", nullptr},
    475                                            {ImGuiKey_F1, "F1", nullptr},
    476                                            {ImGuiKey_F2, "F2", nullptr},
    477                                            {ImGuiKey_F3, "F3", nullptr},
    478                                            {ImGuiKey_F4, "F4", nullptr},
    479                                            {ImGuiKey_F5, "F5", nullptr},
    480                                            {ImGuiKey_F6, "F6", nullptr},
    481                                            {ImGuiKey_F7, "F7", nullptr},
    482                                            {ImGuiKey_F8, "F8", nullptr},
    483                                            {ImGuiKey_F9, "F9", nullptr},
    484                                            {ImGuiKey_F10, "F10", nullptr},
    485                                            {ImGuiKey_F11, "F11", nullptr},
    486                                            {ImGuiKey_F12, "F12", nullptr},
    487                                            {ImGuiKey_Apostrophe, "Apostrophe", nullptr},
    488                                            {ImGuiKey_Comma, "Comma", nullptr},
    489                                            {ImGuiKey_Minus, "Minus", nullptr},
    490                                            {ImGuiKey_Period, "Period", nullptr},
    491                                            {ImGuiKey_Slash, "Slash", nullptr},
    492                                            {ImGuiKey_Semicolon, "Semicolon", nullptr},
    493                                            {ImGuiKey_Equal, "Equal", nullptr},
    494                                            {ImGuiKey_LeftBracket, "BracketLeft", nullptr},
    495                                            {ImGuiKey_Backslash, "Backslash", nullptr},
    496                                            {ImGuiKey_RightBracket, "BracketRight", nullptr},
    497                                            {ImGuiKey_GraveAccent, "QuoteLeft", nullptr},
    498                                            {ImGuiKey_CapsLock, "CapsLock", nullptr},
    499                                            {ImGuiKey_ScrollLock, "ScrollLock", nullptr},
    500                                            {ImGuiKey_NumLock, "NumLock", nullptr},
    501                                            {ImGuiKey_PrintScreen, "PrintScreen", nullptr},
    502                                            {ImGuiKey_Pause, "Pause", nullptr},
    503                                            {ImGuiKey_Keypad0, "Keypad0", nullptr},
    504                                            {ImGuiKey_Keypad1, "Keypad1", nullptr},
    505                                            {ImGuiKey_Keypad2, "Keypad2", nullptr},
    506                                            {ImGuiKey_Keypad3, "Keypad3", nullptr},
    507                                            {ImGuiKey_Keypad4, "Keypad4", nullptr},
    508                                            {ImGuiKey_Keypad5, "Keypad5", nullptr},
    509                                            {ImGuiKey_Keypad6, "Keypad6", nullptr},
    510                                            {ImGuiKey_Keypad7, "Keypad7", nullptr},
    511                                            {ImGuiKey_Keypad8, "Keypad8", nullptr},
    512                                            {ImGuiKey_Keypad9, "Keypad9", nullptr},
    513                                            {ImGuiKey_KeypadDecimal, "KeypadPeriod", nullptr},
    514                                            {ImGuiKey_KeypadDivide, "KeypadDivide", nullptr},
    515                                            {ImGuiKey_KeypadMultiply, "KeypadMultiply", nullptr},
    516                                            {ImGuiKey_KeypadSubtract, "KeypadMinus", nullptr},
    517                                            {ImGuiKey_KeypadAdd, "KeypadPlus", nullptr},
    518                                            {ImGuiKey_KeypadEnter, "KeypadReturn", nullptr},
    519                                            {ImGuiKey_KeypadEqual, "KeypadEqual", nullptr}};
    520 
    521   s_imgui_key_map.clear();
    522   for (const KeyMapping& km : mapping)
    523   {
    524     std::optional<u32> map(InputManager::ConvertHostKeyboardStringToCode(km.name));
    525     if (!map.has_value() && km.alt_name)
    526       map = InputManager::ConvertHostKeyboardStringToCode(km.alt_name);
    527     if (map.has_value())
    528       s_imgui_key_map[map.value()] = km.index;
    529   }
    530 }
    531 
    532 bool ImGuiManager::LoadFontData()
    533 {
    534   if (s_standard_font_data.empty())
    535   {
    536     std::optional<DynamicHeapArray<u8>> font_data = s_font_path.empty() ?
    537                                                       Host::ReadResourceFile("fonts/Roboto-Regular.ttf", true) :
    538                                                       FileSystem::ReadBinaryFile(s_font_path.c_str());
    539     if (!font_data.has_value())
    540       return false;
    541 
    542     s_standard_font_data = std::move(font_data.value());
    543   }
    544 
    545   if (s_fixed_font_data.empty())
    546   {
    547     std::optional<DynamicHeapArray<u8>> font_data = Host::ReadResourceFile("fonts/RobotoMono-Medium.ttf", true);
    548     if (!font_data.has_value())
    549       return false;
    550 
    551     s_fixed_font_data = std::move(font_data.value());
    552   }
    553 
    554   if (s_icon_fa_font_data.empty())
    555   {
    556     std::optional<DynamicHeapArray<u8>> font_data = Host::ReadResourceFile("fonts/fa-solid-900.ttf", true);
    557     if (!font_data.has_value())
    558       return false;
    559 
    560     s_icon_fa_font_data = std::move(font_data.value());
    561   }
    562 
    563   if (s_icon_pf_font_data.empty())
    564   {
    565     std::optional<DynamicHeapArray<u8>> font_data = Host::ReadResourceFile("fonts/promptfont.otf", true);
    566     if (!font_data.has_value())
    567       return false;
    568 
    569     s_icon_pf_font_data = std::move(font_data.value());
    570   }
    571 
    572   if (s_emoji_font_data.empty())
    573   {
    574     std::optional<DynamicHeapArray<u8>> font_data =
    575       Host::ReadCompressedResourceFile("fonts/TwitterColorEmoji-SVGinOT.ttf.zst", true);
    576     if (!font_data.has_value())
    577       return false;
    578 
    579     s_emoji_font_data = std::move(font_data.value());
    580   }
    581 
    582   return true;
    583 }
    584 
    585 ImFont* ImGuiManager::AddTextFont(float size)
    586 {
    587   ImFontConfig cfg;
    588   cfg.FontDataOwnedByAtlas = false;
    589   return ImGui::GetIO().Fonts->AddFontFromMemoryTTF(
    590     s_standard_font_data.data(), static_cast<int>(s_standard_font_data.size()), size, &cfg, s_font_range.data());
    591 }
    592 
    593 ImFont* ImGuiManager::AddFixedFont(float size)
    594 {
    595   ImFontConfig cfg;
    596   cfg.FontDataOwnedByAtlas = false;
    597   return ImGui::GetIO().Fonts->AddFontFromMemoryTTF(s_fixed_font_data.data(),
    598                                                     static_cast<int>(s_fixed_font_data.size()), size, &cfg, nullptr);
    599 }
    600 
    601 bool ImGuiManager::AddIconFonts(float size)
    602 {
    603   {
    604     ImFontConfig cfg;
    605     cfg.MergeMode = true;
    606     cfg.PixelSnapH = true;
    607     cfg.GlyphMinAdvanceX = size;
    608     cfg.GlyphMaxAdvanceX = size;
    609     cfg.FontDataOwnedByAtlas = false;
    610 
    611     if (!ImGui::GetIO().Fonts->AddFontFromMemoryTTF(
    612           s_icon_fa_font_data.data(), static_cast<int>(s_icon_fa_font_data.size()), size * 0.75f, &cfg, FA_ICON_RANGE))
    613       [[unlikely]]
    614     {
    615       return false;
    616     }
    617   }
    618 
    619   {
    620     ImFontConfig cfg;
    621     cfg.MergeMode = true;
    622     cfg.PixelSnapH = true;
    623     cfg.GlyphMinAdvanceX = size;
    624     cfg.GlyphMaxAdvanceX = size;
    625     cfg.FontDataOwnedByAtlas = false;
    626 
    627     if (!ImGui::GetIO().Fonts->AddFontFromMemoryTTF(
    628           s_icon_pf_font_data.data(), static_cast<int>(s_icon_pf_font_data.size()), size * 1.2f, &cfg, PF_ICON_RANGE))
    629       [[unlikely]]
    630     {
    631       return false;
    632     }
    633   }
    634 
    635   {
    636     ImFontConfig cfg;
    637     cfg.MergeMode = true;
    638     cfg.PixelSnapH = true;
    639     cfg.GlyphMinAdvanceX = size;
    640     cfg.GlyphMaxAdvanceX = size;
    641     cfg.FontDataOwnedByAtlas = false;
    642     cfg.FontBuilderFlags = ImGuiFreeTypeBuilderFlags_LoadColor | ImGuiFreeTypeBuilderFlags_Bitmap;
    643 
    644     if (!ImGui::GetIO().Fonts->AddFontFromMemoryTTF(
    645           s_emoji_font_data.data(), static_cast<int>(s_emoji_font_data.size()), size * 0.9f, &cfg,
    646           s_emoji_range.empty() ? EMOJI_ICON_RANGE : s_emoji_range.data())) [[unlikely]]
    647     {
    648       return false;
    649     }
    650   }
    651 
    652   return true;
    653 }
    654 
    655 bool ImGuiManager::AddImGuiFonts(bool fullscreen_fonts)
    656 {
    657   const float standard_font_size = std::ceil(18.0f * s_global_scale);
    658   const float fixed_font_size = std::ceil(15.0f * s_global_scale);
    659 
    660   ImGuiIO& io = ImGui::GetIO();
    661   io.Fonts->Clear();
    662 
    663   s_standard_font = AddTextFont(standard_font_size);
    664   if (!s_standard_font || !AddIconFonts(standard_font_size))
    665     return false;
    666 
    667   s_fixed_font = AddFixedFont(fixed_font_size);
    668   if (!s_fixed_font)
    669     return false;
    670 
    671   if (fullscreen_fonts)
    672   {
    673     const float medium_font_size = ImGuiFullscreen::LayoutScale(ImGuiFullscreen::LAYOUT_MEDIUM_FONT_SIZE);
    674     s_medium_font = AddTextFont(medium_font_size);
    675     if (!s_medium_font || !AddIconFonts(medium_font_size))
    676       return false;
    677 
    678     const float large_font_size = ImGuiFullscreen::LayoutScale(ImGuiFullscreen::LAYOUT_LARGE_FONT_SIZE);
    679     s_large_font = AddTextFont(large_font_size);
    680     if (!s_large_font || !AddIconFonts(large_font_size))
    681       return false;
    682   }
    683   else
    684   {
    685     s_medium_font = nullptr;
    686     s_large_font = nullptr;
    687   }
    688 
    689   ImGuiFullscreen::SetFonts(s_standard_font, s_medium_font, s_large_font);
    690 
    691   return io.Fonts->Build();
    692 }
    693 
    694 void ImGuiManager::ReloadFontDataIfActive()
    695 {
    696   if (!ImGui::GetCurrentContext())
    697     return;
    698 
    699   ImGui::EndFrame();
    700 
    701   if (!LoadFontData())
    702     Panic("Failed to load font data");
    703 
    704   if (!AddImGuiFonts(HasFullscreenFonts()))
    705     Panic("Failed to create ImGui font text");
    706 
    707   if (!g_gpu_device->UpdateImGuiFontTexture())
    708     Panic("Failed to recreate font texture after scale+resize");
    709 
    710   NewFrame();
    711 }
    712 
    713 bool ImGuiManager::AddFullscreenFontsIfMissing()
    714 {
    715   if (HasFullscreenFonts())
    716     return true;
    717 
    718   // can't do this in the middle of a frame
    719   ImGui::EndFrame();
    720 
    721   if (!AddImGuiFonts(true))
    722   {
    723     ERROR_LOG("Failed to lazily allocate fullscreen fonts.");
    724     AddImGuiFonts(false);
    725   }
    726 
    727   g_gpu_device->UpdateImGuiFontTexture();
    728   NewFrame();
    729 
    730   return HasFullscreenFonts();
    731 }
    732 
    733 bool ImGuiManager::HasFullscreenFonts()
    734 {
    735   return (s_medium_font && s_large_font);
    736 }
    737 
    738 void Host::AddOSDMessage(std::string message, float duration /*= 2.0f*/)
    739 {
    740   AddKeyedOSDMessage(std::string(), std::move(message), duration);
    741 }
    742 
    743 void Host::AddKeyedOSDMessage(std::string key, std::string message, float duration /* = 2.0f */)
    744 {
    745   if (!key.empty())
    746     INFO_LOG("OSD [{}]: {}", key, message);
    747   else
    748     INFO_LOG("OSD: {}", message);
    749 
    750   if (!ImGuiManager::s_show_osd_messages)
    751     return;
    752 
    753   const Common::Timer::Value current_time = Common::Timer::GetCurrentValue();
    754 
    755   ImGuiManager::OSDMessage msg;
    756   msg.key = std::move(key);
    757   msg.text = std::move(message);
    758   msg.duration = duration;
    759   msg.start_time = current_time;
    760   msg.move_time = current_time;
    761   msg.target_y = -1.0f;
    762   msg.last_y = -1.0f;
    763 
    764   std::unique_lock<std::mutex> lock(ImGuiManager::s_osd_messages_lock);
    765   ImGuiManager::s_osd_posted_messages.push_back(std::move(msg));
    766 }
    767 
    768 void Host::AddIconOSDMessage(std::string key, const char* icon, std::string message, float duration /* = 2.0f */)
    769 {
    770   return AddKeyedOSDMessage(std::move(key), fmt::format("{}  {}", icon, message), duration);
    771 }
    772 
    773 void Host::RemoveKeyedOSDMessage(std::string key)
    774 {
    775   if (!ImGuiManager::s_show_osd_messages)
    776     return;
    777 
    778   ImGuiManager::OSDMessage msg = {};
    779   msg.key = std::move(key);
    780   msg.duration = 0.0f;
    781 
    782   std::unique_lock<std::mutex> lock(ImGuiManager::s_osd_messages_lock);
    783   ImGuiManager::s_osd_posted_messages.push_back(std::move(msg));
    784 }
    785 
    786 void Host::ClearOSDMessages()
    787 {
    788   {
    789     std::unique_lock<std::mutex> lock(ImGuiManager::s_osd_messages_lock);
    790     ImGuiManager::s_osd_posted_messages.clear();
    791   }
    792 
    793   ImGuiManager::s_osd_active_messages.clear();
    794 }
    795 
    796 void ImGuiManager::AcquirePendingOSDMessages(Common::Timer::Value current_time)
    797 {
    798   std::atomic_thread_fence(std::memory_order_consume);
    799   if (s_osd_posted_messages.empty())
    800     return;
    801 
    802   std::unique_lock lock(s_osd_messages_lock);
    803   for (;;)
    804   {
    805     if (s_osd_posted_messages.empty())
    806       break;
    807 
    808     OSDMessage& new_msg = s_osd_posted_messages.front();
    809     std::deque<OSDMessage>::iterator iter;
    810     if (!new_msg.key.empty() && (iter = std::find_if(s_osd_active_messages.begin(), s_osd_active_messages.end(),
    811                                                      [&new_msg](const OSDMessage& other) {
    812                                                        return new_msg.key == other.key;
    813                                                      })) != s_osd_active_messages.end())
    814     {
    815       iter->text = std::move(new_msg.text);
    816       iter->duration = new_msg.duration;
    817 
    818       // Don't fade it in again
    819       const float time_passed =
    820         static_cast<float>(Common::Timer::ConvertValueToSeconds(current_time - iter->start_time));
    821       iter->start_time = current_time - Common::Timer::ConvertSecondsToValue(std::min(time_passed, OSD_FADE_IN_TIME));
    822     }
    823     else
    824     {
    825       s_osd_active_messages.push_back(std::move(new_msg));
    826     }
    827 
    828     s_osd_posted_messages.pop_front();
    829 
    830     static constexpr size_t MAX_ACTIVE_OSD_MESSAGES = 512;
    831     if (s_osd_active_messages.size() > MAX_ACTIVE_OSD_MESSAGES)
    832       s_osd_active_messages.pop_front();
    833   }
    834 }
    835 
    836 void ImGuiManager::DrawOSDMessages(Common::Timer::Value current_time)
    837 {
    838   static constexpr float MOVE_DURATION = 0.5f;
    839 
    840   ImFont* const font = ImGui::GetFont();
    841   const float scale = s_global_scale;
    842   const float spacing = std::ceil(6.0f * scale);
    843   const float margin = std::ceil(12.0f * scale);
    844   const float padding = std::ceil(10.0f * scale);
    845   const float rounding = std::ceil(6.0f * scale);
    846   const float max_width = s_window_width - (margin + padding) * 2.0f;
    847   float position_x = margin;
    848   float position_y = margin;
    849 
    850   auto iter = s_osd_active_messages.begin();
    851   while (iter != s_osd_active_messages.end())
    852   {
    853     OSDMessage& msg = *iter;
    854     const float time_passed = static_cast<float>(Common::Timer::ConvertValueToSeconds(current_time - msg.start_time));
    855     if (time_passed >= msg.duration)
    856     {
    857       iter = s_osd_active_messages.erase(iter);
    858       continue;
    859     }
    860 
    861     ++iter;
    862 
    863     u8 opacity;
    864     if (time_passed < OSD_FADE_IN_TIME)
    865       opacity = static_cast<u8>((time_passed / OSD_FADE_IN_TIME) * 255.0f);
    866     else if (time_passed > (msg.duration - OSD_FADE_OUT_TIME))
    867       opacity = static_cast<u8>(std::min((msg.duration - time_passed) / OSD_FADE_OUT_TIME, 1.0f) * 255.0f);
    868     else
    869       opacity = 255;
    870 
    871     const float expected_y = position_y;
    872     float actual_y = msg.last_y;
    873     if (msg.target_y != expected_y)
    874     {
    875       if (msg.last_y < 0.0f)
    876       {
    877         // First showing.
    878         msg.last_y = expected_y;
    879       }
    880       else
    881       {
    882         // We got repositioned, probably due to another message above getting removed.
    883         const float time_since_move =
    884           static_cast<float>(Common::Timer::ConvertValueToSeconds(current_time - msg.move_time));
    885         const float frac = Easing::OutExpo(time_since_move / MOVE_DURATION);
    886         msg.last_y = std::floor(msg.last_y - ((msg.last_y - msg.target_y) * frac));
    887       }
    888 
    889       msg.move_time = current_time;
    890       msg.target_y = expected_y;
    891       actual_y = msg.last_y;
    892     }
    893     else if (actual_y != expected_y)
    894     {
    895       const float time_since_move =
    896         static_cast<float>(Common::Timer::ConvertValueToSeconds(current_time - msg.move_time));
    897       if (time_since_move >= MOVE_DURATION)
    898       {
    899         msg.move_time = current_time;
    900         msg.last_y = msg.target_y;
    901         actual_y = msg.last_y;
    902       }
    903       else
    904       {
    905         const float frac = Easing::OutExpo(time_since_move / MOVE_DURATION);
    906         actual_y = std::floor(msg.last_y - ((msg.last_y - msg.target_y) * frac));
    907       }
    908     }
    909 
    910     if (actual_y >= ImGui::GetIO().DisplaySize.y)
    911       break;
    912 
    913     const ImVec2 pos(position_x, actual_y);
    914     const ImVec2 text_size(font->CalcTextSizeA(font->FontSize, max_width, max_width, msg.text.c_str(),
    915                                                msg.text.c_str() + msg.text.length()));
    916     const ImVec2 size(text_size.x + padding * 2.0f, text_size.y + padding * 2.0f);
    917     const ImVec4 text_rect(pos.x + padding, pos.y + padding, pos.x + size.x - padding, pos.y + size.y - padding);
    918 
    919     ImDrawList* dl = ImGui::GetForegroundDrawList();
    920     dl->AddRectFilled(pos, ImVec2(pos.x + size.x, pos.y + size.y), IM_COL32(0x21, 0x21, 0x21, opacity), rounding);
    921     dl->AddRect(pos, ImVec2(pos.x + size.x, pos.y + size.y), IM_COL32(0x48, 0x48, 0x48, opacity), rounding);
    922     dl->AddText(font, font->FontSize, ImVec2(text_rect.x, text_rect.y), IM_COL32(0xff, 0xff, 0xff, opacity),
    923                 msg.text.c_str(), msg.text.c_str() + msg.text.length(), max_width, &text_rect);
    924     position_y += size.y + spacing;
    925   }
    926 }
    927 
    928 void ImGuiManager::RenderOSDMessages()
    929 {
    930   const Common::Timer::Value current_time = Common::Timer::GetCurrentValue();
    931   AcquirePendingOSDMessages(current_time);
    932   DrawOSDMessages(current_time);
    933 }
    934 
    935 float ImGuiManager::GetGlobalScale()
    936 {
    937   return s_global_scale;
    938 }
    939 
    940 ImFont* ImGuiManager::GetStandardFont()
    941 {
    942   return s_standard_font;
    943 }
    944 
    945 ImFont* ImGuiManager::GetFixedFont()
    946 {
    947   return s_fixed_font;
    948 }
    949 
    950 ImFont* ImGuiManager::GetMediumFont()
    951 {
    952   AddFullscreenFontsIfMissing();
    953   return s_medium_font;
    954 }
    955 
    956 ImFont* ImGuiManager::GetLargeFont()
    957 {
    958   AddFullscreenFontsIfMissing();
    959   return s_large_font;
    960 }
    961 
    962 bool ImGuiManager::WantsTextInput()
    963 {
    964   return s_imgui_wants_keyboard.load(std::memory_order_acquire);
    965 }
    966 
    967 bool ImGuiManager::WantsMouseInput()
    968 {
    969   return s_imgui_wants_mouse.load(std::memory_order_acquire);
    970 }
    971 
    972 void ImGuiManager::AddTextInput(std::string str)
    973 {
    974   if (!ImGui::GetCurrentContext())
    975     return;
    976 
    977   if (!s_imgui_wants_keyboard.load(std::memory_order_acquire))
    978     return;
    979 
    980   ImGui::GetIO().AddInputCharactersUTF8(str.c_str());
    981 }
    982 
    983 void ImGuiManager::UpdateMousePosition(float x, float y)
    984 {
    985   if (!ImGui::GetCurrentContext())
    986     return;
    987 
    988   ImGui::GetIO().MousePos = ImVec2(x, y);
    989   std::atomic_thread_fence(std::memory_order_release);
    990 }
    991 
    992 bool ImGuiManager::ProcessPointerButtonEvent(InputBindingKey key, float value)
    993 {
    994   if (!ImGui::GetCurrentContext() || key.data >= std::size(ImGui::GetIO().MouseDown))
    995     return false;
    996 
    997   // still update state anyway
    998   ImGui::GetIO().AddMouseButtonEvent(key.data, value != 0.0f);
    999 
   1000   return s_imgui_wants_mouse.load(std::memory_order_acquire);
   1001 }
   1002 
   1003 bool ImGuiManager::ProcessPointerAxisEvent(InputBindingKey key, float value)
   1004 {
   1005   if (!ImGui::GetCurrentContext() || key.data < static_cast<u32>(InputPointerAxis::WheelX))
   1006     return false;
   1007 
   1008   // still update state anyway
   1009   const bool horizontal = (key.data == static_cast<u32>(InputPointerAxis::WheelX));
   1010   ImGui::GetIO().AddMouseWheelEvent(horizontal ? value : 0.0f, horizontal ? 0.0f : value);
   1011 
   1012   return s_imgui_wants_mouse.load(std::memory_order_acquire);
   1013 }
   1014 
   1015 bool ImGuiManager::ProcessHostKeyEvent(InputBindingKey key, float value)
   1016 {
   1017   decltype(s_imgui_key_map)::iterator iter;
   1018   if (!ImGui::GetCurrentContext() || (iter = s_imgui_key_map.find(key.data)) == s_imgui_key_map.end())
   1019     return false;
   1020 
   1021   // still update state anyway
   1022   ImGui::GetIO().AddKeyEvent(iter->second, value != 0.0);
   1023 
   1024   return s_imgui_wants_keyboard.load(std::memory_order_acquire);
   1025 }
   1026 
   1027 bool ImGuiManager::ProcessGenericInputEvent(GenericInputBinding key, float value)
   1028 {
   1029   static constexpr ImGuiKey key_map[] = {
   1030     ImGuiKey_None,             // Unknown,
   1031     ImGuiKey_GamepadDpadUp,    // DPadUp
   1032     ImGuiKey_GamepadDpadRight, // DPadRight
   1033     ImGuiKey_GamepadDpadLeft,  // DPadLeft
   1034     ImGuiKey_GamepadDpadDown,  // DPadDown
   1035     ImGuiKey_None,             // LeftStickUp
   1036     ImGuiKey_None,             // LeftStickRight
   1037     ImGuiKey_None,             // LeftStickDown
   1038     ImGuiKey_None,             // LeftStickLeft
   1039     ImGuiKey_GamepadL3,        // L3
   1040     ImGuiKey_None,             // RightStickUp
   1041     ImGuiKey_None,             // RightStickRight
   1042     ImGuiKey_None,             // RightStickDown
   1043     ImGuiKey_None,             // RightStickLeft
   1044     ImGuiKey_GamepadR3,        // R3
   1045     ImGuiKey_GamepadFaceUp,    // Triangle
   1046     ImGuiKey_GamepadFaceRight, // Circle
   1047     ImGuiKey_GamepadFaceDown,  // Cross
   1048     ImGuiKey_GamepadFaceLeft,  // Square
   1049     ImGuiKey_GamepadBack,      // Select
   1050     ImGuiKey_GamepadStart,     // Start
   1051     ImGuiKey_None,             // System
   1052     ImGuiKey_GamepadL1,        // L1
   1053     ImGuiKey_GamepadL2,        // L2
   1054     ImGuiKey_GamepadR1,        // R1
   1055     ImGuiKey_GamepadL2,        // R2
   1056   };
   1057 
   1058   if (!ImGui::GetCurrentContext())
   1059     return false;
   1060 
   1061   if (static_cast<u32>(key) >= std::size(key_map) || key_map[static_cast<u32>(key)] == ImGuiKey_None)
   1062     return false;
   1063 
   1064   ImGui::GetIO().AddKeyAnalogEvent(key_map[static_cast<u32>(key)], (value > 0.0f), value);
   1065   return s_imgui_wants_keyboard.load(std::memory_order_acquire);
   1066 }
   1067 
   1068 void ImGuiManager::CreateSoftwareCursorTextures()
   1069 {
   1070   for (u32 i = 0; i < static_cast<u32>(s_software_cursors.size()); i++)
   1071   {
   1072     if (!s_software_cursors[i].image_path.empty())
   1073       UpdateSoftwareCursorTexture(i);
   1074   }
   1075 }
   1076 
   1077 void ImGuiManager::DestroySoftwareCursorTextures()
   1078 {
   1079   for (SoftwareCursor& sc : s_software_cursors)
   1080     sc.texture.reset();
   1081 }
   1082 
   1083 void ImGuiManager::UpdateSoftwareCursorTexture(u32 index)
   1084 {
   1085   SoftwareCursor& sc = s_software_cursors[index];
   1086   if (sc.image_path.empty())
   1087   {
   1088     sc.texture.reset();
   1089     return;
   1090   }
   1091 
   1092   RGBA8Image image;
   1093   if (!image.LoadFromFile(sc.image_path.c_str()))
   1094   {
   1095     ERROR_LOG("Failed to load software cursor {} image '{}'", index, sc.image_path);
   1096     return;
   1097   }
   1098   g_gpu_device->RecycleTexture(std::move(sc.texture));
   1099   sc.texture = g_gpu_device->FetchTexture(image.GetWidth(), image.GetHeight(), 1, 1, 1, GPUTexture::Type::Texture,
   1100                                           GPUTexture::Format::RGBA8, image.GetPixels(), image.GetPitch());
   1101   if (!sc.texture)
   1102   {
   1103     ERROR_LOG("Failed to upload {}x{} software cursor {} image '{}'", image.GetWidth(), image.GetHeight(), index,
   1104               sc.image_path);
   1105     return;
   1106   }
   1107 
   1108   sc.extent_x = std::ceil(static_cast<float>(image.GetWidth()) * sc.scale * s_global_scale) / 2.0f;
   1109   sc.extent_y = std::ceil(static_cast<float>(image.GetHeight()) * sc.scale * s_global_scale) / 2.0f;
   1110 }
   1111 
   1112 void ImGuiManager::DrawSoftwareCursor(const SoftwareCursor& sc, const std::pair<float, float>& pos)
   1113 {
   1114   if (!sc.texture)
   1115     return;
   1116 
   1117   const ImVec2 min(pos.first - sc.extent_x, pos.second - sc.extent_y);
   1118   const ImVec2 max(pos.first + sc.extent_x, pos.second + sc.extent_y);
   1119 
   1120   ImDrawList* dl = ImGui::GetForegroundDrawList();
   1121 
   1122   dl->AddImage(reinterpret_cast<ImTextureID>(sc.texture.get()), min, max, ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f),
   1123                sc.color);
   1124 }
   1125 
   1126 void ImGuiManager::RenderSoftwareCursors()
   1127 {
   1128   // This one's okay to race, worst that happens is we render the wrong number of cursors for a frame.
   1129   const u32 pointer_count = InputManager::GetPointerCount();
   1130   for (u32 i = 0; i < pointer_count; i++)
   1131     DrawSoftwareCursor(s_software_cursors[i], InputManager::GetPointerAbsolutePosition(i));
   1132 
   1133   for (u32 i = InputManager::MAX_POINTER_DEVICES; i < InputManager::MAX_SOFTWARE_CURSORS; i++)
   1134     DrawSoftwareCursor(s_software_cursors[i], s_software_cursors[i].pos);
   1135 }
   1136 
   1137 void ImGuiManager::SetSoftwareCursor(u32 index, std::string image_path, float image_scale, u32 multiply_color)
   1138 {
   1139   DebugAssert(index < std::size(s_software_cursors));
   1140   SoftwareCursor& sc = s_software_cursors[index];
   1141   sc.color = multiply_color | 0xFF000000;
   1142   if (sc.image_path == image_path && sc.scale == image_scale)
   1143     return;
   1144 
   1145   const bool is_hiding_or_showing = (image_path.empty() != sc.image_path.empty());
   1146   sc.image_path = std::move(image_path);
   1147   sc.scale = image_scale;
   1148   if (g_gpu_device)
   1149     UpdateSoftwareCursorTexture(index);
   1150 
   1151   // Hide the system cursor when we activate a software cursor.
   1152   if (is_hiding_or_showing && index <= InputManager::MAX_POINTER_DEVICES)
   1153     InputManager::UpdateRelativeMouseMode();
   1154 }
   1155 
   1156 bool ImGuiManager::HasSoftwareCursor(u32 index)
   1157 {
   1158   return (index < s_software_cursors.size() && !s_software_cursors[index].image_path.empty());
   1159 }
   1160 
   1161 void ImGuiManager::ClearSoftwareCursor(u32 index)
   1162 {
   1163   SetSoftwareCursor(index, std::string(), 0.0f, 0);
   1164 }
   1165 
   1166 void ImGuiManager::SetSoftwareCursorPosition(u32 index, float pos_x, float pos_y)
   1167 {
   1168   DebugAssert(index >= InputManager::MAX_POINTER_DEVICES);
   1169   SoftwareCursor& sc = s_software_cursors[index];
   1170   sc.pos.first = pos_x;
   1171   sc.pos.second = pos_y;
   1172 }
   1173 
   1174 std::string ImGuiManager::StripIconCharacters(std::string_view str)
   1175 {
   1176   std::string result;
   1177   result.reserve(str.length());
   1178 
   1179   for (size_t offset = 0; offset < str.length();)
   1180   {
   1181     char32_t utf;
   1182     offset += StringUtil::DecodeUTF8(str, offset, &utf);
   1183 
   1184     // icon if outside BMP/SMP/TIP, or inside private use area
   1185     if (utf > 0x32FFF || (utf >= 0xE000 && utf <= 0xF8FF))
   1186       continue;
   1187 
   1188     StringUtil::EncodeAndAppendUTF8(result, utf);
   1189   }
   1190 
   1191   StringUtil::StripWhitespace(&result);
   1192 
   1193   return result;
   1194 }