Title selection & bug fixes

Added title selection
Fixed controller hotkeys for multiple connected controllers
Fixed ImGUI dialog box stacking
Added a new font for ImGUI
This commit is contained in:
Adrian 2023-01-15 23:23:43 +00:00 committed by Radosław Gliński
parent 459497f0b6
commit 504fb9f205
4 changed files with 135 additions and 34 deletions

View File

@ -1101,10 +1101,13 @@ void EmulatorWindow::SetInitializingShaderStorage(bool initializing) {
}
// Notes:
// SDL and XInput both support the guide button
//
// Assumes titles do not use the guide button.
// For titles that do such as dashboards these titles could be excluded based on
// their title ID.
//
// Xbox Gamebar:
// If the Xbox Gamebar overlay is enabled Windows will consume the guide
// button's input, this can be seen using hid-demo.
//
@ -1115,6 +1118,12 @@ void EmulatorWindow::SetInitializingShaderStorage(bool initializing) {
// This is not an issue with DualShock controllers because Windows will not
// open the gamebar overlay using the PlayStation menu button.
//
// Xbox One S Controller:
// The guide button on this controller is very buggy no idea why.
// Using xinput usually registers after a double tap.
// Doesn't work at all using SDL.
// Needs more testing.
//
// Steam:
// If guide button focus is enabled steam will open.
// Steam uses BACK + GUIDE to open an On-Screen keyboard, however this is not a
@ -1139,9 +1148,13 @@ const std::map<int, EmulatorWindow::ControllerHotKey> controller_hotkey_map = {
"X + Guide = Toggle Clear Memory Page State", true)},
{X_INPUT_GAMEPAD_RIGHT_SHOULDER | X_INPUT_GAMEPAD_GUIDE,
EmulatorWindow::ControllerHotKey(
EmulatorWindow::ButtonFunctions::ClearGPUCache,
"Right Shoulder + Guide = Clear GPU Cache", true)},
{X_INPUT_GAMEPAD_LEFT_SHOULDER | X_INPUT_GAMEPAD_GUIDE,
EmulatorWindow::ControllerHotKey(
EmulatorWindow::ButtonFunctions::ToggleControllerVibration,
"Right Shoulder + Guide = Toggle Controller Vibration", true)},
"Left Shoulder + Guide = Toggle Controller Vibration", true)},
// CPU Time Scalar with no rumble feedback
{X_INPUT_GAMEPAD_DPAD_DOWN | X_INPUT_GAMEPAD_GUIDE,
@ -1161,14 +1174,21 @@ const std::map<int, EmulatorWindow::ControllerHotKey> controller_hotkey_map = {
{X_INPUT_GAMEPAD_Y, EmulatorWindow::ControllerHotKey(
EmulatorWindow::ButtonFunctions::ToggleFullscreen,
"Y = Toggle Fullscreen", true, false)},
{X_INPUT_GAMEPAD_START,
EmulatorWindow::ControllerHotKey(
EmulatorWindow::ButtonFunctions::RunPreviouslyPlayedTitle,
"Start = Run previously played title", false, false)},
{X_INPUT_GAMEPAD_START, EmulatorWindow::ControllerHotKey(
EmulatorWindow::ButtonFunctions::RunTitle,
"Start = Run Selected Title", false, false)},
{X_INPUT_GAMEPAD_BACK | X_INPUT_GAMEPAD_START,
EmulatorWindow::ControllerHotKey(
EmulatorWindow::ButtonFunctions::CloseWindow,
"Back + Start = Close Window", false, false)}};
"Back + Start = Close Window", false, false)},
{X_INPUT_GAMEPAD_DPAD_DOWN,
EmulatorWindow::ControllerHotKey(
EmulatorWindow::ButtonFunctions::IncTitleSelect,
"D-PAD Down + Guide = Title Selection +1", true, false)},
{X_INPUT_GAMEPAD_DPAD_UP,
EmulatorWindow::ControllerHotKey(
EmulatorWindow::ButtonFunctions::DecTitleSelect,
"D-PAD Up + Guide = Title Selection -1", true, false)}};
EmulatorWindow::ControllerHotKey EmulatorWindow::ProcessControllerHotkey(
int buttons) {
@ -1210,8 +1230,17 @@ EmulatorWindow::ControllerHotKey EmulatorWindow::ProcessControllerHotkey(
// Extra Sleep
xe::threading::Sleep(delay);
break;
case ButtonFunctions::RunPreviouslyPlayedTitle:
RunPreviouslyPlayedTitle();
case ButtonFunctions::RunTitle: {
if (selected_title_index == -1) selected_title_index++;
xe::X_STATUS title_success = RunTitle(
recently_launched_titles_[selected_title_index].path_to_file);
if (title_success == X_ERROR_SUCCESS) {
imgui_drawer_.get()->ClearDialogs();
}
}
// RunPreviouslyPlayedTitle();
break;
case ButtonFunctions::ClearMemoryPageState:
ToggleGPUSetting(gpu_cvar::ClearMemoryPageState);
@ -1239,12 +1268,24 @@ EmulatorWindow::ControllerHotKey EmulatorWindow::ProcessControllerHotkey(
case ButtonFunctions::CpuTimeScalarReset:
CpuTimeScalarReset();
break;
case ButtonFunctions::ClearGPUCache:
GpuClearCaches();
// Extra Sleep
xe::threading::Sleep(delay);
break;
case ButtonFunctions::ToggleControllerVibration:
ToggleControllerVibration();
// Extra Sleep
xe::threading::Sleep(delay);
break;
case ButtonFunctions::IncTitleSelect:
selected_title_index++;
break;
case ButtonFunctions::DecTitleSelect:
selected_title_index--;
break;
case ButtonFunctions::CloseWindow:
window_->RequestClose();
break;
@ -1253,12 +1294,31 @@ EmulatorWindow::ControllerHotKey EmulatorWindow::ProcessControllerHotkey(
break;
}
if (button_combination.function == ButtonFunctions::IncTitleSelect ||
button_combination.function == ButtonFunctions::DecTitleSelect) {
selected_title_index = std::clamp(
selected_title_index, 0, (int)recently_launched_titles_.size() - 1);
imgui_drawer_.get()->ClearDialogs();
// Titles may contain Unicode characters such as At Worlds End
// Must use ImGUI font that can render these Unicode characters
std::string title =
std::to_string(selected_title_index + 1) + ": " +
recently_launched_titles_[selected_title_index].title_name + "\n\n" +
controller_hotkey_map.find(X_INPUT_GAMEPAD_START)->second.pretty;
xe::ui::ImGuiDialog::ShowMessageBox(imgui_drawer_.get(), "Title Selection",
title);
}
xe::threading::Sleep(delay);
return it->second;
}
void EmulatorWindow::VibrateController(xe::hid::InputSystem* input_sys,
uint32_t user_index,
bool toggle_rumble) {
const std::chrono::milliseconds rumble_duration(100);
@ -1266,12 +1326,12 @@ void EmulatorWindow::VibrateController(xe::hid::InputSystem* input_sys,
// otherwise the rumble may fail.
auto input_lock = input_sys->lock();
X_INPUT_VIBRATION vibration;
X_INPUT_VIBRATION vibration{};
vibration.left_motor_speed = toggle_rumble ? UINT16_MAX : 0;
vibration.right_motor_speed = toggle_rumble ? UINT16_MAX : 0;
input_sys->SetState(0, &vibration);
input_sys->SetState(user_index, &vibration);
// Vibration duration
if (toggle_rumble) {
@ -1286,24 +1346,29 @@ void EmulatorWindow::GamepadHotKeys() {
auto input_sys = emulator_->input_system();
// uint8_t users = emulator_->kernel_state()->GetConnectedUsers();
// uint8_t users = input_sys->GetConnectedSlots();
if (input_sys) {
// SDL and XInput both support the guide button
while (true) {
// Block scope surrounding input_lock used to release the lock
{
auto input_lock = input_sys->lock();
for (uint32_t user_index = 0; user_index < MAX_USERS; ++user_index) {
input_sys->GetState(user_index, &state);
}
}
X_RESULT result = input_sys->GetState(user_index, &state);
// Check if the controller is connected
if (result == X_ERROR_SUCCESS) {
// Release the lock before processing the hotkey
input_lock.mutex()->unlock();
if (ProcessControllerHotkey(state.gamepad.buttons).rumble) {
// Enable Vibration
VibrateController(input_sys, true);
VibrateController(input_sys, user_index, true);
// Disable Vibration
VibrateController(input_sys, false);
VibrateController(input_sys, user_index, false);
}
}
}
xe::threading::Sleep(thread_delay);
@ -1383,6 +1448,8 @@ void EmulatorWindow::DisplayHotKeysConfig() {
"Clear Memory Page State: " +
std::string(cvars::d3d12_clear_memory_page_state ? "true\n" : "false\n");
imgui_drawer_.get()->ClearDialogs();
xe::ui::ImGuiDialog::ShowMessageBox(imgui_drawer_.get(), "Controller Hotkeys",
msg);
}
@ -1396,6 +1463,8 @@ xe::X_STATUS EmulatorWindow::RunTitle(std::filesystem::path path) {
XELOGE(log_msg);
imgui_drawer_.get()->ClearDialogs();
xe::ui::ImGuiDialog::ShowMessageBox(imgui_drawer_.get(),
"Title Launch Failed!", log_msg);

View File

@ -57,6 +57,8 @@ class EmulatorWindow {
std::unique_ptr<xe::threading::Thread> Gamepad_HotKeys_Listener;
int selected_title_index = -1;
Emulator* emulator() const { return emulator_; }
ui::WindowedAppContext& app_context() const { return app_context_; }
ui::Window* window() const { return window_.get(); }
@ -76,14 +78,17 @@ class EmulatorWindow {
// Types of button functions for hotkeys.
enum class ButtonFunctions {
ToggleFullscreen,
RunPreviouslyPlayedTitle,
RunTitle,
CpuTimeScalarSetHalf,
CpuTimeScalarSetDouble,
CpuTimeScalarReset,
ClearGPUCache,
ToggleControllerVibration,
ClearMemoryPageState,
ReadbackResolve,
CloseWindow,
IncTitleSelect,
DecTitleSelect,
Unknown
};
@ -198,7 +203,8 @@ class EmulatorWindow {
void ShowBuildCommit();
EmulatorWindow::ControllerHotKey ProcessControllerHotkey(int buttons);
void VibrateController(xe::hid::InputSystem* input_sys, bool vibrate = true);
void VibrateController(xe::hid::InputSystem* input_sys, uint32_t user_index,
bool vibrate = true);
void GamepadHotKeys();
void ToggleGPUSetting(gpu_cvar index);
bool IsUseNexusForGameBarEnabled();

View File

@ -109,13 +109,29 @@ void ImGuiDrawer::Initialize() {
ImFontConfig font_config;
font_config.OversampleH = font_config.OversampleV = 1;
font_config.PixelSnapH = true;
// https://jrgraphix.net/r/Unicode/
static const ImWchar font_glyph_ranges[] = {
0x0020,
0x00FF, // Basic Latin + Latin Supplement
0x0020, 0x00FF, // Basic Latin + Latin Supplement
0x2000, 0x206F, // General Punctuation
0,
};
io.Fonts->AddFontFromMemoryCompressedBase85TTF(
kProggyTinyCompressedDataBase85, 10.0f, &font_config, font_glyph_ranges);
kProggyTinyCompressedDataBase85, 10.0f, &font_config,
io.Fonts->GetGlyphRangesDefault());
font_config.MergeMode = true;
const char* alt_font = "C:\\Windows\\Fonts\\segoeui.ttf";
if (std::filesystem::exists(alt_font)) {
io.Fonts->AddFontFromFileTTF(alt_font, 16.0f, &font_config,
font_glyph_ranges);
} else {
XELOGW(
"Unable to load Segoe UI; General Punctuation characters will be "
"boxes");
}
// TODO(benvanik): jp font on other platforms?
// https://github.com/Koruri/kibitaki looks really good, but is 1.5MiB.
const char* jp_font_path = "C:\\Windows\\Fonts\\msgothic.ttc";
@ -328,6 +344,14 @@ void ImGuiDrawer::Draw(UIDrawContext& ui_draw_context) {
}
}
void ImGuiDrawer::ClearDialogs() {
size_t dialog_loop = 0;
while (dialog_loop < dialogs_.size()) {
RemoveDialog(dialogs_[dialog_loop++]);
}
}
void ImGuiDrawer::RenderDrawLists(ImDrawData* data,
UIDrawContext& ui_draw_context) {
ImGuiIO& io = ImGui::GetIO();

View File

@ -52,6 +52,8 @@ class ImGuiDrawer : public WindowInputListener, public UIDrawer {
void Draw(UIDrawContext& ui_draw_context) override;
void ClearDialogs();
protected:
void OnKeyDown(KeyEvent& e) override;
void OnKeyUp(KeyEvent& e) override;