teleport_panel.cpp 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. /**
  2. * The teleport module's interface. Every control writes the configuration straight to disk, so a
  3. * change made here survives the next launch without a settings edit.
  4. */
  5. #include "teleport_panel.h"
  6. #include <Windows.h>
  7. #include <array>
  8. #include <cstdio>
  9. #include <imgui.h>
  10. #include "../../../core/ui/components/toggle/ui_toggle_component.h"
  11. #include "../../teleport/teleport_settings_store.h"
  12. namespace sunrise::client::ui::teleport {
  13. namespace {
  14. /** Lowest and highest virtual keys the picker scans. Zero is not a key. */
  15. constexpr int kFirstVirtualKey = 1;
  16. constexpr int kLastVirtualKey = 254;
  17. /** Mouse buttons are skipped so a click on the picker cannot bind itself. */
  18. constexpr int kLastMouseKey = 6;
  19. /** Longest key name Windows returns, plus the null. */
  20. constexpr std::size_t kKeyNameCapacity = 64;
  21. bool g_capturing{};
  22. /**
  23. * Names one virtual key for display.
  24. * @param virtualKey Key to name, or zero for no binding.
  25. * @param output Receives the name.
  26. */
  27. void key_name(std::uint32_t virtualKey, std::array<char, kKeyNameCapacity>& output) noexcept {
  28. if (virtualKey == client::teleport::kNoKey) {
  29. (void)std::snprintf(output.data(), output.size(), "None");
  30. return;
  31. }
  32. const UINT scanCode = MapVirtualKeyW(virtualKey, MAPVK_VK_TO_VSC);
  33. std::array<wchar_t, kKeyNameCapacity> wide{};
  34. const int written = scanCode != 0 ? GetKeyNameTextW(static_cast<LONG>(scanCode << 16),
  35. wide.data(),
  36. static_cast<int>(wide.size()))
  37. : 0;
  38. if (written <= 0
  39. || WideCharToMultiByte(CP_UTF8,
  40. 0,
  41. wide.data(),
  42. written,
  43. output.data(),
  44. static_cast<int>(output.size() - 1),
  45. nullptr,
  46. nullptr)
  47. <= 0) {
  48. (void)std::snprintf(
  49. output.data(), output.size(), "Key 0x%02X", static_cast<unsigned>(virtualKey));
  50. }
  51. }
  52. /**
  53. * Takes the first key held while the picker is armed.
  54. * @param picked Receives the key, or zero when Escape clears the binding.
  55. * @return True when this frame ended the capture.
  56. */
  57. [[nodiscard]] bool capture_key(std::uint32_t& picked) noexcept {
  58. if ((GetAsyncKeyState(VK_ESCAPE) & 0x8000) != 0) {
  59. picked = client::teleport::kNoKey;
  60. return true;
  61. }
  62. for (int key = kFirstVirtualKey; key <= kLastVirtualKey; ++key) {
  63. if (key <= kLastMouseKey) {
  64. continue;
  65. }
  66. if ((GetAsyncKeyState(key) & 0x8000) != 0) {
  67. picked = static_cast<std::uint32_t>(key);
  68. return true;
  69. }
  70. }
  71. return false;
  72. }
  73. } // namespace
  74. /** Draws the teleport module inside the active Core UI frame. */
  75. void draw() noexcept {
  76. client::teleport::Settings settings = client::teleport::get();
  77. bool changed = false;
  78. ImGui::TextUnformatted("Teleport");
  79. ImGui::Separator();
  80. ImGui::TextWrapped("Teleports you forward in the facing direction. "
  81. "Cancels vertical momentum.");
  82. ImGui::Spacing();
  83. changed = core::ui::components::toggle::control("Enabled", settings.enabled) || changed;
  84. ImGui::Spacing();
  85. // One label column and one control column, so the slider and the key button share both edges.
  86. const float labelWidth =
  87. ImGui::CalcTextSize("Distance").x + ImGui::GetStyle().ItemSpacing.x * 2;
  88. const float controlWidth = ImGui::GetContentRegionAvail().x - labelWidth;
  89. ImGui::AlignTextToFramePadding();
  90. ImGui::TextUnformatted("Distance");
  91. ImGui::SameLine(labelWidth);
  92. ImGui::SetNextItemWidth(controlWidth);
  93. float distance = settings.distance;
  94. if (ImGui::SliderFloat("##distance",
  95. &distance,
  96. client::teleport::kMinimumDistance,
  97. client::teleport::kMaximumDistance,
  98. "%.0f units")) {
  99. settings.distance = distance;
  100. changed = true;
  101. }
  102. ImGui::Spacing();
  103. ImGui::AlignTextToFramePadding();
  104. ImGui::TextUnformatted("Key");
  105. ImGui::SameLine(labelWidth);
  106. if (g_capturing) {
  107. if (ImGui::Button("...", ImVec2(controlWidth, 0.0F))) {
  108. g_capturing = false;
  109. }
  110. std::uint32_t picked = client::teleport::kNoKey;
  111. if (capture_key(picked)) {
  112. settings.virtualKey = picked;
  113. g_capturing = false;
  114. changed = true;
  115. }
  116. } else {
  117. std::array<char, kKeyNameCapacity> name{};
  118. key_name(settings.virtualKey, name);
  119. if (ImGui::Button(name.data(), ImVec2(controlWidth, 0.0F))) {
  120. g_capturing = true;
  121. }
  122. }
  123. if (changed && !client::teleport::publish(settings)) {
  124. ImGui::Spacing();
  125. ImGui::TextUnformatted("value out of range, not saved");
  126. }
  127. }
  128. } // namespace sunrise::client::ui::teleport