[UI] Add simple support for controller usage in UI
- Disabled unnecessary hotkeys thread when it is not used - Removed problematic xam_exports static initialization - Moved xam_dialogs_shown_ and xam_nui_dialogs_shown_ to XamState
This commit is contained in:
committed by
Radosław Gliński
parent
2e9c459846
commit
9054a29483
@@ -140,15 +140,6 @@ DEFINE_int32(recent_titles_entry_amount, 10,
|
||||
"Allows user to define how many titles is saved in list of "
|
||||
"recently played titles.",
|
||||
"General");
|
||||
|
||||
namespace xe {
|
||||
namespace kernel {
|
||||
namespace xam {
|
||||
extern std::atomic<int> xam_dialogs_shown_;
|
||||
}
|
||||
} // namespace kernel
|
||||
} // namespace xe
|
||||
|
||||
namespace xe {
|
||||
namespace app {
|
||||
|
||||
@@ -275,9 +266,11 @@ void EmulatorWindow::OnEmulatorInitialized() {
|
||||
}
|
||||
|
||||
// Create a thread to listen for controller hotkeys.
|
||||
Gamepad_HotKeys_Listener =
|
||||
threading::Thread::Create({}, [&] { GamepadHotKeys(); });
|
||||
Gamepad_HotKeys_Listener->set_name("Gamepad HotKeys Listener");
|
||||
if (cvars::controller_hotkeys) {
|
||||
Gamepad_HotKeys_Listener =
|
||||
threading::Thread::Create({}, [&] { GamepadHotKeys(); });
|
||||
Gamepad_HotKeys_Listener->set_name("Gamepad HotKeys Listener");
|
||||
}
|
||||
}
|
||||
|
||||
void EmulatorWindow::EmulatorWindowListener::OnClosing(ui::UIEvent& e) {
|
||||
@@ -1462,12 +1455,12 @@ void EmulatorWindow::ToggleProfilesConfigDialog() {
|
||||
emulator_->kernel_state()->BroadcastNotification(kXNotificationSystemUI, 1);
|
||||
profile_config_dialog_ =
|
||||
std::make_unique<ProfileConfigDialog>(imgui_drawer_.get(), this);
|
||||
kernel::xam::xam_dialogs_shown_++;
|
||||
emulator_->kernel_state()->xam_state()->xam_dialogs_shown_++;
|
||||
} else {
|
||||
disable_hotkeys_ = false;
|
||||
emulator_->kernel_state()->BroadcastNotification(kXNotificationSystemUI, 0);
|
||||
profile_config_dialog_.reset();
|
||||
kernel::xam::xam_dialogs_shown_--;
|
||||
emulator_->kernel_state()->xam_state()->xam_dialogs_shown_--;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2065,7 +2058,7 @@ xe::X_STATUS EmulatorWindow::RunTitle(
|
||||
|
||||
if (profile_config_dialog_) {
|
||||
profile_config_dialog_.reset();
|
||||
kernel::xam::xam_dialogs_shown_--;
|
||||
emulator_->kernel_state()->xam_state()->xam_dialogs_shown_--;
|
||||
}
|
||||
|
||||
if (display_config_dialog_) {
|
||||
@@ -2210,7 +2203,7 @@ void EmulatorWindow::ClearDialogs() {
|
||||
}
|
||||
|
||||
imgui_drawer_.get()->ClearDialogs();
|
||||
kernel::xam::xam_dialogs_shown_ = 0;
|
||||
emulator_->kernel_state()->xam_state()->xam_dialogs_shown_ = 0;
|
||||
}
|
||||
|
||||
} // namespace app
|
||||
|
||||
@@ -279,10 +279,7 @@ X_STATUS Emulator::Setup(
|
||||
if (input_driver_factory) {
|
||||
auto input_drivers = input_driver_factory(display_window_);
|
||||
for (size_t i = 0; i < input_drivers.size(); ++i) {
|
||||
auto& input_driver = input_drivers[i];
|
||||
input_driver->set_is_active_callback(
|
||||
[]() -> bool { return !xe::kernel::xam::xeXamIsUIActive(); });
|
||||
input_system_->AddDriver(std::move(input_driver));
|
||||
input_system_->AddDriver(std::move(input_drivers[i]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -291,6 +288,9 @@ X_STATUS Emulator::Setup(
|
||||
return result;
|
||||
}
|
||||
|
||||
// Add inputSystem to UI
|
||||
imgui_drawer_->LoadInputSystem(input_system_.get());
|
||||
|
||||
XELOGI("{}: Initializing VFS...", __func__);
|
||||
// Bring up the virtual filesystem used by the kernel.
|
||||
file_system_ = std::make_unique<xe::vfs::VirtualFileSystem>();
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
namespace xe {
|
||||
namespace hid {
|
||||
|
||||
constexpr int32_t X_INPUT_GAMEPAD_LEFT_THUMB_DEADZONE = 7849;
|
||||
constexpr int32_t X_INPUT_GAMEPAD_RIGHT_THUMB_DEADZONE = 8689;
|
||||
constexpr uint8_t X_INPUT_GAMEPAD_TRIGGER_THRESHOLD = 30;
|
||||
|
||||
enum X_INPUT_CAPS {
|
||||
X_INPUT_CAPS_FFB_SUPPORTED = 0x0001,
|
||||
X_INPUT_CAPS_WIRELESS = 0x0002,
|
||||
|
||||
@@ -46,10 +46,6 @@ class InputDriver {
|
||||
|
||||
virtual InputType GetInputType() const = 0;
|
||||
|
||||
void set_is_active_callback(std::function<bool()> is_active_callback) {
|
||||
is_active_callback_ = is_active_callback;
|
||||
}
|
||||
|
||||
protected:
|
||||
explicit InputDriver(xe::ui::Window* window, size_t window_z_order)
|
||||
: window_(window), window_z_order_(window_z_order) {}
|
||||
@@ -57,14 +53,9 @@ class InputDriver {
|
||||
xe::ui::Window* window() const { return window_; }
|
||||
size_t window_z_order() const { return window_z_order_; }
|
||||
|
||||
bool is_active() const {
|
||||
return !is_active_callback_ || is_active_callback_();
|
||||
}
|
||||
|
||||
private:
|
||||
xe::ui::Window* window_;
|
||||
size_t window_z_order_;
|
||||
std::function<bool()> is_active_callback_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace hid
|
||||
|
||||
@@ -219,11 +219,7 @@ X_RESULT SDLInputDriver::GetState(uint32_t user_index,
|
||||
return X_ERROR_BAD_ARGUMENTS;
|
||||
}
|
||||
|
||||
auto is_active = this->is_active();
|
||||
|
||||
if (is_active) {
|
||||
QueueControllerUpdate();
|
||||
}
|
||||
QueueControllerUpdate();
|
||||
|
||||
auto controller = GetControllerState(user_index);
|
||||
if (!controller) {
|
||||
@@ -233,18 +229,10 @@ X_RESULT SDLInputDriver::GetState(uint32_t user_index,
|
||||
// Make sure packet_number is only incremented by 1, even if there have been
|
||||
// multiple updates between GetState calls. Also track `is_active` to
|
||||
// increment the packet number if it changed.
|
||||
if ((is_active != controller->is_active) ||
|
||||
(is_active && controller->state_changed)) {
|
||||
if (controller->is_active || controller->state_changed) {
|
||||
controller->state.packet_number++;
|
||||
controller->is_active = is_active;
|
||||
controller->state_changed = false;
|
||||
}
|
||||
std::memcpy(out_state, &controller->state, sizeof(*out_state));
|
||||
if (!is_active) {
|
||||
// Simulate an "untouched" controller. When we become active again the
|
||||
// pressed buttons aren't lost and will be visible again.
|
||||
std::memset(&out_state->gamepad, 0, sizeof(out_state->gamepad));
|
||||
}
|
||||
return X_ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -332,11 +320,7 @@ X_RESULT SDLInputDriver::GetKeystroke(uint32_t users, uint32_t flags,
|
||||
ui::VirtualKey::kXInputPadRThumbDownLeft,
|
||||
};
|
||||
|
||||
auto is_active = this->is_active();
|
||||
|
||||
if (is_active) {
|
||||
QueueControllerUpdate();
|
||||
}
|
||||
QueueControllerUpdate();
|
||||
|
||||
for (uint32_t user_index = (user_any ? 0 : users);
|
||||
user_index < (user_any ? HID_SDL_USER_COUNT : users + 1); user_index++) {
|
||||
@@ -352,10 +336,8 @@ X_RESULT SDLInputDriver::GetKeystroke(uint32_t users, uint32_t flags,
|
||||
// If input is not active (e.g. due to a dialog overlay), force buttons to
|
||||
// "unpressed". The algorithm will automatically send UP events when
|
||||
// `is_active()` goes low and DOWN events when it goes high again.
|
||||
const uint64_t curr_butts =
|
||||
is_active ? (controller->state.gamepad.buttons |
|
||||
AnalogToKeyfield(controller->state.gamepad))
|
||||
: uint64_t(0);
|
||||
const uint64_t curr_butts = controller->state.gamepad.buttons |
|
||||
AnalogToKeyfield(controller->state.gamepad);
|
||||
KeystrokeState& last = keystroke_states_.at(user_index);
|
||||
|
||||
// Handle repeating
|
||||
|
||||
@@ -169,7 +169,7 @@ X_RESULT WinKeyInputDriver::GetState(uint32_t user_index,
|
||||
int16_t thumb_rx = 0;
|
||||
int16_t thumb_ry = 0;
|
||||
|
||||
if (window()->HasFocus() && is_active()) {
|
||||
if (window()->HasFocus()) {
|
||||
bool capital = IsKeyToggled(VK_CAPITAL) || IsKeyDown(VK_SHIFT);
|
||||
for (const KeyBinding& b : key_bindings_) {
|
||||
if (((b.lowercase == b.uppercase) || (b.lowercase && !capital) ||
|
||||
@@ -283,10 +283,6 @@ X_RESULT WinKeyInputDriver::SetState(uint32_t user_index,
|
||||
|
||||
X_RESULT WinKeyInputDriver::GetKeystroke(uint32_t user_index, uint32_t flags,
|
||||
X_INPUT_KEYSTROKE* out_keystroke) {
|
||||
if (!is_active()) {
|
||||
return X_ERROR_DEVICE_NOT_CONNECTED;
|
||||
}
|
||||
|
||||
if (!IsKeyboardForUserEnabled(user_index) && !IsPassthroughEnabled()) {
|
||||
return X_ERROR_DEVICE_NOT_CONNECTED;
|
||||
}
|
||||
@@ -382,8 +378,8 @@ void WinKeyInputDriver::WinKeyWindowInputListener::OnKeyUp(ui::KeyEvent& e) {
|
||||
}
|
||||
|
||||
void WinKeyInputDriver::OnKey(ui::KeyEvent& e, bool is_down) {
|
||||
if (!is_active() || static_cast<KeyboardMode>(cvars::keyboard_mode) ==
|
||||
KeyboardMode::Disabled) {
|
||||
if (static_cast<KeyboardMode>(cvars::keyboard_mode) ==
|
||||
KeyboardMode::Disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -164,17 +164,13 @@ X_RESULT XInputInputDriver::GetState(uint32_t user_index,
|
||||
}
|
||||
|
||||
out_state->packet_number = native_state.state.dwPacketNumber;
|
||||
if (is_active()) {
|
||||
out_state->gamepad.buttons = native_state.state.Gamepad.wButtons;
|
||||
out_state->gamepad.left_trigger = native_state.state.Gamepad.bLeftTrigger;
|
||||
out_state->gamepad.right_trigger = native_state.state.Gamepad.bRightTrigger;
|
||||
out_state->gamepad.thumb_lx = native_state.state.Gamepad.sThumbLX;
|
||||
out_state->gamepad.thumb_ly = native_state.state.Gamepad.sThumbLY;
|
||||
out_state->gamepad.thumb_rx = native_state.state.Gamepad.sThumbRX;
|
||||
out_state->gamepad.thumb_ry = native_state.state.Gamepad.sThumbRY;
|
||||
} else {
|
||||
std::memset(&out_state->gamepad, 0, sizeof(out_state->gamepad));
|
||||
}
|
||||
out_state->gamepad.buttons = native_state.state.Gamepad.wButtons;
|
||||
out_state->gamepad.left_trigger = native_state.state.Gamepad.bLeftTrigger;
|
||||
out_state->gamepad.right_trigger = native_state.state.Gamepad.bRightTrigger;
|
||||
out_state->gamepad.thumb_lx = native_state.state.Gamepad.sThumbLX;
|
||||
out_state->gamepad.thumb_ly = native_state.state.Gamepad.sThumbLY;
|
||||
out_state->gamepad.thumb_rx = native_state.state.Gamepad.sThumbRX;
|
||||
out_state->gamepad.thumb_ry = native_state.state.Gamepad.sThumbRY;
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -226,10 +222,6 @@ X_RESULT XInputInputDriver::GetKeystroke(uint32_t user_index, uint32_t flags,
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!is_active()) {
|
||||
return X_ERROR_EMPTY;
|
||||
}
|
||||
|
||||
out_keystroke->virtual_key = native_keystroke.VirtualKey;
|
||||
out_keystroke->unicode = native_keystroke.Unicode;
|
||||
out_keystroke->flags = native_keystroke.Flags;
|
||||
|
||||
@@ -107,6 +107,10 @@ dword_result_t XamInputGetState_entry(dword_t user_index, dword_t flags,
|
||||
return X_ERROR_DEVICE_NOT_CONNECTED;
|
||||
}
|
||||
|
||||
if (kernel_state()->xam_state()->xam_dialogs_shown_ > 0) {
|
||||
return X_ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
// Games call this with a NULL state ptr, probably as a query.
|
||||
|
||||
uint32_t actual_user_index = user_index;
|
||||
|
||||
@@ -17,23 +17,6 @@ namespace xe {
|
||||
namespace kernel {
|
||||
namespace xam {
|
||||
|
||||
std::atomic<int> xam_dialogs_shown_ = {0};
|
||||
std::atomic<int> xam_nui_dialogs_shown_ = {0};
|
||||
|
||||
// FixMe(RodoMa92): Same hack as main_init_posix.cc:40
|
||||
// Force initialization before constructor calling, mimicking
|
||||
// Windows.
|
||||
// Ref:
|
||||
// https://reviews.llvm.org/D12689#243295
|
||||
#ifdef XE_PLATFORM_LINUX
|
||||
__attribute__((init_priority(101)))
|
||||
#endif
|
||||
static std::vector<xe::cpu::Export*>
|
||||
xam_exports(4096);
|
||||
|
||||
bool xeXamIsUIActive() { return xam_dialogs_shown_ > 0; }
|
||||
bool xeXamIsNuiUIActive() { return xam_nui_dialogs_shown_ > 0; }
|
||||
|
||||
XamModule::XamModule(Emulator* emulator, KernelState* kernel_state)
|
||||
: KernelModule(kernel_state, "xe:\\xam.xex"), loader_data_() {
|
||||
RegisterExportTable(export_resolver_);
|
||||
@@ -45,7 +28,13 @@ XamModule::XamModule(Emulator* emulator, KernelState* kernel_state)
|
||||
#undef XE_MODULE_EXPORT_GROUP
|
||||
}
|
||||
|
||||
static auto& get_xam_exports() {
|
||||
static std::vector<xe::cpu::Export*> xam_exports(4096);
|
||||
return xam_exports;
|
||||
}
|
||||
|
||||
xe::cpu::Export* RegisterExport_xam(xe::cpu::Export* export_entry) {
|
||||
auto& xam_exports = get_xam_exports();
|
||||
assert_true(export_entry->ordinal < xam_exports.size());
|
||||
xam_exports[export_entry->ordinal] = export_entry;
|
||||
return export_entry;
|
||||
@@ -58,6 +47,7 @@ static constexpr xe::cpu::Export xam_export_table[] = {
|
||||
#include "xenia/kernel/util/export_table_post.inc"
|
||||
void XamModule::RegisterExportTable(xe::cpu::ExportResolver* export_resolver) {
|
||||
assert_not_null(export_resolver);
|
||||
auto& xam_exports = get_xam_exports();
|
||||
|
||||
for (size_t i = 0; i < xe::countof(xam_export_table); ++i) {
|
||||
auto& export_entry = xam_export_table[i];
|
||||
@@ -67,7 +57,7 @@ void XamModule::RegisterExportTable(xe::cpu::ExportResolver* export_resolver) {
|
||||
const_cast<xe::cpu::Export*>(&export_entry);
|
||||
}
|
||||
}
|
||||
export_resolver->RegisterTable("xam.xex", &xam_exports);
|
||||
export_resolver->RegisterTable("xam.xex", &get_xam_exports());
|
||||
}
|
||||
|
||||
XamModule::~XamModule() {}
|
||||
|
||||
@@ -20,9 +20,6 @@ namespace xe {
|
||||
namespace kernel {
|
||||
namespace xam {
|
||||
|
||||
bool xeXamIsUIActive();
|
||||
bool xeXamIsNuiUIActive();
|
||||
|
||||
static constexpr std::string_view kXamModuleLoaderDataFileName =
|
||||
"launch_data.bin";
|
||||
|
||||
|
||||
@@ -28,10 +28,6 @@ DEFINE_bool(allow_nui_initialization, false,
|
||||
namespace xe {
|
||||
namespace kernel {
|
||||
namespace xam {
|
||||
|
||||
extern std::atomic<int> xam_dialogs_shown_;
|
||||
extern std::atomic<int> xam_nui_dialogs_shown_;
|
||||
|
||||
// https://web.cs.ucdavis.edu/~okreylos/ResDev/Kinect/MainPage.html
|
||||
|
||||
struct X_NUI_DEVICE_STATUS {
|
||||
@@ -185,7 +181,9 @@ dword_result_t XamNuiCameraSetFlags_entry(qword_t unk1, dword_t unk2) {
|
||||
}
|
||||
DECLARE_XAM_EXPORT1(XamNuiCameraSetFlags, kNone, kStub);
|
||||
|
||||
dword_result_t XamIsNuiUIActive_entry() { return xeXamIsNuiUIActive(); }
|
||||
dword_result_t XamIsNuiUIActive_entry() {
|
||||
return kernel_state()->xam_state()->xam_nui_dialogs_shown_ > 0;
|
||||
}
|
||||
DECLARE_XAM_EXPORT1(XamIsNuiUIActive, kNone, kImplemented);
|
||||
|
||||
dword_result_t XamNuiIsDeviceReady_entry() {
|
||||
@@ -367,9 +365,9 @@ dword_result_t XamShowNuiTroubleshooterUI_entry(dword_t user_index,
|
||||
"The game has indicated there is a problem with NUI (Kinect).")
|
||||
->Then(&fence);
|
||||
})) {
|
||||
++xam_dialogs_shown_;
|
||||
kernel_state()->xam_state()->xam_dialogs_shown_++;
|
||||
fence.Wait();
|
||||
--xam_dialogs_shown_;
|
||||
kernel_state()->xam_state()->xam_dialogs_shown_--;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,9 +17,6 @@ namespace xe {
|
||||
namespace kernel {
|
||||
namespace xam {
|
||||
|
||||
bool xeXamIsUIActive();
|
||||
bool xeXamIsNuiUIActive();
|
||||
|
||||
xe::cpu::Export* RegisterExport_xam(xe::cpu::Export* export_entry);
|
||||
|
||||
// Registration functions, one per file.
|
||||
|
||||
@@ -59,6 +59,9 @@ class XamState {
|
||||
|
||||
X_DASH_APP_INFO dash_app_info_ = {};
|
||||
|
||||
std::atomic<int32_t> xam_dialogs_shown_ = {0};
|
||||
std::atomic<int32_t> xam_nui_dialogs_shown_ = {0};
|
||||
|
||||
private:
|
||||
KernelState* kernel_state_;
|
||||
|
||||
|
||||
@@ -54,16 +54,24 @@ namespace xam {
|
||||
//
|
||||
// We deliberately delay the XN_SYS_UI = false notification to give games time
|
||||
// to create a listener (if they're insane enough do this).
|
||||
XamDialog::XamDialog(xe::ui::ImGuiDrawer* imgui_drawer)
|
||||
: xe::ui::ImGuiDialog(imgui_drawer) {
|
||||
kernel_state()->BroadcastNotification(kXNotificationSystemUI, true);
|
||||
kernel_state()->xam_state()->xam_dialogs_shown_++;
|
||||
}
|
||||
|
||||
extern std::atomic<int> xam_dialogs_shown_;
|
||||
XamDialog::~XamDialog() {
|
||||
kernel_state()->xam_state()->xam_dialogs_shown_--;
|
||||
if (kernel_state()->xam_state()->xam_dialogs_shown_ == 0) {
|
||||
kernel_state()->BroadcastNotification(kXNotificationSystemUI, false);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
X_RESULT xeXamDispatchDialog(T* dialog,
|
||||
std::function<X_RESULT(T*)> close_callback,
|
||||
uint32_t overlapped) {
|
||||
auto pre = []() {
|
||||
kernel_state()->BroadcastNotification(kXNotificationSystemUI, true);
|
||||
};
|
||||
auto pre = []() {};
|
||||
auto run = [dialog, close_callback]() -> X_RESULT {
|
||||
X_RESULT result;
|
||||
dialog->set_close_callback([&dialog, &result, &close_callback]() {
|
||||
@@ -74,19 +82,14 @@ X_RESULT xeXamDispatchDialog(T* dialog,
|
||||
kernel_state()->emulator()->display_window()->app_context();
|
||||
if (app_context.CallInUIThreadSynchronous(
|
||||
[&dialog, &fence]() { dialog->Then(&fence); })) {
|
||||
++xam_dialogs_shown_;
|
||||
fence.Wait();
|
||||
--xam_dialogs_shown_;
|
||||
} else {
|
||||
delete dialog;
|
||||
}
|
||||
// dialog should be deleted at this point!
|
||||
return result;
|
||||
};
|
||||
auto post = []() {
|
||||
xe::threading::Sleep(std::chrono::milliseconds(100));
|
||||
kernel_state()->BroadcastNotification(kXNotificationSystemUI, false);
|
||||
};
|
||||
auto post = []() { xe::threading::Sleep(std::chrono::milliseconds(100)); };
|
||||
if (!overlapped) {
|
||||
pre();
|
||||
auto result = run();
|
||||
@@ -102,9 +105,7 @@ template <typename T>
|
||||
X_RESULT xeXamDispatchDialogEx(
|
||||
T* dialog, std::function<X_RESULT(T*, uint32_t&, uint32_t&)> close_callback,
|
||||
uint32_t overlapped) {
|
||||
auto pre = []() {
|
||||
kernel_state()->BroadcastNotification(kXNotificationSystemUI, true);
|
||||
};
|
||||
auto pre = []() {};
|
||||
auto run = [dialog, close_callback](uint32_t& extended_error,
|
||||
uint32_t& length) -> X_RESULT {
|
||||
auto display_window = kernel_state()->emulator()->display_window();
|
||||
@@ -116,19 +117,14 @@ X_RESULT xeXamDispatchDialogEx(
|
||||
xe::threading::Fence fence;
|
||||
if (display_window->app_context().CallInUIThreadSynchronous(
|
||||
[&dialog, &fence]() { dialog->Then(&fence); })) {
|
||||
++xam_dialogs_shown_;
|
||||
fence.Wait();
|
||||
--xam_dialogs_shown_;
|
||||
} else {
|
||||
delete dialog;
|
||||
}
|
||||
// dialog should be deleted at this point!
|
||||
return result;
|
||||
};
|
||||
auto post = []() {
|
||||
xe::threading::Sleep(std::chrono::milliseconds(100));
|
||||
kernel_state()->BroadcastNotification(kXNotificationSystemUI, false);
|
||||
};
|
||||
auto post = []() { xe::threading::Sleep(std::chrono::milliseconds(100)); };
|
||||
if (!overlapped) {
|
||||
pre();
|
||||
uint32_t extended_error, length;
|
||||
@@ -144,13 +140,8 @@ X_RESULT xeXamDispatchDialogEx(
|
||||
|
||||
X_RESULT xeXamDispatchHeadless(std::function<X_RESULT()> run_callback,
|
||||
uint32_t overlapped) {
|
||||
auto pre = []() {
|
||||
kernel_state()->BroadcastNotification(kXNotificationSystemUI, true);
|
||||
};
|
||||
auto post = []() {
|
||||
xe::threading::Sleep(std::chrono::milliseconds(100));
|
||||
kernel_state()->BroadcastNotification(kXNotificationSystemUI, false);
|
||||
};
|
||||
auto pre = []() {};
|
||||
auto post = []() { xe::threading::Sleep(std::chrono::milliseconds(100)); };
|
||||
if (!overlapped) {
|
||||
pre();
|
||||
auto result = run_callback();
|
||||
@@ -166,13 +157,8 @@ X_RESULT xeXamDispatchHeadless(std::function<X_RESULT()> run_callback,
|
||||
X_RESULT xeXamDispatchHeadlessEx(
|
||||
std::function<X_RESULT(uint32_t&, uint32_t&)> run_callback,
|
||||
uint32_t overlapped) {
|
||||
auto pre = []() {
|
||||
kernel_state()->BroadcastNotification(kXNotificationSystemUI, true);
|
||||
};
|
||||
auto post = []() {
|
||||
xe::threading::Sleep(std::chrono::milliseconds(100));
|
||||
kernel_state()->BroadcastNotification(kXNotificationSystemUI, false);
|
||||
};
|
||||
auto pre = []() {};
|
||||
auto post = []() { xe::threading::Sleep(std::chrono::milliseconds(100)); };
|
||||
if (!overlapped) {
|
||||
pre();
|
||||
uint32_t extended_error, length;
|
||||
@@ -190,20 +176,14 @@ X_RESULT xeXamDispatchHeadlessEx(
|
||||
template <typename T>
|
||||
X_RESULT xeXamDispatchDialogAsync(T* dialog,
|
||||
std::function<void(T*)> close_callback) {
|
||||
kernel_state()->BroadcastNotification(kXNotificationSystemUI, true);
|
||||
++xam_dialogs_shown_;
|
||||
|
||||
// Important to pass captured vars by value here since we return from this
|
||||
// without waiting for the dialog to close so the original local vars will be
|
||||
// destroyed.
|
||||
dialog->set_close_callback([dialog, close_callback]() {
|
||||
close_callback(dialog);
|
||||
|
||||
--xam_dialogs_shown_;
|
||||
|
||||
auto run = []() -> void {
|
||||
xe::threading::Sleep(std::chrono::milliseconds(100));
|
||||
kernel_state()->BroadcastNotification(kXNotificationSystemUI, false);
|
||||
};
|
||||
|
||||
std::thread thread(run);
|
||||
@@ -214,18 +194,12 @@ X_RESULT xeXamDispatchDialogAsync(T* dialog,
|
||||
}
|
||||
|
||||
X_RESULT xeXamDispatchHeadlessAsync(std::function<void()> run_callback) {
|
||||
kernel_state()->BroadcastNotification(kXNotificationSystemUI, true);
|
||||
++xam_dialogs_shown_;
|
||||
|
||||
auto display_window = kernel_state()->emulator()->display_window();
|
||||
display_window->app_context().CallInUIThread([run_callback]() {
|
||||
run_callback();
|
||||
|
||||
--xam_dialogs_shown_;
|
||||
|
||||
auto run = []() -> void {
|
||||
xe::threading::Sleep(std::chrono::milliseconds(100));
|
||||
kernel_state()->BroadcastNotification(kXNotificationSystemUI, false);
|
||||
};
|
||||
|
||||
std::thread thread(run);
|
||||
@@ -393,7 +367,9 @@ static dword_result_t XamShowMessageBoxUi(
|
||||
return result;
|
||||
}
|
||||
|
||||
dword_result_t XamIsUIActive_entry() { return xeXamIsUIActive(); }
|
||||
dword_result_t XamIsUIActive_entry() {
|
||||
return kernel_state()->xam_state()->xam_dialogs_shown_ > 0;
|
||||
}
|
||||
DECLARE_XAM_EXPORT2(XamIsUIActive, kUI, kImplemented, kHighFrequency);
|
||||
|
||||
// https://www.se7ensins.com/forums/threads/working-xshowmessageboxui.844116/
|
||||
|
||||
@@ -25,10 +25,9 @@ class XamDialog : public xe::ui::ImGuiDialog {
|
||||
}
|
||||
|
||||
protected:
|
||||
XamDialog(xe::ui::ImGuiDrawer* imgui_drawer)
|
||||
: xe::ui::ImGuiDialog(imgui_drawer) {}
|
||||
XamDialog(xe::ui::ImGuiDrawer* imgui_drawer);
|
||||
|
||||
virtual ~XamDialog() {}
|
||||
~XamDialog();
|
||||
void OnClose() override {
|
||||
if (close_callback_) {
|
||||
close_callback_();
|
||||
|
||||
@@ -143,6 +143,9 @@ void ImGuiDrawer::Initialize() {
|
||||
internal_state_ = ImGui::CreateContext();
|
||||
ImGui::SetCurrentContext(internal_state_);
|
||||
|
||||
auto& io = ImGui::GetIO();
|
||||
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
|
||||
|
||||
const float font_size = std::max((float)cvars::font_size, 8.f);
|
||||
const float title_font_size = font_size + 6.f;
|
||||
|
||||
@@ -211,6 +214,18 @@ void ImGuiDrawer::Initialize() {
|
||||
reset_mouse_position_after_next_frame_ = false;
|
||||
}
|
||||
|
||||
void ImGuiDrawer::LoadInputSystem(hid::InputSystem* input_system) {
|
||||
if (input_system_) {
|
||||
return;
|
||||
}
|
||||
|
||||
input_system_ = input_system;
|
||||
}
|
||||
|
||||
void ImGuiDrawer::SetGuideButtonAction(std::function<void(uint8_t)> func) {
|
||||
onGuidePressFunction_ = func;
|
||||
}
|
||||
|
||||
std::optional<ImGuiKey> ImGuiDrawer::VirtualKeyToImGuiKey(VirtualKey vkey) {
|
||||
static const std::map<VirtualKey, ImGuiKey> map = {
|
||||
{ui::VirtualKey::kTab, ImGuiKey_Tab},
|
||||
@@ -568,6 +583,10 @@ void ImGuiDrawer::Draw(UIDrawContext& ui_draw_context) {
|
||||
io.DisplaySize.x = window_->GetActualPhysicalWidth() * physical_to_logical;
|
||||
io.DisplaySize.y = window_->GetActualPhysicalHeight() * physical_to_logical;
|
||||
|
||||
if (!dialogs_.empty()) {
|
||||
UpdateGamepads();
|
||||
}
|
||||
|
||||
ImGui::NewFrame();
|
||||
|
||||
assert_true(!IsDrawingDialogs());
|
||||
@@ -874,5 +893,124 @@ void ImGuiDrawer::DetachIfLastWindowRemoved() {
|
||||
ClearInput();
|
||||
}
|
||||
|
||||
void ImGuiDrawer::UpdateGamepads() {
|
||||
if (!input_system_) {
|
||||
return;
|
||||
}
|
||||
|
||||
hid::X_INPUT_CAPABILITIES caps = {};
|
||||
|
||||
bool is_gamepad_connected = false;
|
||||
|
||||
for (uint8_t i = 0; i < XUserMaxUserCount; i++) {
|
||||
if (input_system_->GetCapabilities(i, 1, &caps) == X_ERROR_SUCCESS) {
|
||||
// Special case to skip keyboard being set in gamepad mode.
|
||||
if (caps.gamepad.buttons == 0xFFFF &&
|
||||
caps.vibration.left_motor_speed == 0 &&
|
||||
caps.vibration.right_motor_speed == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
is_gamepad_connected = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto& io = GetIO();
|
||||
|
||||
if (!is_gamepad_connected) {
|
||||
io.BackendFlags &= ~ImGuiBackendFlags_HasGamepad;
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t controller_to_poke = XUserIndexNone;
|
||||
hid::X_INPUT_STATE gamepad_state;
|
||||
for (uint8_t i = 0; i < XUserMaxUserCount; i++) {
|
||||
if (input_system_->GetState(i, 1, &gamepad_state) == X_ERROR_SUCCESS) {
|
||||
if (gamepad_state.gamepad.buttons != 0) {
|
||||
controller_to_poke = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (controller_to_poke == XUserIndexNone) {
|
||||
io.ClearInputKeys();
|
||||
return;
|
||||
}
|
||||
|
||||
io.BackendFlags |= ImGuiBackendFlags_HasGamepad;
|
||||
hid::X_INPUT_GAMEPAD& gamepad = gamepad_state.gamepad;
|
||||
|
||||
// GUIDE BUTTON - More info needed
|
||||
if (gamepad_state.gamepad.buttons ==
|
||||
hid::X_INPUT_GAMEPAD_BUTTON::X_INPUT_GAMEPAD_GUIDE) {
|
||||
if (onGuidePressFunction_) {
|
||||
onGuidePressFunction_(controller_to_poke);
|
||||
}
|
||||
}
|
||||
|
||||
#define IM_SATURATE(V) (V < 0.0f ? 0.0f : V > 1.0f ? 1.0f : V)
|
||||
#define MAP_BUTTON(KEY_NO, BUTTON_ENUM) \
|
||||
{ \
|
||||
io.AddKeyEvent(KEY_NO, (gamepad.buttons & BUTTON_ENUM) != 0); \
|
||||
}
|
||||
#define MAP_ANALOG(KEY_NO, VALUE, V0, V1) \
|
||||
{ \
|
||||
float vn = (float)(VALUE - V0) / (float)(V1 - V0); \
|
||||
io.AddKeyAnalogEvent(KEY_NO, vn > 0.10f, IM_SATURATE(vn)); \
|
||||
}
|
||||
|
||||
MAP_BUTTON(ImGuiKey_GamepadStart,
|
||||
hid::X_INPUT_GAMEPAD_BUTTON::X_INPUT_GAMEPAD_START);
|
||||
MAP_BUTTON(ImGuiKey_GamepadBack,
|
||||
hid::X_INPUT_GAMEPAD_BUTTON::X_INPUT_GAMEPAD_BACK);
|
||||
MAP_BUTTON(ImGuiKey_GamepadFaceLeft,
|
||||
hid::X_INPUT_GAMEPAD_BUTTON::X_INPUT_GAMEPAD_X);
|
||||
MAP_BUTTON(ImGuiKey_GamepadFaceRight,
|
||||
hid::X_INPUT_GAMEPAD_BUTTON::X_INPUT_GAMEPAD_B);
|
||||
MAP_BUTTON(ImGuiKey_GamepadFaceUp,
|
||||
hid::X_INPUT_GAMEPAD_BUTTON::X_INPUT_GAMEPAD_Y);
|
||||
MAP_BUTTON(ImGuiKey_GamepadFaceDown,
|
||||
hid::X_INPUT_GAMEPAD_BUTTON::X_INPUT_GAMEPAD_A);
|
||||
MAP_BUTTON(ImGuiKey_GamepadDpadLeft,
|
||||
hid::X_INPUT_GAMEPAD_BUTTON::X_INPUT_GAMEPAD_DPAD_LEFT);
|
||||
MAP_BUTTON(ImGuiKey_GamepadDpadRight,
|
||||
hid::X_INPUT_GAMEPAD_BUTTON::X_INPUT_GAMEPAD_DPAD_RIGHT);
|
||||
MAP_BUTTON(ImGuiKey_GamepadDpadUp,
|
||||
hid::X_INPUT_GAMEPAD_BUTTON::X_INPUT_GAMEPAD_DPAD_UP);
|
||||
MAP_BUTTON(ImGuiKey_GamepadDpadDown,
|
||||
hid::X_INPUT_GAMEPAD_BUTTON::X_INPUT_GAMEPAD_DPAD_DOWN);
|
||||
MAP_BUTTON(ImGuiKey_GamepadL1,
|
||||
hid::X_INPUT_GAMEPAD_BUTTON::X_INPUT_GAMEPAD_LEFT_SHOULDER);
|
||||
MAP_BUTTON(ImGuiKey_GamepadR1,
|
||||
hid::X_INPUT_GAMEPAD_BUTTON::X_INPUT_GAMEPAD_RIGHT_SHOULDER);
|
||||
MAP_ANALOG(ImGuiKey_GamepadL2, gamepad.left_trigger,
|
||||
hid::X_INPUT_GAMEPAD_TRIGGER_THRESHOLD, 255);
|
||||
MAP_ANALOG(ImGuiKey_GamepadR2, gamepad.right_trigger,
|
||||
hid::X_INPUT_GAMEPAD_TRIGGER_THRESHOLD, 255);
|
||||
MAP_BUTTON(ImGuiKey_GamepadL3,
|
||||
hid::X_INPUT_GAMEPAD_BUTTON::X_INPUT_GAMEPAD_LEFT_THUMB);
|
||||
MAP_BUTTON(ImGuiKey_GamepadR3,
|
||||
hid::X_INPUT_GAMEPAD_BUTTON::X_INPUT_GAMEPAD_RIGHT_THUMB);
|
||||
MAP_ANALOG(ImGuiKey_GamepadLStickLeft, gamepad.thumb_lx,
|
||||
-hid::X_INPUT_GAMEPAD_LEFT_THUMB_DEADZONE, -32768);
|
||||
MAP_ANALOG(ImGuiKey_GamepadLStickRight, gamepad.thumb_lx,
|
||||
+hid::X_INPUT_GAMEPAD_LEFT_THUMB_DEADZONE, +32767);
|
||||
MAP_ANALOG(ImGuiKey_GamepadLStickUp, gamepad.thumb_ly,
|
||||
+hid::X_INPUT_GAMEPAD_LEFT_THUMB_DEADZONE, +32767);
|
||||
MAP_ANALOG(ImGuiKey_GamepadLStickDown, gamepad.thumb_ly,
|
||||
-hid::X_INPUT_GAMEPAD_LEFT_THUMB_DEADZONE, -32768);
|
||||
MAP_ANALOG(ImGuiKey_GamepadRStickLeft, gamepad.thumb_rx,
|
||||
-hid::X_INPUT_GAMEPAD_LEFT_THUMB_DEADZONE, -32768);
|
||||
MAP_ANALOG(ImGuiKey_GamepadRStickRight, gamepad.thumb_rx,
|
||||
+hid::X_INPUT_GAMEPAD_LEFT_THUMB_DEADZONE, +32767);
|
||||
MAP_ANALOG(ImGuiKey_GamepadRStickUp, gamepad.thumb_ry,
|
||||
+hid::X_INPUT_GAMEPAD_LEFT_THUMB_DEADZONE, +32767);
|
||||
MAP_ANALOG(ImGuiKey_GamepadRStickDown, gamepad.thumb_ry,
|
||||
-hid::X_INPUT_GAMEPAD_LEFT_THUMB_DEADZONE, -32768);
|
||||
#undef MAP_BUTTON
|
||||
#undef MAP_ANALOG
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
} // namespace xe
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "third_party/imgui/imgui.h"
|
||||
#include "xenia/hid/input_system.h"
|
||||
#include "xenia/ui/immediate_drawer.h"
|
||||
#include "xenia/ui/presenter.h"
|
||||
#include "xenia/ui/window.h"
|
||||
@@ -89,6 +90,9 @@ class ImGuiDrawer : public WindowInputListener, public UIDrawer {
|
||||
return GetIO().Fonts->Fonts[1];
|
||||
}
|
||||
|
||||
void LoadInputSystem(hid::InputSystem* input_system);
|
||||
void SetGuideButtonAction(std::function<void(uint8_t)> func);
|
||||
|
||||
protected:
|
||||
void OnKeyDown(KeyEvent& e) override;
|
||||
void OnKeyUp(KeyEvent& e) override;
|
||||
@@ -119,6 +123,7 @@ class ImGuiDrawer : public WindowInputListener, public UIDrawer {
|
||||
|
||||
bool IsDrawingDialogs() const { return dialog_loop_next_index_ != SIZE_MAX; }
|
||||
void DetachIfLastWindowRemoved();
|
||||
void UpdateGamepads();
|
||||
|
||||
std::optional<ImGuiKey> VirtualKeyToImGuiKey(VirtualKey vkey);
|
||||
|
||||
@@ -126,7 +131,9 @@ class ImGuiDrawer : public WindowInputListener, public UIDrawer {
|
||||
size_t z_order_;
|
||||
|
||||
ImGuiContext* internal_state_ = nullptr;
|
||||
hid::InputSystem* input_system_ = nullptr;
|
||||
|
||||
std::function<void(uint8_t)> onGuidePressFunction_;
|
||||
// All currently-attached dialogs that get drawn.
|
||||
std::vector<ImGuiDialog*> dialogs_;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user