Launcher display options (#1938)

* launcher: add minimize and maximize buttons to the main window

The main window is a QDialog, which on Windows only gets a close button.

* graphics: keep the game's aspect ratio when presenting to the window

The guest frame was blitted across the whole swapchain image, so a window
or fullscreen display whose shape differs from the frame stretched the
picture. Fit the frame inside the window instead and clear the remaining
bars to black. The video-out overlay layer is drawn into the same
rectangle so it stays aligned with the frame underneath.

* launcher: accept custom screen resolutions such as 21:9

The resolution was a fixed enum of four 16:9 sizes. Make the combo box
editable so any WIDTHxHEIGHT between 320 and 16384 can be entered, and
add ultrawide presets (2560x1080, 3440x1440, 3840x1600, 5120x2160).

Settings saved by older launchers stored the enum key ("R1920X1080");
NormalizeResolution still accepts that form, so they load unchanged.

* launcher: add a Stretch to window option

Presentation now keeps the game's aspect ratio with black bars. Add
--stretch to fill the whole window as before, exposed in the launcher as
a "Stretch to window" checkbox under General, off by default.

* launcher: make the settings dialog resizable and its tabs scrollable

The dialog had no maximize button and pinned its minimum width to its
initial 900 px, and without scroll areas it could not be made shorter
than its content. Add a maximize button (not minimize, since the dialog
is modal), drop the width pin, and wrap each tab page in a frameless
scroll area.

With the scroll areas the layout's minimum size no longer forces the
dialog open, so size it explicitly: grow it to fit the largest tab,
capped at the available screen, when there is no saved geometry and
whenever the global settings add the game folders panel.

* launcher: simplify display options and preserve saved settings

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
This commit is contained in:
M4
2026-10-27 21:59:56 +01:00
committed by GitHub
parent 2efd31443f
commit 69fdb03dc2
10 changed files with 181 additions and 46 deletions
+4
View File
@@ -105,6 +105,10 @@ bool HideCursorEnabled() {
return g_config->hide_cursor_enabled; return g_config->hide_cursor_enabled;
} }
bool StretchEnabled() {
return g_config->stretch_enabled;
}
bool VrEnabled() { bool VrEnabled() {
return g_config->vr_enabled; return g_config->vr_enabled;
} }
+2
View File
@@ -64,6 +64,7 @@ struct ConfigOptions {
bool fullscreen_enabled = false; bool fullscreen_enabled = false;
bool hdr_enabled = false; bool hdr_enabled = false;
bool hide_cursor_enabled = false; bool hide_cursor_enabled = false;
bool stretch_enabled = false;
bool splash_enabled = true; bool splash_enabled = true;
bool vr_enabled = false; bool vr_enabled = false;
bool amd_cpu_enabled = false; bool amd_cpu_enabled = false;
@@ -125,6 +126,7 @@ int32_t GetGpuIndex();
bool FullscreenEnabled(); bool FullscreenEnabled();
bool HdrEnabled(); bool HdrEnabled();
bool HideCursorEnabled(); bool HideCursorEnabled();
bool StretchEnabled();
bool VrEnabled(); bool VrEnabled();
bool AmdCpuEnabled(); bool AmdCpuEnabled();
bool IsNetworkConnected(); bool IsNetworkConnected();
+49 -14
View File
@@ -290,7 +290,8 @@ public:
private: private:
vk::SurfaceFormatKHR SelectFormat(); vk::SurfaceFormatKHR SelectFormat();
void Destroy(); void Destroy();
void DrawOverlay(vk::CommandBuffer command, const Presenter::Layer& layer); void DrawOverlay(vk::CommandBuffer command, const Presenter::Layer& layer,
const vk::Rect2D& rect);
WindowContext& m_window; WindowContext& m_window;
vk::SwapchainKHR m_handle = nullptr; vk::SwapchainKHR m_handle = nullptr;
@@ -637,7 +638,23 @@ bool Swapchain::PrepareSystemOverlay() {
return m_system_overlay->PrepareFrame(m_extent, m_format, ImageCount()); return m_system_overlay->PrepareFrame(m_extent, m_format, ImageCount());
} }
void Swapchain::DrawOverlay(vk::CommandBuffer command, const Presenter::Layer& layer) { static vk::Rect2D FitAspect(const vk::Extent3D& source, const vk::Extent2D& target) {
const uint64_t src_w = std::max(source.width, 1u);
const uint64_t src_h = std::max(source.height, 1u);
uint32_t w = target.width;
uint32_t h = target.height;
if (src_w * h > src_h * w) {
h = std::max<uint32_t>(1, static_cast<uint32_t>(src_h * w / src_w));
} else {
w = std::max<uint32_t>(1, static_cast<uint32_t>(src_w * h / src_h));
}
return {{static_cast<int32_t>((target.width - w) / 2),
static_cast<int32_t>((target.height - h) / 2)},
{w, h}};
}
void Swapchain::DrawOverlay(vk::CommandBuffer command, const Presenter::Layer& layer,
const vk::Rect2D& rect) {
auto device = m_window.graphic_ctx.device; auto device = m_window.graphic_ctx.device;
if (m_overlay_pipeline == nullptr) { if (m_overlay_pipeline == nullptr) {
vk::SamplerCreateInfo sampler {}; vk::SamplerCreateInfo sampler {};
@@ -756,12 +773,14 @@ void Swapchain::DrawOverlay(vk::CommandBuffer command, const Presenter::Layer& l
rendering.colorAttachmentCount = 1; rendering.colorAttachmentCount = 1;
rendering.pColorAttachments = &attachment; rendering.pColorAttachments = &attachment;
command.beginRendering(&rendering); command.beginRendering(&rendering);
const vk::Viewport viewport { const vk::Viewport viewport {static_cast<float>(rect.offset.x),
0.0f, 0.0f, static_cast<float>(m_extent.width), static_cast<float>(m_extent.height), static_cast<float>(rect.offset.y),
0.0f, 1.0f}; static_cast<float>(rect.extent.width),
const vk::Rect2D scissor {{0, 0}, m_extent}; static_cast<float>(rect.extent.height),
0.0f,
1.0f};
command.setViewport(0, 1, &viewport); command.setViewport(0, 1, &viewport);
command.setScissor(0, 1, &scissor); command.setScissor(0, 1, &rect);
command.draw(3, 1, 0, 0); command.draw(3, 1, 0, 0);
command.endRendering(); command.endRendering();
} }
@@ -799,6 +818,24 @@ void Swapchain::RecordPresentCommands(CommandBuffer& command, Presenter::Frame*
vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlags {}, 0, vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlags {}, 0,
nullptr, 0, nullptr, 1, &to_transfer); nullptr, 0, nullptr, 1, &to_transfer);
const auto* aspect_source = source != nullptr ? source : overlay.frame;
const auto game_rect = !Config::StretchEnabled() && aspect_source != nullptr
? FitAspect(aspect_source->image.extent, m_extent)
: vk::Rect2D {{0, 0}, m_extent};
if (source == nullptr || game_rect.extent != m_extent) {
const vk::ClearColorValue black {};
vk_command.clearColorImage(m_images[m_image_index], vk::ImageLayout::eTransferDstOptimal,
&black, 1, &to_transfer.subresourceRange);
if (source != nullptr) {
vk::ImageMemoryBarrier after_clear = to_transfer;
after_clear.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
after_clear.oldLayout = vk::ImageLayout::eTransferDstOptimal;
vk_command.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
vk::PipelineStageFlagBits::eTransfer,
vk::DependencyFlags {}, 0, nullptr, 0, nullptr, 1,
&after_clear);
}
}
if (source != nullptr) { if (source != nullptr) {
vk::ImageBlit region {}; vk::ImageBlit region {};
region.srcSubresource.aspectMask = vk::ImageAspectFlagBits::eColor; region.srcSubresource.aspectMask = vk::ImageAspectFlagBits::eColor;
@@ -812,16 +849,14 @@ void Swapchain::RecordPresentCommands(CommandBuffer& command, Presenter::Frame*
region.dstSubresource.mipLevel = 0; region.dstSubresource.mipLevel = 0;
region.dstSubresource.baseArrayLayer = 0; region.dstSubresource.baseArrayLayer = 0;
region.dstSubresource.layerCount = 1; region.dstSubresource.layerCount = 1;
region.dstOffsets[1].x = static_cast<int>(m_extent.width); region.dstOffsets[0].x = game_rect.offset.x;
region.dstOffsets[1].y = static_cast<int>(m_extent.height); region.dstOffsets[0].y = game_rect.offset.y;
region.dstOffsets[1].x = game_rect.offset.x + static_cast<int>(game_rect.extent.width);
region.dstOffsets[1].y = game_rect.offset.y + static_cast<int>(game_rect.extent.height);
region.dstOffsets[1].z = 1; region.dstOffsets[1].z = 1;
vk_command.blitImage(source->image.image, vk::ImageLayout::eTransferSrcOptimal, vk_command.blitImage(source->image.image, vk::ImageLayout::eTransferSrcOptimal,
m_images[m_image_index], vk::ImageLayout::eTransferDstOptimal, 1, m_images[m_image_index], vk::ImageLayout::eTransferDstOptimal, 1,
&region, vk::Filter::eLinear); &region, vk::Filter::eLinear);
} else {
const vk::ClearColorValue black {};
vk_command.clearColorImage(m_images[m_image_index], vk::ImageLayout::eTransferDstOptimal,
&black, 1, &to_transfer.subresourceRange);
} }
vk::ImageMemoryBarrier to_present {}; vk::ImageMemoryBarrier to_present {};
@@ -848,7 +883,7 @@ void Swapchain::RecordPresentCommands(CommandBuffer& command, Presenter::Frame*
&to_present); &to_present);
if (draw_attachment) { if (draw_attachment) {
if (draw_overlay) { if (draw_overlay) {
DrawOverlay(vk_command, overlay); DrawOverlay(vk_command, overlay, game_rect);
if (draw_system_overlay) { if (draw_system_overlay) {
to_present.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite; to_present.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite;
to_present.oldLayout = vk::ImageLayout::eColorAttachmentOptimal; to_present.oldLayout = vk::ImageLayout::eColorAttachmentOptimal;
@@ -115,7 +115,17 @@
</item> </item>
<item row="0" column="1"> <item row="0" column="1">
<widget class="QComboBox" name="comboBox_screen_resolution"> <widget class="QComboBox" name="comboBox_screen_resolution">
<property name="toolTip"><string>Window resolution</string></property> <property name="toolTip"><string>Window resolution as WIDTHxHEIGHT; pick a preset or type a custom size. Non-16:9 sizes such as 21:9 show the game with black bars unless Stretch to window is on.</string></property>
<property name="editable"><bool>true</bool></property>
<property name="insertPolicy"><enum>QComboBox::NoInsert</enum></property>
<item><property name="text"><string notr="true">1280x720</string></property></item>
<item><property name="text"><string notr="true">1920x1080</string></property></item>
<item><property name="text"><string notr="true">2560x1440</string></property></item>
<item><property name="text"><string notr="true">3840x2160</string></property></item>
<item><property name="text"><string notr="true">2560x1080</string></property></item>
<item><property name="text"><string notr="true">3440x1440</string></property></item>
<item><property name="text"><string notr="true">3840x1600</string></property></item>
<item><property name="text"><string notr="true">5120x2160</string></property></item>
</widget> </widget>
</item> </item>
<item row="1" column="0"> <item row="1" column="0">
@@ -256,6 +266,12 @@
<property name="text"><string>Auto-hide cursor</string></property> <property name="text"><string>Auto-hide cursor</string></property>
</widget> </widget>
</item> </item>
<item>
<widget class="QCheckBox" name="checkBox_stretch">
<property name="toolTip"><string>Stretch the game image to fill the window instead of keeping its aspect ratio with black bars</string></property>
<property name="text"><string>Stretch to window</string></property>
</widget>
</item>
<item> <item>
<widget class="QCheckBox" name="checkBox_perf_overlay"> <widget class="QCheckBox" name="checkBox_perf_overlay">
<property name="toolTip"><string>Show FPS, frame times, CPU/GPU usage, RAM and memory pressure in the game window. Press F2 (Cmd+P on macOS) to toggle it, and add Shift for the detailed view.</string></property> <property name="toolTip"><string>Show FPS, frame times, CPU/GPU usage, RAM and memory pressure in the game window. Press F2 (Cmd+P on macOS) to toggle it, and add Shift for the detailed view.</string></property>
+10 -18
View File
@@ -21,10 +21,7 @@ inline QStringList EnumToList() {
auto me = QMetaEnum::fromType<T>(); auto me = QMetaEnum::fromType<T>();
int count = me.keyCount(); int count = me.keyCount();
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
auto key = QString(me.key(i)); ret << QString(me.key(i));
ret << (key.startsWith('R') && key.size() > 2 && key.at(1).isDigit()
? key.remove('R').toLower()
: key);
} }
return ret; return ret;
} }
@@ -32,16 +29,12 @@ inline QStringList EnumToList() {
template <class T> template <class T>
T TextToEnum(const QString& text) { T TextToEnum(const QString& text) {
auto me = QMetaEnum::fromType<T>(); auto me = QMetaEnum::fromType<T>();
return static_cast<T>(me.keyToValue( return static_cast<T>(me.keyToValue(text.toUtf8().constData()));
((text.size() > 1 && text.at(0).isDigit()) ? 'R' + text.toUpper() : text).toUtf8().data()));
} }
template <class T> template <class T>
QString EnumToText(T value) { QString EnumToText(T value) {
auto me = QMetaEnum::fromType<T>(); return QString(QMetaEnum::fromType<T>().valueToKey(static_cast<int>(value)));
auto key = QString(me.valueToKey(static_cast<int>(value)));
return (key.startsWith('R') && key.size() > 2 && key.at(1).isDigit() ? key.remove('R').toLower()
: key);
} }
struct ControllerSettings { struct ControllerSettings {
@@ -80,13 +73,10 @@ public:
static constexpr int DEFAULT_CONSOLE_LANGUAGE = 1; static constexpr int DEFAULT_CONSOLE_LANGUAGE = 1;
static constexpr int MAX_CONSOLE_LANGUAGE = 29; static constexpr int MAX_CONSOLE_LANGUAGE = 29;
enum class Resolution { static constexpr int MIN_SCREEN_SIZE = 320;
R1280X720, static constexpr int MAX_SCREEN_SIZE = 16384;
R1920X1080,
R2560X1440, static QString NormalizeResolution(const QString& text);
R3840X2160,
};
Q_ENUM(Resolution)
enum class ShaderOptimizationType { None, Size, Performance }; enum class ShaderOptimizationType { None, Size, Performance };
Q_ENUM(ShaderOptimizationType) Q_ENUM(ShaderOptimizationType)
@@ -117,7 +107,7 @@ public:
// Controller preferences always come from the global configuration. // Controller preferences always come from the global configuration.
ControllerSettings controller; ControllerSettings controller;
Resolution screen_resolution = Resolution::R1280X720; QString screen_resolution = QStringLiteral("1280x720");
QString user_name = "Kyty"; QString user_name = "Kyty";
int user_id = Config::DEFAULT_USER_ID; int user_id = Config::DEFAULT_USER_ID;
QString audio_input_device; QString audio_input_device;
@@ -126,6 +116,7 @@ public:
bool fullscreen_enabled = false; bool fullscreen_enabled = false;
bool hdr_enabled = false; bool hdr_enabled = false;
bool hide_cursor_enabled = false; bool hide_cursor_enabled = false;
bool stretch_enabled = false;
bool readback_linear_images = false; bool readback_linear_images = false;
bool readback_tiled_images = false; bool readback_tiled_images = false;
bool sync_raw_image_buffers = false; bool sync_raw_image_buffers = false;
@@ -168,6 +159,7 @@ public:
fullscreen_enabled = other.fullscreen_enabled; fullscreen_enabled = other.fullscreen_enabled;
hdr_enabled = other.hdr_enabled; hdr_enabled = other.hdr_enabled;
hide_cursor_enabled = other.hide_cursor_enabled; hide_cursor_enabled = other.hide_cursor_enabled;
stretch_enabled = other.stretch_enabled;
readback_linear_images = other.readback_linear_images; readback_linear_images = other.readback_linear_images;
readback_tiled_images = other.readback_tiled_images; readback_tiled_images = other.readback_tiled_images;
sync_raw_image_buffers = other.sync_raw_image_buffers; sync_raw_image_buffers = other.sync_raw_image_buffers;
@@ -46,11 +46,15 @@ private:
QLineEdit* m_addons_dir_edit = nullptr; QLineEdit* m_addons_dir_edit = nullptr;
QToolButton* m_remove_game_dir_button = nullptr; QToolButton* m_remove_game_dir_button = nullptr;
bool m_global_settings = false; bool m_global_settings = false;
bool m_geometry_restored = false;
protected: protected:
void Init(const Configuration& info); void Init(const Configuration& info);
void InitGameDirectories(); void InitGameDirectories();
void AddGameDirectoryItem(const QString& dir); void AddGameDirectoryItem(const QString& dir);
void MakeTabsScrollable();
void GrowToFitContents();
void ShowTab(QWidget* page);
void moveEvent(QMoveEvent* event) override; void moveEvent(QMoveEvent* event) override;
void resizeEvent(QResizeEvent* event) override; void resizeEvent(QResizeEvent* event) override;
+25 -2
View File
@@ -1,5 +1,23 @@
#include "configuration.h" #include "configuration.h"
#include <QRegularExpression>
QString Configuration::NormalizeResolution(const QString& text) {
// Older settings stored the enum key, e.g. "R1920X1080".
static const QRegularExpression re(QStringLiteral("^\\s*R?(\\d+)\\s*[xX]\\s*(\\d+)\\s*$"));
const auto match = re.match(text);
if (!match.hasMatch()) {
return {};
}
const int width = match.captured(1).toInt();
const int height = match.captured(2).toInt();
if (width < MIN_SCREEN_SIZE || height < MIN_SCREEN_SIZE || width > MAX_SCREEN_SIZE ||
height > MAX_SCREEN_SIZE) {
return {};
}
return QStringLiteral("%1x%2").arg(width).arg(height);
}
QVariantMap Configuration::GameSettings() const { QVariantMap Configuration::GameSettings() const {
QVariantMap values; QVariantMap values;
#define KYTY_CFG_SET(n) values.insert(QStringLiteral(#n), QVariant::fromValue(n).toString()) #define KYTY_CFG_SET(n) values.insert(QStringLiteral(#n), QVariant::fromValue(n).toString())
@@ -12,6 +30,7 @@ QVariantMap Configuration::GameSettings() const {
KYTY_CFG_SET(fullscreen_enabled); KYTY_CFG_SET(fullscreen_enabled);
KYTY_CFG_SET(hdr_enabled); KYTY_CFG_SET(hdr_enabled);
KYTY_CFG_SET(hide_cursor_enabled); KYTY_CFG_SET(hide_cursor_enabled);
KYTY_CFG_SET(stretch_enabled);
KYTY_CFG_SET(readback_linear_images); KYTY_CFG_SET(readback_linear_images);
KYTY_CFG_SET(readback_tiled_images); KYTY_CFG_SET(readback_tiled_images);
KYTY_CFG_SET(sync_raw_image_buffers); KYTY_CFG_SET(sync_raw_image_buffers);
@@ -48,7 +67,8 @@ QVariantMap Configuration::GameSettings() const {
template <class Settings> template <class Settings>
void Configuration::ReadGameSettingsValues(const Settings& s) { void Configuration::ReadGameSettingsValues(const Settings& s) {
#define KYTY_CFG_GET(n) n = s.value(QStringLiteral(#n)).template value<decltype(n)>() #define KYTY_CFG_GET(n) n = s.value(QStringLiteral(#n)).template value<decltype(n)>()
KYTY_CFG_GET(screen_resolution); const auto resolution = NormalizeResolution(s.value("screen_resolution").toString());
screen_resolution = resolution.isEmpty() ? QStringLiteral("1280x720") : resolution;
user_name = s.value("user_name", user_name).toString(); user_name = s.value("user_name", user_name).toString();
bool user_id_ok = false; bool user_id_ok = false;
auto saved_user_id = s.value("user_id", user_id).toInt(&user_id_ok); auto saved_user_id = s.value("user_id", user_id).toInt(&user_id_ok);
@@ -64,6 +84,7 @@ void Configuration::ReadGameSettingsValues(const Settings& s) {
KYTY_CFG_GET(fullscreen_enabled); KYTY_CFG_GET(fullscreen_enabled);
KYTY_CFG_GET(hdr_enabled); KYTY_CFG_GET(hdr_enabled);
KYTY_CFG_GET(hide_cursor_enabled); KYTY_CFG_GET(hide_cursor_enabled);
KYTY_CFG_GET(stretch_enabled);
KYTY_CFG_GET(readback_linear_images); KYTY_CFG_GET(readback_linear_images);
KYTY_CFG_GET(readback_tiled_images); KYTY_CFG_GET(readback_tiled_images);
KYTY_CFG_GET(sync_raw_image_buffers); KYTY_CFG_GET(sync_raw_image_buffers);
@@ -134,7 +155,9 @@ bool Configuration::SetGameSettings(const QJsonObject& settings, QString& error)
if (!it.value().isString()) { if (!it.value().isString()) {
return fail(tr("Game setting %1 must be a string.").arg(it.key())); return fail(tr("Game setting %1 must be a string.").arg(it.key()));
} }
values.insert(it.key(), it.value().toString()); const auto value = it.value().toString();
values.insert(it.key(), it.key() == "screen_resolution" ? NormalizeResolution(value)
: value);
} }
ReadGameSettingsValues(values); ReadGameSettingsValues(values);
+63 -9
View File
@@ -24,6 +24,8 @@
#include <QMessageBox> #include <QMessageBox>
#include <QPushButton> #include <QPushButton>
#include <QResizeEvent> #include <QResizeEvent>
#include <QScreen>
#include <QScrollArea>
#include <QSettings> #include <QSettings>
#include <QSlider> #include <QSlider>
#include <QSpinBox> #include <QSpinBox>
@@ -122,10 +124,10 @@ static QString GameDirectoryKey(const QString& dir) {
} }
ConfigurationEditDialog::ConfigurationEditDialog(Configuration& info, QWidget* parent) ConfigurationEditDialog::ConfigurationEditDialog(Configuration& info, QWidget* parent)
: QDialog(parent, Qt::WindowCloseButtonHint), m_ui(new Ui::ConfigurationEditDialog), : QDialog(parent, Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint),
m_info(info) { m_ui(new Ui::ConfigurationEditDialog), m_info(info) {
m_ui->setupUi(this); m_ui->setupUi(this);
setMinimumWidth(width()); MakeTabsScrollable();
InitGameDirectories(); InitGameDirectories();
m_ui->controller_group->setVisible(false); m_ui->controller_group->setVisible(false);
m_ui->settings_button->setVisible(false); m_ui->settings_button->setVisible(false);
@@ -170,9 +172,11 @@ ConfigurationEditDialog::ConfigurationEditDialog(Configuration& info, QWidget* p
setSizeGripEnabled(true); setSizeGripEnabled(true);
restoreGeometry(g_last_geometry);
Init(info); Init(info);
m_geometry_restored = restoreGeometry(g_last_geometry);
if (!m_geometry_restored) {
GrowToFitContents();
}
} }
QByteArray ConfigurationEditDialog::g_last_geometry; QByteArray ConfigurationEditDialog::g_last_geometry;
@@ -248,7 +252,7 @@ void ConfigurationEditDialog::Init(const Configuration& info) {
info.audio_input_device); info.audio_input_device);
} }
microphone->setCurrentIndex(microphone->findData(info.audio_input_device)); microphone->setCurrentIndex(microphone->findData(info.audio_input_device));
ListInit(m_ui->comboBox_screen_resolution, info.screen_resolution); m_ui->comboBox_screen_resolution->setCurrentText(info.screen_resolution);
ListInit(m_ui->comboBox_present_mode, info.present_mode); ListInit(m_ui->comboBox_present_mode, info.present_mode);
m_ui->comboBox_gpu->clear(); m_ui->comboBox_gpu->clear();
m_ui->comboBox_gpu->addItem(tr("Auto")); m_ui->comboBox_gpu->addItem(tr("Auto"));
@@ -285,6 +289,7 @@ void ConfigurationEditDialog::Init(const Configuration& info) {
m_ui->checkBox_fullscreen->setChecked(info.fullscreen_enabled); m_ui->checkBox_fullscreen->setChecked(info.fullscreen_enabled);
m_ui->checkBox_hdr->setChecked(info.hdr_enabled); m_ui->checkBox_hdr->setChecked(info.hdr_enabled);
m_ui->checkBox_hide_cursor->setChecked(info.hide_cursor_enabled); m_ui->checkBox_hide_cursor->setChecked(info.hide_cursor_enabled);
m_ui->checkBox_stretch->setChecked(info.stretch_enabled);
m_ui->checkBox_readback->setChecked(info.readback_linear_images); m_ui->checkBox_readback->setChecked(info.readback_linear_images);
m_ui->checkBox_readback_tiled->setChecked(info.readback_tiled_images); m_ui->checkBox_readback_tiled->setChecked(info.readback_tiled_images);
m_ui->checkBox_sync_raw_image_buffers->setChecked(info.sync_raw_image_buffers); m_ui->checkBox_sync_raw_image_buffers->setChecked(info.sync_raw_image_buffers);
@@ -413,8 +418,46 @@ void ConfigurationEditDialog::SetGlobalSettings(const QStringList& dirs, const Q
m_game_dirs_group->setVisible(true); m_game_dirs_group->setVisible(true);
m_ui->controller_group->setVisible(true); m_ui->controller_group->setVisible(true);
update_game_directory_buttons(); update_game_directory_buttons();
if (!m_geometry_restored) {
GrowToFitContents();
}
}
void ConfigurationEditDialog::MakeTabsScrollable() {
auto* tabs = m_ui->settings_tabs;
for (int i = 0; i < tabs->count(); i++) {
auto* page = tabs->widget(i);
auto title = tabs->tabText(i);
auto* scroll = new QScrollArea;
scroll->setWidgetResizable(true);
scroll->setFrameShape(QFrame::NoFrame);
tabs->removeTab(i);
scroll->setWidget(page);
tabs->insertTab(i, scroll, title);
}
tabs->setCurrentIndex(0);
}
void ConfigurationEditDialog::GrowToFitContents() {
layout()->activate(); layout()->activate();
resize(size().expandedTo(minimumSizeHint())); auto* tabs = m_ui->settings_tabs;
QSize content;
for (int i = 0; i < tabs->count(); i++) {
content = content.expandedTo(
static_cast<QScrollArea*>(tabs->widget(i))->widget()->minimumSizeHint());
}
const QSize chrome = size() - tabs->currentWidget()->size();
resize(size().expandedTo(content + chrome).boundedTo(screen()->availableGeometry().size()));
}
void ConfigurationEditDialog::ShowTab(QWidget* page) {
auto* tabs = m_ui->settings_tabs;
for (int i = 0; i < tabs->count(); i++) {
if (tabs->widget(i)->isAncestorOf(page)) {
tabs->setCurrentIndex(i);
return;
}
}
} }
QString ConfigurationEditDialog::GetAddonsDirectory() const { QString ConfigurationEditDialog::GetAddonsDirectory() const {
@@ -474,13 +517,14 @@ static void UpdateInfo(Configuration& info, Ui::ConfigurationEditDialog& ui, boo
info.controller.keyboard_player = ui.comboBox_keyboard_player->currentData().toInt(); info.controller.keyboard_player = ui.comboBox_keyboard_player->currentData().toInt();
} }
info.screen_resolution = info.screen_resolution =
TextToEnum<Configuration::Resolution>(ui.comboBox_screen_resolution->currentText()); Configuration::NormalizeResolution(ui.comboBox_screen_resolution->currentText());
info.present_mode = info.present_mode =
TextToEnum<Configuration::PresentMode>(ui.comboBox_present_mode->currentText()); TextToEnum<Configuration::PresentMode>(ui.comboBox_present_mode->currentText());
info.gpu_index = ui.comboBox_gpu->currentIndex() - 1; info.gpu_index = ui.comboBox_gpu->currentIndex() - 1;
info.fullscreen_enabled = ui.checkBox_fullscreen->isChecked(); info.fullscreen_enabled = ui.checkBox_fullscreen->isChecked();
info.hdr_enabled = ui.checkBox_hdr->isChecked(); info.hdr_enabled = ui.checkBox_hdr->isChecked();
info.hide_cursor_enabled = ui.checkBox_hide_cursor->isChecked(); info.hide_cursor_enabled = ui.checkBox_hide_cursor->isChecked();
info.stretch_enabled = ui.checkBox_stretch->isChecked();
info.readback_linear_images = ui.checkBox_readback->isChecked(); info.readback_linear_images = ui.checkBox_readback->isChecked();
info.readback_tiled_images = ui.checkBox_readback_tiled->isChecked(); info.readback_tiled_images = ui.checkBox_readback_tiled->isChecked();
info.sync_raw_image_buffers = ui.checkBox_sync_raw_image_buffers->isChecked(); info.sync_raw_image_buffers = ui.checkBox_sync_raw_image_buffers->isChecked();
@@ -518,7 +562,7 @@ void ConfigurationEditDialog::save() {
for (auto* field: {m_ui->lineEdit_shader_log_folder, m_ui->lineEdit_printf_file, for (auto* field: {m_ui->lineEdit_shader_log_folder, m_ui->lineEdit_printf_file,
m_ui->lineEdit_cmd_dump_folder}) { m_ui->lineEdit_cmd_dump_folder}) {
if (field->isEnabled() && field->text().isEmpty()) { if (field->isEnabled() && field->text().isEmpty()) {
m_ui->settings_tabs->setCurrentWidget(m_ui->debug_tab); ShowTab(m_ui->debug_tab);
field->setFocus(); field->setFocus();
break; break;
} }
@@ -540,6 +584,16 @@ void ConfigurationEditDialog::save() {
tr("User ID cannot be 254 (everyone) or 255 (system)")); tr("User ID cannot be 254 (everyone) or 255 (system)"));
return; return;
} }
if (Configuration::NormalizeResolution(m_ui->comboBox_screen_resolution->currentText())
.isEmpty()) {
ShowTab(m_ui->settings_tab);
m_ui->comboBox_screen_resolution->setFocus();
QMessageBox::critical(this, tr("Save failed"),
tr("Screen resolution must be WIDTHxHEIGHT, each between %1 and %2")
.arg(Configuration::MIN_SCREEN_SIZE)
.arg(Configuration::MAX_SCREEN_SIZE));
return;
}
UpdateInfo(m_info, *m_ui, m_global_settings); UpdateInfo(m_info, *m_ui, m_global_settings);
+4 -2
View File
@@ -134,7 +134,8 @@ void MainDialogPrivate::Setup(MainDialog* main_dialog) {
m_ui->close_terminal->setVisible(false); // The emulator runs without a terminal. m_ui->close_terminal->setVisible(false); // The emulator runs without a terminal.
#endif #endif
main_dialog->setWindowFlags(Qt::Dialog /*| Qt::MSWindowsFixedSizeDialogHint*/); main_dialog->setWindowFlags(Qt::Dialog | Qt::WindowMinMaxButtonsHint |
Qt::WindowCloseButtonHint);
connect(main_dialog, &MainDialog::Start, this, &MainDialogPrivate::FindInterpreter, connect(main_dialog, &MainDialog::Start, this, &MainDialogPrivate::FindInterpreter,
Qt::QueuedConnection); Qt::QueuedConnection);
@@ -240,7 +241,7 @@ static void AddFlag(QStringList& args, bool enabled, const char* name) {
static QStringList CreateEmulatorArgs(const Configuration& info) { static QStringList CreateEmulatorArgs(const Configuration& info) {
QStringList args; QStringList args;
auto r = EnumToText(info.screen_resolution).split('x'); auto r = Configuration::NormalizeResolution(info.screen_resolution).split('x');
if (r.size() != 2) { if (r.size() != 2) {
return {}; return {};
@@ -266,6 +267,7 @@ static QStringList CreateEmulatorArgs(const Configuration& info) {
AddFlag(args, info.fullscreen_enabled, "--fullscreen"); AddFlag(args, info.fullscreen_enabled, "--fullscreen");
AddFlag(args, info.hdr_enabled, "--hdr"); AddFlag(args, info.hdr_enabled, "--hdr");
AddFlag(args, info.hide_cursor_enabled, "--hide-cursor"); AddFlag(args, info.hide_cursor_enabled, "--hide-cursor");
AddFlag(args, info.stretch_enabled, "--stretch");
AddFlag(args, info.readback_linear_images, "--readback-linear-images"); AddFlag(args, info.readback_linear_images, "--readback-linear-images");
AddFlag(args, info.readback_tiled_images, "--readback-tiled-images"); AddFlag(args, info.readback_tiled_images, "--readback-tiled-images");
AddFlag(args, info.sync_raw_image_buffers, "--sync-raw-image-buffers"); AddFlag(args, info.sync_raw_image_buffers, "--sync-raw-image-buffers");
+3
View File
@@ -70,6 +70,8 @@ static void PrintUsage() {
::printf(" --hdr Enable HDR10 output when supported.\n"); ::printf(" --hdr Enable HDR10 output when supported.\n");
::printf( ::printf(
" --hide-cursor Hide the cursor after 2 s idle. Default: off.\n"); " --hide-cursor Hide the cursor after 2 s idle. Default: off.\n");
::printf(
" --stretch Stretch the image to the window. Default: off.\n");
::printf(" --no-splash Disable the startup splash screen.\n"); ::printf(" --no-splash Disable the startup splash screen.\n");
::printf(" --vr Enable the virtual VR headset.\n"); ::printf(" --vr Enable the virtual VR headset.\n");
::printf(" --amd-cpu Apply AMD CPU instruction patches.\n"); ::printf(" --amd-cpu Apply AMD CPU instruction patches.\n");
@@ -199,6 +201,7 @@ static constexpr Flag FLAGS[] = {
{"--fullscreen", &ConfigOptions::fullscreen_enabled}, {"--fullscreen", &ConfigOptions::fullscreen_enabled},
{"--hdr", &ConfigOptions::hdr_enabled}, {"--hdr", &ConfigOptions::hdr_enabled},
{"--hide-cursor", &ConfigOptions::hide_cursor_enabled}, {"--hide-cursor", &ConfigOptions::hide_cursor_enabled},
{"--stretch", &ConfigOptions::stretch_enabled},
{"--no-splash", &ConfigOptions::splash_enabled, false}, {"--no-splash", &ConfigOptions::splash_enabled, false},
{"--vr", &ConfigOptions::vr_enabled}, {"--vr", &ConfigOptions::vr_enabled},
{"--amd-cpu", &ConfigOptions::amd_cpu_enabled}, {"--amd-cpu", &ConfigOptions::amd_cpu_enabled},