improve Linux runtime behavior
This commit is contained in:
parent
32a6743554
commit
2c21bd516b
11 changed files with 349 additions and 54 deletions
|
|
@ -112,6 +112,8 @@ bool IsFinalized();
|
|||
void SaveConfig(const std::filesystem::path& config_path);
|
||||
void SaveConfigValues(const std::filesystem::path& config_path,
|
||||
std::initializer_list<std::string_view> names);
|
||||
void SaveConfigValues(const std::filesystem::path& config_path,
|
||||
const std::vector<std::string_view>& names);
|
||||
|
||||
//=============================================================================
|
||||
// Flag Registry
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ class MnkInputDriver final : public InputDriver,
|
|||
void UpdateMouseCapture();
|
||||
void SetKeyState(uint16_t vk, bool down);
|
||||
void EnqueueKeystroke(uint16_t vk_pad, bool down);
|
||||
void ClearStateLocked();
|
||||
|
||||
rex::ui::Window* attached_window_ = nullptr;
|
||||
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@ class ImGuiDialog {
|
|||
|
||||
ImGuiDrawer* imgui_drawer() const { return imgui_drawer_; }
|
||||
ImGuiIO& GetIO();
|
||||
void SetDrawActive(bool active);
|
||||
bool IsDrawActive() const { return is_draw_active_; }
|
||||
|
||||
// Closes the dialog and returns to any waiters.
|
||||
void Close();
|
||||
|
|
@ -54,6 +56,7 @@ class ImGuiDialog {
|
|||
|
||||
private:
|
||||
ImGuiDrawer* imgui_drawer_ = nullptr;
|
||||
bool is_draw_active_ = true;
|
||||
bool has_close_pending_ = false;
|
||||
std::vector<rex::thread::Fence*> waiting_fences_;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -38,12 +38,14 @@ class GTKWindow : public Window {
|
|||
GtkWidget* window() const { return window_; }
|
||||
|
||||
protected:
|
||||
uint32_t GetLatestDpiImpl() const override;
|
||||
bool OpenImpl() override;
|
||||
void RequestCloseImpl() override;
|
||||
|
||||
void ApplyNewFullscreen() override;
|
||||
void ApplyNewTitle() override;
|
||||
void ApplyNewMainMenu(MenuItem* old_main_menu) override;
|
||||
void ApplyNewCursorVisibility(CursorVisibility old_cursor_visibility) override;
|
||||
// Mouse capture seems to happen implicitly compared to Windows.
|
||||
void FocusImpl() override;
|
||||
|
||||
|
|
@ -51,6 +53,7 @@ class GTKWindow : public Window {
|
|||
void RequestPaintImpl() override;
|
||||
|
||||
private:
|
||||
void UpdateDpi(WindowDestructionReceiver* destruction_receiver = nullptr);
|
||||
void HandleSizeUpdate(WindowDestructionReceiver& destruction_receiver);
|
||||
// For updating multiple factors that may influence the window size at once,
|
||||
// without handling the configure event multiple times (that may not only
|
||||
|
|
@ -64,6 +67,9 @@ class GTKWindow : public Window {
|
|||
bool HandleKeyboard(GdkEventKey* event, WindowDestructionReceiver& destruction_receiver);
|
||||
gboolean WindowEventHandler(GdkEvent* event);
|
||||
static gboolean WindowEventHandlerThunk(GtkWidget* widget, GdkEvent* event, gpointer user_data);
|
||||
void ApplyCursor();
|
||||
void SetCursorAutoHideTimer();
|
||||
static gboolean AutoHideCursorTimerCallback(gpointer user_data);
|
||||
|
||||
// Handling events related specifically to the drawing (client) area.
|
||||
gboolean DrawingAreaEventHandler(GdkEvent* event);
|
||||
|
|
@ -75,6 +81,10 @@ class GTKWindow : public Window {
|
|||
GtkWidget* window_ = nullptr;
|
||||
GtkWidget* box_ = nullptr;
|
||||
GtkWidget* drawing_area_ = nullptr;
|
||||
GdkCursor* blank_cursor_ = nullptr;
|
||||
guint cursor_auto_hide_timer_ = 0;
|
||||
bool cursor_currently_auto_hidden_ = false;
|
||||
uint32_t dpi_ = 96;
|
||||
|
||||
uint32_t batched_size_update_depth_ = 0;
|
||||
bool batched_size_update_contained_configure_ = false;
|
||||
|
|
|
|||
|
|
@ -606,25 +606,16 @@ bool IsFinalized() {
|
|||
void SaveConfig(const std::filesystem::path& config_path) {
|
||||
try {
|
||||
toml::table config;
|
||||
if (std::filesystem::exists(config_path)) {
|
||||
config = toml::parse_file(config_path.string());
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard lock(GetRegistryMutex());
|
||||
bool modified = false;
|
||||
for (const auto& entry : GetRegistryStorage()) {
|
||||
if (entry.getter() == entry.default_value) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string value = entry.getter();
|
||||
modified |= WriteConfigValue(config, entry, value);
|
||||
}
|
||||
|
||||
if (!modified) {
|
||||
REXLOG_DEBUG("SaveConfig: no modified flags to save");
|
||||
return;
|
||||
WriteConfigValue(config, entry, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -638,6 +629,11 @@ void SaveConfig(const std::filesystem::path& config_path) {
|
|||
|
||||
void SaveConfigValues(const std::filesystem::path& config_path,
|
||||
std::initializer_list<std::string_view> names) {
|
||||
SaveConfigValues(config_path, std::vector<std::string_view>(names));
|
||||
}
|
||||
|
||||
void SaveConfigValues(const std::filesystem::path& config_path,
|
||||
const std::vector<std::string_view>& names) {
|
||||
try {
|
||||
toml::table config;
|
||||
if (std::filesystem::exists(config_path)) {
|
||||
|
|
|
|||
|
|
@ -22,8 +22,20 @@
|
|||
#include <rex/input/sdl/sdl_input_driver.h>
|
||||
#include <rex/input/xinput/xinput_input_driver.h>
|
||||
#include <rex/logging.h>
|
||||
#include <rex/platform.h>
|
||||
|
||||
REXCVAR_DEFINE_STRING(input_backend, "xinput", "Input", "Input backend: sdl, xinput")
|
||||
namespace {
|
||||
|
||||
constexpr const char* kDefaultInputBackend =
|
||||
#if REX_PLATFORM_WIN32
|
||||
"xinput";
|
||||
#else
|
||||
"sdl";
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
REXCVAR_DEFINE_STRING(input_backend, kDefaultInputBackend, "Input", "Input backend: sdl, xinput")
|
||||
.allowed({"sdl", "xinput"});
|
||||
|
||||
REXCVAR_DEFINE_BOOL(guide_button, false, "Input", "Enable guide button pass-through");
|
||||
|
|
|
|||
|
|
@ -106,6 +106,12 @@ bool MnkInputDriver::IsEnabled() const {
|
|||
return REXCVAR_GET(mnk_mode);
|
||||
}
|
||||
|
||||
void MnkInputDriver::ClearStateLocked() {
|
||||
std::memset(key_down_, 0, sizeof(key_down_));
|
||||
mouse_dx_ = 0;
|
||||
mouse_dy_ = 0;
|
||||
}
|
||||
|
||||
static bool IsBindPressed(const bool (&key_down)[256], const std::string& cvar_val) {
|
||||
VirtualKey vk = rex::ui::ParseVirtualKey(cvar_val);
|
||||
if (vk == VirtualKey::kNone)
|
||||
|
|
@ -138,13 +144,20 @@ X_RESULT MnkInputDriver::GetCapabilities(uint32_t user_index, uint32_t flags,
|
|||
}
|
||||
|
||||
X_RESULT MnkInputDriver::GetState(uint32_t user_index, X_INPUT_STATE* out_state) {
|
||||
if (!IsEnabled() || user_index != UserIndex()) {
|
||||
if (!IsEnabled()) {
|
||||
std::lock_guard lock(state_mutex_);
|
||||
ClearStateLocked();
|
||||
return X_ERROR_DEVICE_NOT_CONNECTED;
|
||||
}
|
||||
if (user_index != UserIndex()) {
|
||||
return X_ERROR_DEVICE_NOT_CONNECTED;
|
||||
}
|
||||
|
||||
UpdateMouseCapture();
|
||||
|
||||
if (!is_active() || !has_focus_) {
|
||||
std::lock_guard lock(state_mutex_);
|
||||
ClearStateLocked();
|
||||
if (out_state) {
|
||||
std::memset(out_state, 0, sizeof(*out_state));
|
||||
out_state->packet_number = packet_number_;
|
||||
|
|
@ -235,10 +248,25 @@ X_RESULT MnkInputDriver::SetState(uint32_t user_index, X_INPUT_VIBRATION* vibrat
|
|||
|
||||
X_RESULT MnkInputDriver::GetKeystroke(uint32_t user_index, uint32_t flags,
|
||||
X_INPUT_KEYSTROKE* out_keystroke) {
|
||||
if (!IsEnabled() || user_index != UserIndex()) {
|
||||
if (!IsEnabled()) {
|
||||
std::lock_guard lock(state_mutex_);
|
||||
ClearStateLocked();
|
||||
while (!keystroke_queue_.empty()) {
|
||||
keystroke_queue_.pop();
|
||||
}
|
||||
return X_ERROR_DEVICE_NOT_CONNECTED;
|
||||
}
|
||||
if (user_index != UserIndex()) {
|
||||
return X_ERROR_DEVICE_NOT_CONNECTED;
|
||||
}
|
||||
std::lock_guard lock(state_mutex_);
|
||||
if (!is_active() || !has_focus_) {
|
||||
ClearStateLocked();
|
||||
while (!keystroke_queue_.empty()) {
|
||||
keystroke_queue_.pop();
|
||||
}
|
||||
return X_ERROR_EMPTY;
|
||||
}
|
||||
if (keystroke_queue_.empty()) {
|
||||
return X_ERROR_EMPTY;
|
||||
}
|
||||
|
|
@ -308,8 +336,9 @@ void MnkInputDriver::SetKeyState(uint16_t vk, bool down) {
|
|||
}
|
||||
|
||||
void MnkInputDriver::OnKeyDown(rex::ui::KeyEvent& e) {
|
||||
if (!IsEnabled() || !has_focus_)
|
||||
if (!IsEnabled() || !has_focus_ || !is_active()) {
|
||||
return;
|
||||
}
|
||||
std::lock_guard lock(state_mutex_);
|
||||
uint16_t vk = static_cast<uint16_t>(e.virtual_key());
|
||||
SetKeyState(vk, true);
|
||||
|
|
@ -324,7 +353,7 @@ void MnkInputDriver::OnKeyUp(rex::ui::KeyEvent& e) {
|
|||
}
|
||||
|
||||
void MnkInputDriver::OnMouseDown(rex::ui::MouseEvent& e) {
|
||||
if (!IsEnabled() || !has_focus_)
|
||||
if (!IsEnabled() || !has_focus_ || !is_active())
|
||||
return;
|
||||
std::lock_guard lock(state_mutex_);
|
||||
switch (e.button()) {
|
||||
|
|
@ -362,7 +391,7 @@ void MnkInputDriver::OnMouseUp(rex::ui::MouseEvent& e) {
|
|||
}
|
||||
|
||||
void MnkInputDriver::OnMouseMove(rex::ui::MouseEvent& e) {
|
||||
if (!IsEnabled() || !has_focus_)
|
||||
if (!IsEnabled() || !has_focus_ || !is_active())
|
||||
return;
|
||||
std::lock_guard lock(state_mutex_);
|
||||
int32_t x = e.x();
|
||||
|
|
@ -376,9 +405,7 @@ void MnkInputDriver::OnMouseMove(rex::ui::MouseEvent& e) {
|
|||
void MnkInputDriver::OnLostFocus(rex::ui::UISetupEvent&) {
|
||||
std::lock_guard lock(state_mutex_);
|
||||
has_focus_ = false;
|
||||
std::memset(key_down_, 0, sizeof(key_down_));
|
||||
mouse_dx_ = 0;
|
||||
mouse_dy_ = 0;
|
||||
ClearStateLocked();
|
||||
if (mouse_captured_ && attached_window_) {
|
||||
mouse_captured_ = false;
|
||||
attached_window_->SetCursorVisibility(rex::ui::Window::CursorVisibility::kVisible);
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ ImGuiDialog::ImGuiDialog(ImGuiDrawer* imgui_drawer) : imgui_drawer_(imgui_drawer
|
|||
}
|
||||
|
||||
ImGuiDialog::~ImGuiDialog() {
|
||||
imgui_drawer_->RemoveDialog(this);
|
||||
SetDrawActive(false);
|
||||
for (auto fence : waiting_fences_) {
|
||||
fence->Signal();
|
||||
}
|
||||
|
|
@ -41,6 +41,18 @@ ImGuiIO& ImGuiDialog::GetIO() {
|
|||
return imgui_drawer()->GetIO();
|
||||
}
|
||||
|
||||
void ImGuiDialog::SetDrawActive(bool active) {
|
||||
if (is_draw_active_ == active) {
|
||||
return;
|
||||
}
|
||||
is_draw_active_ = active;
|
||||
if (active) {
|
||||
imgui_drawer_->AddDialog(this);
|
||||
} else {
|
||||
imgui_drawer_->RemoveDialog(this);
|
||||
}
|
||||
}
|
||||
|
||||
void ImGuiDialog::Draw() {
|
||||
// Draw UI.
|
||||
OnDraw(GetIO());
|
||||
|
|
|
|||
|
|
@ -23,18 +23,44 @@ constexpr std::array<double, 6> kFrameCapRates = {60.0, 90.0, 120.0, 144.0, 165.
|
|||
constexpr std::array<const char*, 7> kFrameCapLabels = {"Unlimited", "60 FPS", "90 FPS",
|
||||
"120 FPS", "144 FPS", "165 FPS",
|
||||
"240 FPS"};
|
||||
constexpr std::array<std::string_view, 10> kSimpleSettingsCvars = {
|
||||
constexpr std::array<std::string_view, 7> kCoreSimpleSettingsCvars = {
|
||||
"resolution_scale",
|
||||
"draw_resolution_scale_x",
|
||||
"draw_resolution_scale_y",
|
||||
"d3d12_present_frame_limiter",
|
||||
"d3d12_present_frame_limiter_fps",
|
||||
"fullscreen",
|
||||
"vsync",
|
||||
"d3d12_allow_variable_refresh_rate_and_tearing",
|
||||
"mnk_mode",
|
||||
"mnk_capture_mouse"};
|
||||
|
||||
bool HasCvar(std::string_view name) {
|
||||
return rex::cvar::GetFlagInfo(name) != nullptr;
|
||||
}
|
||||
|
||||
std::vector<std::string_view> GetSimpleSettingsCvars() {
|
||||
std::vector<std::string_view> cvars;
|
||||
cvars.reserve(14);
|
||||
cvars.insert(cvars.end(), kCoreSimpleSettingsCvars.begin(), kCoreSimpleSettingsCvars.end());
|
||||
if (HasCvar("d3d12_present_frame_limiter")) {
|
||||
cvars.push_back("d3d12_present_frame_limiter");
|
||||
}
|
||||
if (HasCvar("d3d12_present_frame_limiter_fps")) {
|
||||
cvars.push_back("d3d12_present_frame_limiter_fps");
|
||||
}
|
||||
if (HasCvar("d3d12_allow_variable_refresh_rate_and_tearing")) {
|
||||
cvars.push_back("d3d12_allow_variable_refresh_rate_and_tearing");
|
||||
}
|
||||
if (HasCvar("vulkan_allow_present_mode_immediate")) {
|
||||
cvars.push_back("vulkan_allow_present_mode_immediate");
|
||||
}
|
||||
if (HasCvar("vulkan_allow_present_mode_mailbox")) {
|
||||
cvars.push_back("vulkan_allow_present_mode_mailbox");
|
||||
}
|
||||
if (HasCvar("vulkan_allow_present_mode_fifo_relaxed")) {
|
||||
cvars.push_back("vulkan_allow_present_mode_fifo_relaxed");
|
||||
}
|
||||
return cvars;
|
||||
}
|
||||
|
||||
int ResolutionIndexFromCvar() {
|
||||
int32_t current = rex::cvar::Query<int32_t>("resolution_scale");
|
||||
int best = 0;
|
||||
|
|
@ -50,7 +76,14 @@ int ResolutionIndexFromCvar() {
|
|||
return best;
|
||||
}
|
||||
|
||||
bool HasFrameCapCvars() {
|
||||
return HasCvar("d3d12_present_frame_limiter") && HasCvar("d3d12_present_frame_limiter_fps");
|
||||
}
|
||||
|
||||
int FrameCapIndexFromCvar() {
|
||||
if (!HasFrameCapCvars()) {
|
||||
return 0;
|
||||
}
|
||||
if (!rex::cvar::Query<bool>("d3d12_present_frame_limiter")) {
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -77,6 +110,31 @@ void SetBoolCvar(std::string_view name, bool value) {
|
|||
rex::cvar::SetFlagByName(name, value ? "true" : "false");
|
||||
}
|
||||
|
||||
bool TearingFromCvar() {
|
||||
if (HasCvar("d3d12_allow_variable_refresh_rate_and_tearing")) {
|
||||
return rex::cvar::Query<bool>("d3d12_allow_variable_refresh_rate_and_tearing");
|
||||
}
|
||||
if (HasCvar("vulkan_allow_present_mode_immediate")) {
|
||||
return rex::cvar::Query<bool>("vulkan_allow_present_mode_immediate");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void SetTearingCvars(bool value) {
|
||||
if (HasCvar("d3d12_allow_variable_refresh_rate_and_tearing")) {
|
||||
SetBoolCvar("d3d12_allow_variable_refresh_rate_and_tearing", value);
|
||||
}
|
||||
if (HasCvar("vulkan_allow_present_mode_immediate")) {
|
||||
SetBoolCvar("vulkan_allow_present_mode_immediate", value);
|
||||
}
|
||||
if (HasCvar("vulkan_allow_present_mode_mailbox")) {
|
||||
SetBoolCvar("vulkan_allow_present_mode_mailbox", value);
|
||||
}
|
||||
if (HasCvar("vulkan_allow_present_mode_fifo_relaxed")) {
|
||||
SetBoolCvar("vulkan_allow_present_mode_fifo_relaxed", value);
|
||||
}
|
||||
}
|
||||
|
||||
void SectionTitle(const char* label) {
|
||||
ImGui::PushFont(nullptr, 20.0f);
|
||||
ImGui::TextUnformatted(label);
|
||||
|
|
@ -103,17 +161,7 @@ bool PrimaryButton(const char* label) {
|
|||
} // namespace
|
||||
|
||||
void SaveSimpleSettingsConfig(const std::filesystem::path& config_path) {
|
||||
rex::cvar::SaveConfigValues(config_path,
|
||||
{"resolution_scale",
|
||||
"draw_resolution_scale_x",
|
||||
"draw_resolution_scale_y",
|
||||
"d3d12_present_frame_limiter",
|
||||
"d3d12_present_frame_limiter_fps",
|
||||
"fullscreen",
|
||||
"vsync",
|
||||
"d3d12_allow_variable_refresh_rate_and_tearing",
|
||||
"mnk_mode",
|
||||
"mnk_capture_mouse"});
|
||||
rex::cvar::SaveConfigValues(config_path, GetSimpleSettingsCvars());
|
||||
}
|
||||
|
||||
void EnsureSimpleSettingsConfig(const std::filesystem::path& config_path) {
|
||||
|
|
@ -121,7 +169,7 @@ void EnsureSimpleSettingsConfig(const std::filesystem::path& config_path) {
|
|||
if (!should_save) {
|
||||
try {
|
||||
auto config = toml::parse_file(config_path.string());
|
||||
for (std::string_view name : kSimpleSettingsCvars) {
|
||||
for (std::string_view name : GetSimpleSettingsCvars()) {
|
||||
if (!config.contains(name)) {
|
||||
should_save = true;
|
||||
break;
|
||||
|
|
@ -152,12 +200,14 @@ SimpleSettingsDialog::SimpleSettingsDialog(ImGuiDrawer* drawer, std::filesystem:
|
|||
restart_game_(std::move(restart_game)) {
|
||||
ReloadProfiles();
|
||||
LoadSettingsFromCvars();
|
||||
SetDrawActive(false);
|
||||
}
|
||||
|
||||
SimpleSettingsDialog::~SimpleSettingsDialog() = default;
|
||||
|
||||
void SimpleSettingsDialog::Show() {
|
||||
visible_ = true;
|
||||
SetDrawActive(true);
|
||||
ReloadProfiles();
|
||||
LoadSettingsFromCvars();
|
||||
}
|
||||
|
|
@ -167,7 +217,7 @@ void SimpleSettingsDialog::LoadSettingsFromCvars() {
|
|||
frame_cap_index_ = FrameCapIndexFromCvar();
|
||||
fullscreen_ = rex::cvar::Query<bool>("fullscreen");
|
||||
vsync_ = rex::cvar::Query<bool>("vsync");
|
||||
tearing_ = rex::cvar::Query<bool>("d3d12_allow_variable_refresh_rate_and_tearing");
|
||||
tearing_ = TearingFromCvar();
|
||||
mnk_mode_ = rex::cvar::Query<bool>("mnk_mode");
|
||||
mnk_capture_mouse_ = rex::cvar::Query<bool>("mnk_capture_mouse");
|
||||
}
|
||||
|
|
@ -177,7 +227,7 @@ bool SimpleSettingsDialog::HasSettingsChanges() const {
|
|||
frame_cap_index_ != FrameCapIndexFromCvar() ||
|
||||
fullscreen_ != rex::cvar::Query<bool>("fullscreen") ||
|
||||
vsync_ != rex::cvar::Query<bool>("vsync") ||
|
||||
tearing_ != rex::cvar::Query<bool>("d3d12_allow_variable_refresh_rate_and_tearing") ||
|
||||
tearing_ != TearingFromCvar() ||
|
||||
mnk_mode_ != rex::cvar::Query<bool>("mnk_mode") ||
|
||||
mnk_capture_mouse_ != rex::cvar::Query<bool>("mnk_capture_mouse");
|
||||
}
|
||||
|
|
@ -195,6 +245,7 @@ void SimpleSettingsDialog::Hide() {
|
|||
return;
|
||||
}
|
||||
visible_ = false;
|
||||
SetDrawActive(false);
|
||||
if (close_settings_) {
|
||||
close_settings_();
|
||||
}
|
||||
|
|
@ -222,14 +273,16 @@ void SimpleSettingsDialog::SaveVideo() {
|
|||
rex::cvar::SetFlagByName("resolution_scale", scale);
|
||||
rex::cvar::SetFlagByName("draw_resolution_scale_x", scale);
|
||||
rex::cvar::SetFlagByName("draw_resolution_scale_y", scale);
|
||||
SetBoolCvar("d3d12_present_frame_limiter", frame_cap_index_ != 0);
|
||||
if (frame_cap_index_ != 0) {
|
||||
if (HasFrameCapCvars()) {
|
||||
SetBoolCvar("d3d12_present_frame_limiter", frame_cap_index_ != 0);
|
||||
}
|
||||
if (HasFrameCapCvars() && frame_cap_index_ != 0) {
|
||||
rex::cvar::SetFlagByName("d3d12_present_frame_limiter_fps",
|
||||
std::to_string(kFrameCapRates[frame_cap_index_ - 1]));
|
||||
}
|
||||
SetBoolCvar("fullscreen", fullscreen_);
|
||||
SetBoolCvar("vsync", vsync_);
|
||||
SetBoolCvar("d3d12_allow_variable_refresh_rate_and_tearing", tearing_);
|
||||
SetTearingCvars(tearing_);
|
||||
SetBoolCvar("mnk_mode", mnk_mode_);
|
||||
SetBoolCvar("mnk_capture_mouse", mnk_capture_mouse_);
|
||||
SaveSimpleSettingsConfig(config_path_);
|
||||
|
|
@ -341,10 +394,12 @@ void SimpleSettingsDialog::OnDraw(ImGuiIO& io) {
|
|||
static_cast<int>(kResolutionLabels.size()));
|
||||
EndFieldRow();
|
||||
|
||||
BeginFieldRow("Framerate Cap");
|
||||
ImGui::Combo("##frame_cap", &frame_cap_index_, kFrameCapLabels.data(),
|
||||
static_cast<int>(kFrameCapLabels.size()));
|
||||
EndFieldRow();
|
||||
if (HasFrameCapCvars()) {
|
||||
BeginFieldRow("Framerate Cap");
|
||||
ImGui::Combo("##frame_cap", &frame_cap_index_, kFrameCapLabels.data(),
|
||||
static_cast<int>(kFrameCapLabels.size()));
|
||||
EndFieldRow();
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
#include <rex/filesystem.h>
|
||||
#include <rex/logging/sink.h>
|
||||
#include <rex/logging.h>
|
||||
#include <rex/platform.h>
|
||||
#include <rex/ui/overlay/console_overlay.h>
|
||||
#include <rex/ui/overlay/debug_overlay.h>
|
||||
#include <rex/ui/overlay/settings_overlay.h>
|
||||
|
|
@ -50,7 +51,18 @@
|
|||
|
||||
namespace rex {
|
||||
|
||||
REXCVAR_DEFINE_BOOL(advanced_settings_overlay_enabled, false, "UI/Advanced",
|
||||
namespace {
|
||||
|
||||
constexpr bool kBlockShaderStorageStartup =
|
||||
#if REX_PLATFORM_WIN32
|
||||
true;
|
||||
#else
|
||||
false;
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
REXCVAR_DEFINE_BOOL(advanced_settings_overlay_enabled, true, "UI/Advanced",
|
||||
"Enable the developer cvar browser on F4");
|
||||
|
||||
// --- ReXApp ---
|
||||
|
|
@ -262,7 +274,8 @@ bool ReXApp::ConstructRuntime(const PathConfig& paths) {
|
|||
input_sys->SetActiveCallback([this]() {
|
||||
if (!debug_overlay_ && !console_overlay_ && !settings_overlay_)
|
||||
return true;
|
||||
return !imgui_drawer_->GetIO().WantCaptureMouse;
|
||||
const auto& io = imgui_drawer_->GetIO();
|
||||
return !io.WantCaptureMouse && !io.WantCaptureKeyboard;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -416,7 +429,8 @@ void ReXApp::LaunchModule() {
|
|||
uint32_t title_id = runtime_->kernel_state()->title_id();
|
||||
if (title_id != 0) {
|
||||
REXLOG_INFO("Initializing shader storage for title {:08X}...", title_id);
|
||||
graphics_system->InitializeShaderStorage(runtime_->cache_root(), title_id, true);
|
||||
graphics_system->InitializeShaderStorage(runtime_->cache_root(), title_id,
|
||||
kBlockShaderStorageStartup);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,8 +27,50 @@
|
|||
#include <gdk/gdkx.h>
|
||||
#include <xcb/xcb.h>
|
||||
|
||||
REXCVAR_DEFINE_DOUBLE(gtk_ui_scale, 1.25, "UI/GTK",
|
||||
"GTK effective UI scale multiplier")
|
||||
.range(0.75, 2.0)
|
||||
.lifecycle(rex::cvar::Lifecycle::kRequiresRestart);
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr uint32_t kDefaultDpi = 96;
|
||||
|
||||
uint32_t CalculateMonitorDpi(GdkMonitor* monitor) {
|
||||
if (!monitor) {
|
||||
return kDefaultDpi;
|
||||
}
|
||||
// GDK monitor geometry plus physical millimeter size gives EDID physical
|
||||
// density, not the desktop UI scale. Match Windows DPI behavior by exposing
|
||||
// effective UI DPI instead.
|
||||
const int scale_factor = std::max(1, gdk_monitor_get_scale_factor(monitor));
|
||||
const auto dpi =
|
||||
uint32_t(double(kDefaultDpi * uint32_t(scale_factor)) * REXCVAR_GET(gtk_ui_scale) + 0.5);
|
||||
return std::clamp(dpi, uint32_t(48), uint32_t(384));
|
||||
}
|
||||
|
||||
uint32_t QueryWidgetDpi(GtkWidget* widget) {
|
||||
GdkDisplay* display = widget ? gtk_widget_get_display(widget) : gdk_display_get_default();
|
||||
if (!display) {
|
||||
return kDefaultDpi;
|
||||
}
|
||||
|
||||
GdkMonitor* monitor = nullptr;
|
||||
if (widget) {
|
||||
GdkWindow* gdk_window = gtk_widget_get_window(widget);
|
||||
if (gdk_window) {
|
||||
monitor = gdk_display_get_monitor_at_window(display, gdk_window);
|
||||
}
|
||||
}
|
||||
if (!monitor) {
|
||||
monitor = gdk_display_get_primary_monitor(display);
|
||||
}
|
||||
if (!monitor && gdk_display_get_n_monitors(display) > 0) {
|
||||
monitor = gdk_display_get_monitor(display, 0);
|
||||
}
|
||||
return CalculateMonitorDpi(monitor);
|
||||
}
|
||||
|
||||
uint32_t ResolveWindowWidth(uint32_t requested_width) {
|
||||
if (REXCVAR_GET(window_width) > 0) {
|
||||
return uint32_t(REXCVAR_GET(window_width));
|
||||
|
|
@ -313,10 +355,20 @@ std::unique_ptr<Window> Window::Create(WindowedAppContext& app_context,
|
|||
|
||||
GTKWindow::GTKWindow(WindowedAppContext& app_context, const std::string_view title,
|
||||
uint32_t desired_logical_width, uint32_t desired_logical_height)
|
||||
: Window(app_context, title, desired_logical_width, desired_logical_height) {}
|
||||
: Window(app_context, title, desired_logical_width, desired_logical_height) {
|
||||
dpi_ = QueryWidgetDpi(nullptr);
|
||||
}
|
||||
|
||||
GTKWindow::~GTKWindow() {
|
||||
EnterDestructor();
|
||||
if (cursor_auto_hide_timer_) {
|
||||
g_source_remove(cursor_auto_hide_timer_);
|
||||
cursor_auto_hide_timer_ = 0;
|
||||
}
|
||||
if (blank_cursor_) {
|
||||
g_object_unref(blank_cursor_);
|
||||
blank_cursor_ = nullptr;
|
||||
}
|
||||
if (window_) {
|
||||
// Set window_ to null to ignore events from now on since this ui::GTKWindow
|
||||
// is entering an indeterminate state.
|
||||
|
|
@ -329,8 +381,26 @@ GTKWindow::~GTKWindow() {
|
|||
}
|
||||
}
|
||||
|
||||
uint32_t GTKWindow::GetLatestDpiImpl() const {
|
||||
return dpi_;
|
||||
}
|
||||
|
||||
void GTKWindow::UpdateDpi(WindowDestructionReceiver* destruction_receiver) {
|
||||
const uint32_t new_dpi = QueryWidgetDpi(drawing_area_ ? drawing_area_ : window_);
|
||||
if (!new_dpi || new_dpi == dpi_) {
|
||||
return;
|
||||
}
|
||||
|
||||
dpi_ = new_dpi;
|
||||
if (destruction_receiver) {
|
||||
UISetupEvent e(this);
|
||||
OnDpiChanged(e, *destruction_receiver);
|
||||
}
|
||||
}
|
||||
|
||||
bool GTKWindow::OpenImpl() {
|
||||
window_ = gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
||||
blank_cursor_ = gdk_cursor_new_for_display(gtk_widget_get_display(window_), GDK_BLANK_CURSOR);
|
||||
|
||||
gtk_window_set_title(GTK_WINDOW(window_), GetTitle().c_str());
|
||||
|
||||
|
|
@ -345,6 +415,10 @@ bool GTKWindow::OpenImpl() {
|
|||
if (main_menu_widget) {
|
||||
gtk_box_pack_start(GTK_BOX(box_), main_menu_widget, FALSE, FALSE, 0);
|
||||
}
|
||||
const bool initial_fullscreen = IsFullscreen();
|
||||
if (initial_fullscreen && main_menu_widget) {
|
||||
gtk_container_remove(GTK_CONTAINER(box_), main_menu_widget);
|
||||
}
|
||||
|
||||
// Create the drawing area for creating the surface for, which will be the
|
||||
// client area of the window occupying all the window space not taken by the
|
||||
|
|
@ -371,8 +445,22 @@ bool GTKWindow::OpenImpl() {
|
|||
g_signal_connect(G_OBJECT(drawing_area_), "draw", G_CALLBACK(DrawHandler),
|
||||
reinterpret_cast<gpointer>(this));
|
||||
|
||||
if (initial_fullscreen) {
|
||||
gtk_window_fullscreen(GTK_WINDOW(window_));
|
||||
g_idle_add(
|
||||
[](gpointer user_data) -> gboolean {
|
||||
auto* gtk_window = reinterpret_cast<GTKWindow*>(user_data);
|
||||
if (gtk_window->window_ && gtk_window->IsFullscreen()) {
|
||||
gtk_window_fullscreen(GTK_WINDOW(gtk_window->window_));
|
||||
}
|
||||
return G_SOURCE_REMOVE;
|
||||
},
|
||||
this);
|
||||
}
|
||||
|
||||
// Finally show all the widgets in the window, including the main menu.
|
||||
gtk_widget_show_all(window_);
|
||||
UpdateDpi();
|
||||
|
||||
// Remove the size request after finishing the initial layout because it makes
|
||||
// it impossible to make the window smaller.
|
||||
|
|
@ -393,12 +481,10 @@ bool GTKWindow::OpenImpl() {
|
|||
|
||||
// After setting up the initial layout for non-fullscreen, enter fullscreen if
|
||||
// requested.
|
||||
if (IsFullscreen()) {
|
||||
if (main_menu_widget) {
|
||||
gtk_container_remove(GTK_CONTAINER(box_), main_menu_widget);
|
||||
}
|
||||
if (initial_fullscreen) {
|
||||
gtk_window_fullscreen(GTK_WINDOW(window_));
|
||||
}
|
||||
ApplyNewCursorVisibility(CursorVisibility::kVisible);
|
||||
|
||||
// Make sure the initial state after opening is reported to the common Window
|
||||
// class no matter how GTK sends the events.
|
||||
|
|
@ -556,6 +642,20 @@ void GTKWindow::ApplyNewMainMenu(MenuItem* old_main_menu) {
|
|||
}
|
||||
}
|
||||
|
||||
void GTKWindow::ApplyNewCursorVisibility(CursorVisibility old_cursor_visibility) {
|
||||
CursorVisibility new_cursor_visibility = GetCursorVisibility();
|
||||
if (cursor_auto_hide_timer_) {
|
||||
g_source_remove(cursor_auto_hide_timer_);
|
||||
cursor_auto_hide_timer_ = 0;
|
||||
}
|
||||
cursor_currently_auto_hidden_ = new_cursor_visibility != CursorVisibility::kVisible;
|
||||
ApplyCursor();
|
||||
if (new_cursor_visibility == CursorVisibility::kAutoHidden &&
|
||||
old_cursor_visibility == CursorVisibility::kAutoHidden) {
|
||||
SetCursorAutoHideTimer();
|
||||
}
|
||||
}
|
||||
|
||||
void GTKWindow::FocusImpl() {
|
||||
gtk_window_activate_focus(GTK_WINDOW(window_));
|
||||
}
|
||||
|
|
@ -594,10 +694,17 @@ void GTKWindow::HandleSizeUpdate(WindowDestructionReceiver& destruction_receiver
|
|||
return;
|
||||
}
|
||||
|
||||
// TODO(Triang3l): Report the desired client area size.
|
||||
UpdateDpi(&destruction_receiver);
|
||||
if (destruction_receiver.IsWindowDestroyedOrClosed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
GtkAllocation drawing_area_allocation;
|
||||
gtk_widget_get_allocation(drawing_area_, &drawing_area_allocation);
|
||||
if (!IsFullscreen()) {
|
||||
OnDesiredLogicalSizeUpdate(SizeToLogical(uint32_t(drawing_area_allocation.width)),
|
||||
SizeToLogical(uint32_t(drawing_area_allocation.height)));
|
||||
}
|
||||
OnActualSizeUpdate(uint32_t(drawing_area_allocation.width),
|
||||
uint32_t(drawing_area_allocation.height), destruction_receiver);
|
||||
if (destruction_receiver.IsWindowDestroyedOrClosed()) {
|
||||
|
|
@ -757,6 +864,14 @@ gboolean GTKWindow::WindowEventHandler(GdkEvent* event) {
|
|||
// at some point in closing anyway.
|
||||
GtkWidget* window = window_;
|
||||
window_ = nullptr;
|
||||
if (cursor_auto_hide_timer_) {
|
||||
g_source_remove(cursor_auto_hide_timer_);
|
||||
cursor_auto_hide_timer_ = 0;
|
||||
}
|
||||
if (blank_cursor_) {
|
||||
g_object_unref(blank_cursor_);
|
||||
blank_cursor_ = nullptr;
|
||||
}
|
||||
// Destroying the top-level window also destroys its children.
|
||||
drawing_area_ = nullptr;
|
||||
box_ = nullptr;
|
||||
|
|
@ -773,6 +888,7 @@ gboolean GTKWindow::WindowEventHandler(GdkEvent* event) {
|
|||
if (destruction_receiver.IsWindowDestroyedOrClosed()) {
|
||||
break;
|
||||
}
|
||||
ApplyCursor();
|
||||
} break;
|
||||
|
||||
case GDK_KEY_PRESS:
|
||||
|
|
@ -805,12 +921,59 @@ gboolean GTKWindow::WindowEventHandlerThunk(GtkWidget* widget, GdkEvent* event,
|
|||
return window->WindowEventHandler(event);
|
||||
}
|
||||
|
||||
void GTKWindow::ApplyCursor() {
|
||||
if (!drawing_area_) {
|
||||
return;
|
||||
}
|
||||
GdkWindow* drawing_area_window = gtk_widget_get_window(drawing_area_);
|
||||
if (!drawing_area_window) {
|
||||
return;
|
||||
}
|
||||
GdkCursor* cursor = nullptr;
|
||||
switch (GetCursorVisibility()) {
|
||||
case CursorVisibility::kVisible:
|
||||
cursor = nullptr;
|
||||
break;
|
||||
case CursorVisibility::kAutoHidden:
|
||||
cursor = cursor_currently_auto_hidden_ ? blank_cursor_ : nullptr;
|
||||
break;
|
||||
case CursorVisibility::kHidden:
|
||||
cursor = blank_cursor_;
|
||||
break;
|
||||
}
|
||||
gdk_window_set_cursor(drawing_area_window, cursor);
|
||||
}
|
||||
|
||||
void GTKWindow::SetCursorAutoHideTimer() {
|
||||
if (cursor_auto_hide_timer_) {
|
||||
g_source_remove(cursor_auto_hide_timer_);
|
||||
cursor_auto_hide_timer_ = 0;
|
||||
}
|
||||
cursor_auto_hide_timer_ =
|
||||
g_timeout_add(kDefaultCursorAutoHideMilliseconds, AutoHideCursorTimerCallback, this);
|
||||
}
|
||||
|
||||
gboolean GTKWindow::AutoHideCursorTimerCallback(gpointer user_data) {
|
||||
auto* gtk_window = reinterpret_cast<GTKWindow*>(user_data);
|
||||
gtk_window->cursor_auto_hide_timer_ = 0;
|
||||
if (gtk_window->GetCursorVisibility() == CursorVisibility::kAutoHidden) {
|
||||
gtk_window->cursor_currently_auto_hidden_ = true;
|
||||
gtk_window->ApplyCursor();
|
||||
}
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
gboolean GTKWindow::DrawingAreaEventHandler(GdkEvent* event) {
|
||||
switch (event->type) {
|
||||
case GDK_MOTION_NOTIFY:
|
||||
case GDK_BUTTON_PRESS:
|
||||
case GDK_BUTTON_RELEASE:
|
||||
case GDK_SCROLL: {
|
||||
if (GetCursorVisibility() == CursorVisibility::kAutoHidden) {
|
||||
cursor_currently_auto_hidden_ = false;
|
||||
ApplyCursor();
|
||||
SetCursorAutoHideTimer();
|
||||
}
|
||||
WindowDestructionReceiver destruction_receiver(this);
|
||||
HandleMouse(event, destruction_receiver);
|
||||
if (destruction_receiver.IsWindowDestroyedOrClosed()) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue