mirror of
https://git.citron-emu.org/citron/emulator
synced 2026-01-28 05:33:28 +00:00
Merge pull request 'fix(gamescope): Rearchitecture Tabs & Overlays for Gamescope' (#86) from fix/deck-gamescope into main
Reviewed-on: https://git.citron-emu.org/Citron/Emulator/pulls/86
This commit is contained in:
@@ -2,25 +2,56 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
#include <QIcon>
|
||||
#include <fmt/format.h>
|
||||
#include "common/scm_rev.h"
|
||||
#include "ui_aboutdialog.h"
|
||||
#include "citron/about_dialog.h"
|
||||
#include "citron/uisettings.h"
|
||||
|
||||
AboutDialog::AboutDialog(QWidget* parent)
|
||||
: QDialog(parent), ui{std::make_unique<Ui::AboutDialog>()} {
|
||||
: QDialog(parent) {
|
||||
const bool is_gamescope = UISettings::IsGamescope();
|
||||
|
||||
if (is_gamescope) {
|
||||
setWindowFlags(Qt::Window | Qt::CustomizeWindowHint | Qt::WindowTitleHint);
|
||||
setWindowModality(Qt::NonModal);
|
||||
}
|
||||
|
||||
ui = std::make_unique<Ui::AboutDialog>();
|
||||
ui->setupUi(this);
|
||||
|
||||
connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
|
||||
std::string citron_build_version = "citron | 0.12.25";
|
||||
#ifdef CITRON_ENABLE_PGO_USE
|
||||
citron_build_version += " | PGO";
|
||||
#endif
|
||||
|
||||
ui->setupUi(this);
|
||||
// Try and request the icon from Qt theme (Linux?)
|
||||
const QIcon citron_logo = QIcon::fromTheme(QStringLiteral("org.citron_emu.citron"));
|
||||
if (!citron_logo.isNull()) {
|
||||
ui->labelLogo->setPixmap(citron_logo.pixmap(200));
|
||||
if (is_gamescope) {
|
||||
resize(700, 450);
|
||||
|
||||
// Scale fonts up slightly so they aren't "too small"
|
||||
QFont font = this->font();
|
||||
font.setPointSize(font.pointSize() + 1);
|
||||
this->setFont(font);
|
||||
|
||||
// Keep the Citron header large
|
||||
ui->labelCitron->setStyleSheet(QStringLiteral("font-size: 24pt; font-weight: bold;"));
|
||||
}
|
||||
|
||||
QPixmap logo_pixmap(QStringLiteral(":/icons/default/256x256/citron.png"));
|
||||
if (!logo_pixmap.isNull()) {
|
||||
int logo_size = is_gamescope ? 150 : 200;
|
||||
ui->labelLogo->setPixmap(logo_pixmap);
|
||||
ui->labelLogo->setFixedSize(logo_size, logo_size);
|
||||
ui->labelLogo->setScaledContents(true);
|
||||
}
|
||||
|
||||
ui->labelBuildInfo->setText(
|
||||
ui->labelBuildInfo->text().arg(QString::fromStdString(citron_build_version),
|
||||
QString::fromUtf8(Common::g_build_date).left(10)));
|
||||
|
||||
@@ -3,147 +3,69 @@
|
||||
<class>AboutDialog</class>
|
||||
<widget class="QDialog" name="AboutDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>622</width>
|
||||
<height>294</height>
|
||||
</rect>
|
||||
<rect><x>0</x><y>0</y><width>620</width><height>300</height></rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>About citron</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<layout class="QVBoxLayout" name="mainVerticalLayout">
|
||||
<property name="spacing"><number>12</number></property>
|
||||
<property name="leftMargin"><number>12</number></property>
|
||||
<property name="topMargin"><number>12</number></property>
|
||||
<property name="rightMargin"><number>12</number></property>
|
||||
<property name="bottomMargin"><number>12</number></property>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,1">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<layout class="QVBoxLayout" name="logoColumn">
|
||||
<item>
|
||||
<widget class="QLabel" name="labelLogo">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>200</width>
|
||||
<height>200</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="pixmap">
|
||||
<pixmap resource="../../dist/qt_themes/default/default.qrc">:/icons/default/256x256/citron.png</pixmap>
|
||||
</property>
|
||||
<property name="scaledContents">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="minimumSize"><size><width>160</width><height>160</height></size></property>
|
||||
<property name="text"><string/></property>
|
||||
<property name="scaledContents"><bool>true</bool></property>
|
||||
<property name="alignment"><set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item><spacer name="vS1"><property name="orientation"><enum>Qt::Vertical</enum></property></spacer></item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<layout class="QVBoxLayout" name="textColumn">
|
||||
<property name="spacing"><number>6</number></property>
|
||||
<item>
|
||||
<widget class="QLabel" name="labelCitron">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string><html><head/><body><p><span style=" font-size:28pt;">citron</span></p></body></html></string>
|
||||
<string><html><body><p><span style=" font-size:28pt; font-weight:600;">citron</span></p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="labelBuildInfo">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string><html><head/><body><p>%1 (%2)</p></body></html></string>
|
||||
<property name="text"><string><html><body><p><span style=" font-size:10pt; color:#888888;">%1 (%2)</span></p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="labelAbout">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;">
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-size:12pt;">citron is an experimental open-source emulator for the Nintendo Switch licensed under GPLv3.0+.</span></p>
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;"><br /></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-size:12pt;">This software should not be used to play games you have not legally obtained.</span></p></body></html></string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
<string>citron is an experimental open-source emulator for the Nintendo Switch licensed under GPLv3.0+. This software should not be used to play games you have not legally obtained.</string>
|
||||
</property>
|
||||
<property name="wordWrap"><bool>true</bool></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item><spacer name="vS2"><property name="orientation"><enum>Qt::Vertical</enum></property></spacer></item>
|
||||
<item>
|
||||
<widget class="QLabel" name="labelLinks">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p><a href="https://citron-emu.org/"><span style=" text-decoration: underline; color:#039be5;">Website</span></a> | <a href="https://git.citron-emu.org/citron/emulator"><span style=" text-decoration: underline; color:#039be5;">Source Code</span></a> | <a href="https://git.citron-emu.org/Citron/Emulator/commits/branch/main"><span style=" text-decoration: underline; color:#039be5;">Recent Commits</span></a> | <a href="https://git.citron-emu.org/Citron/Emulator/src/branch/main/LICENSE"><span style=" text-decoration: underline; color:#039be5;">License</span></a></p></body></html></string>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
<string><a href="https://citron-emu.org/">Website</a> | <a href="https://git.citron-emu.org/citron/emulator">Source</a> | <a href="https://git.citron-emu.org/">Commits</a></string>
|
||||
</property>
|
||||
<property name="openExternalLinks"><bool>true</bool></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="labelLiability">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string><html><head/><body><p><span style=" font-size:7pt;">&quot;Nintendo Switch&quot; is a trademark of Nintendo. citron is not affiliated with Nintendo in any way.</span></p></body></html></string>
|
||||
<string><span style=" font-size:8pt; color:#777777;">Nintendo Switch is a trademark of Nintendo. citron is not affiliated with Nintendo.</span></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -153,52 +75,17 @@ p, li { white-space: pre-wrap; }
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
<property name="standardButtons"><set>QDialogButtonBox::Ok</set></property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="../../dist/qt_themes_default/default/default.qrc"/>
|
||||
<include location="../../dist/qt_themes/default/default.qrc"/>
|
||||
</resources>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>AboutDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>20</x>
|
||||
<y>20</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>20</x>
|
||||
<y>20</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>AboutDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>20</x>
|
||||
<y>20</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>20</x>
|
||||
<y>20</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
|
||||
@@ -108,8 +108,17 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_,
|
||||
}
|
||||
|
||||
Settings::SetConfiguringGlobal(true);
|
||||
setWindowFlags(Qt::Dialog | Qt::WindowTitleHint | Qt::WindowSystemMenuHint |
|
||||
Qt::WindowMinMaxButtonsHint | Qt::WindowCloseButtonHint);
|
||||
|
||||
const bool is_gamescope = UISettings::IsGamescope();
|
||||
if (is_gamescope) {
|
||||
// GameScope: Use Window flags instead of Dialog to ensure mouse focus
|
||||
setWindowFlags(Qt::Window | Qt::CustomizeWindowHint | Qt::WindowTitleHint);
|
||||
setWindowModality(Qt::NonModal);
|
||||
} else {
|
||||
setWindowFlags(Qt::Dialog | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint);
|
||||
setWindowModality(Qt::WindowModal);
|
||||
}
|
||||
|
||||
ui->setupUi(this);
|
||||
|
||||
auto* animation_filter = new StyleAnimationEventFilter(this);
|
||||
@@ -128,9 +137,13 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_,
|
||||
ui->topButtonWidget->setLayout(nav_layout);
|
||||
|
||||
last_palette_text_color = qApp->palette().color(QPalette::WindowText);
|
||||
if (!UISettings::values.configure_dialog_geometry.isEmpty()) {
|
||||
|
||||
if (is_gamescope) {
|
||||
resize(1100, 700);
|
||||
} else if (!UISettings::values.configure_dialog_geometry.isEmpty()) {
|
||||
restoreGeometry(UISettings::values.configure_dialog_geometry);
|
||||
}
|
||||
|
||||
UpdateTheme();
|
||||
|
||||
tab_button_group = std::make_unique<QButtonGroup>(this);
|
||||
@@ -238,8 +251,6 @@ void ConfigureDialog::UpdateTheme() {
|
||||
if (!rainbow_timer) {
|
||||
rainbow_timer = new QTimer(this);
|
||||
connect(rainbow_timer, &QTimer::timeout, this, [this] {
|
||||
// MODAL GUARD: If a color dialog or popup is open, pause updates.
|
||||
// This makes the Color Picker buttons static and responsive.
|
||||
if (m_is_tab_animating || !this->isVisible() || !this->isActiveWindow()) return;
|
||||
|
||||
const int current_index = ui->stackedWidget->currentIndex();
|
||||
@@ -261,12 +272,18 @@ void ConfigureDialog::UpdateTheme() {
|
||||
if (ui->horizontalNavWidget) ui->horizontalNavWidget->setStyleSheet(sidebar_css);
|
||||
|
||||
// 2. Action Buttons (OK/Apply/Cancel)
|
||||
if (ui->buttonBox && !ui->buttonBox->underMouse()) {
|
||||
ui->buttonBox->setStyleSheet(QStringLiteral(
|
||||
if (ui->buttonBox) {
|
||||
const QString button_css = QStringLiteral(
|
||||
"QPushButton { background-color: %1; color: #ffffff; border-radius: 4px; font-weight: bold; padding: 5px 15px; }"
|
||||
"QPushButton:hover { background-color: %2; }"
|
||||
"QPushButton:pressed { background-color: %3; }"
|
||||
).arg(hue_hex).arg(hue_light).arg(hue_dark));
|
||||
).arg(hue_hex).arg(hue_light).arg(hue_dark);
|
||||
|
||||
for (auto* button : ui->buttonBox->findChildren<QPushButton*>()) {
|
||||
if (!button->isDown()) {
|
||||
button->setStyleSheet(button_css);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Tab Content Area
|
||||
@@ -302,7 +319,17 @@ void ConfigureDialog::UpdateTheme() {
|
||||
});
|
||||
}
|
||||
rainbow_timer->start(33);
|
||||
} else if (rainbow_timer) {
|
||||
}
|
||||
|
||||
if (ui->buttonBox) {
|
||||
ui->buttonBox->setStyleSheet(QStringLiteral(
|
||||
"QPushButton { background-color: %1; color: #ffffff; border-radius: 4px; font-weight: bold; padding: 5px 15px; }"
|
||||
"QPushButton:hover { background-color: %2; }"
|
||||
"QPushButton:pressed { background-color: %3; }"
|
||||
).arg(accent).arg(Theme::GetAccentColorHover()).arg(Theme::GetAccentColorPressed()));
|
||||
}
|
||||
|
||||
if (UISettings::values.enable_rainbow_mode.GetValue() == false && rainbow_timer) {
|
||||
rainbow_timer->stop();
|
||||
if (ui->topButtonWidget) ui->topButtonWidget->setStyleSheet({});
|
||||
if (ui->horizontalNavWidget) ui->horizontalNavWidget->setStyleSheet({});
|
||||
|
||||
@@ -76,14 +76,11 @@
|
||||
static bool IsDarkMode() {
|
||||
const std::string& theme_name = UISettings::values.theme;
|
||||
|
||||
// Priority 1: Check for explicitly chosen dark themes.
|
||||
if (theme_name == "qdarkstyle" || theme_name == "colorful_dark" ||
|
||||
theme_name == "qdarkstyle_midnight_blue" || theme_name == "colorful_midnight_blue") {
|
||||
return true; // These themes are always dark.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Priority 2: Check for adaptive themes ("default" and "colorful").
|
||||
// For these, we fall back to checking the OS palette.
|
||||
if (theme_name == "default" || theme_name == "colorful") {
|
||||
const QPalette palette = qApp->palette();
|
||||
const QColor text_color = palette.color(QPalette::WindowText);
|
||||
@@ -91,7 +88,6 @@ static bool IsDarkMode() {
|
||||
return text_color.value() > base_color.value();
|
||||
}
|
||||
|
||||
// Fallback for any other unknown theme (assumed light).
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -112,7 +108,6 @@ ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::st
|
||||
: fmt::format("{:016X}", title_id);
|
||||
game_config = std::make_unique<QtConfig>(config_file_name, Config::ConfigType::PerGameConfig);
|
||||
|
||||
// Create tab instances
|
||||
addons_tab = std::make_unique<ConfigurePerGameAddons>(system_, this);
|
||||
cheats_tab = std::make_unique<ConfigurePerGameCheats>(system_, this);
|
||||
audio_tab = std::make_unique<ConfigureAudio>(system_, tab_group, *builder, this);
|
||||
@@ -126,8 +121,18 @@ ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::st
|
||||
linux_tab = std::make_unique<ConfigureLinuxTab>(system_, tab_group, *builder, this);
|
||||
system_tab = std::make_unique<ConfigureSystem>(system_, tab_group, *builder, this);
|
||||
|
||||
if (!UISettings::values.per_game_configure_geometry.isEmpty()) {
|
||||
restoreGeometry(UISettings::values.per_game_configure_geometry);
|
||||
const bool is_gamescope = UISettings::IsGamescope();
|
||||
|
||||
if (is_gamescope) {
|
||||
setWindowFlags(Qt::Window | Qt::CustomizeWindowHint | Qt::WindowTitleHint);
|
||||
setWindowModality(Qt::NonModal);
|
||||
resize(1100, 700);
|
||||
} else {
|
||||
setWindowFlags(Qt::Dialog | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint);
|
||||
setWindowModality(Qt::WindowModal);
|
||||
if (!UISettings::values.per_game_configure_geometry.isEmpty()) {
|
||||
restoreGeometry(UISettings::values.per_game_configure_geometry);
|
||||
}
|
||||
}
|
||||
|
||||
UpdateTheme();
|
||||
@@ -140,10 +145,8 @@ ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::st
|
||||
const auto add_tab = [&](QWidget* widget, const QString& title, int id) {
|
||||
auto button = new QPushButton(title, this);
|
||||
button->setCheckable(true);
|
||||
// This object name matches the stylesheet ID selector `QPushButton#aestheticTabButton`
|
||||
button->setObjectName(QStringLiteral("aestheticTabButton"));
|
||||
// This custom property is used by the event filter for the animated style
|
||||
button->setProperty("class", QStringLiteral("tabButton")); // Keep class for animation
|
||||
button->setProperty("class", QStringLiteral("tabButton"));
|
||||
button->installEventFilter(animation_filter);
|
||||
|
||||
ui->tabButtonsLayout->addWidget(button);
|
||||
@@ -177,7 +180,6 @@ ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::st
|
||||
ui->stackedWidget->setCurrentIndex(0);
|
||||
}
|
||||
|
||||
|
||||
setFocusPolicy(Qt::ClickFocus);
|
||||
setWindowTitle(tr("Properties"));
|
||||
addons_tab->SetTitleId(title_id);
|
||||
@@ -322,26 +324,25 @@ void ConfigurePerGame::UpdateTheme() {
|
||||
if (ui->tabButtonsScrollArea) {
|
||||
ui->tabButtonsScrollArea->setStyleSheet(QStringLiteral(
|
||||
"QScrollBar:horizontal { height: 14px; background: transparent; border-radius: 7px; }"
|
||||
"QScrollBar::handle:horizontal { background-color: %1; border-radius: 6px; min-width: 30px; margin: 1px; }"
|
||||
"QScrollBar::handle:horizontal { background-color: %1; border-radius: 64px; min-width: 30px; margin: 1px; }"
|
||||
"QScrollBar::add-line, QScrollBar::sub-line { background: none; width: 0px; }"
|
||||
).arg(hue_hex));
|
||||
}
|
||||
|
||||
// 3. Action Buttons (OK/Cancel) and Trim button
|
||||
if (ui->buttonBox && !ui->buttonBox->underMouse()) {
|
||||
ui->buttonBox->setStyleSheet(QStringLiteral(
|
||||
"QPushButton { background-color: %1; color: #ffffff; border-radius: 4px; font-weight: bold; padding: 5px 15px; }"
|
||||
"QPushButton:hover { background-color: %2; }"
|
||||
"QPushButton:pressed { background-color: %3; }"
|
||||
).arg(hue_hex).arg(hue_light).arg(hue_dark));
|
||||
}
|
||||
// 3. Action Buttons
|
||||
const QString button_css = QStringLiteral(
|
||||
"QPushButton { background-color: %1; color: #ffffff; border-radius: 4px; font-weight: bold; padding: 5px 15px; }"
|
||||
"QPushButton:hover { background-color: %2; }"
|
||||
"QPushButton:pressed { background-color: %3; }"
|
||||
).arg(hue_hex).arg(hue_light).arg(hue_dark);
|
||||
|
||||
if (ui->trim_xci_button && !ui->trim_xci_button->underMouse()) {
|
||||
ui->trim_xci_button->setStyleSheet(QStringLiteral(
|
||||
"QPushButton { background-color: %1; color: #ffffff; border: none; border-radius: 4px; padding: 10px; }"
|
||||
"QPushButton:hover { background-color: %2; }"
|
||||
"QPushButton:pressed { background-color: %3; }"
|
||||
).arg(hue_hex).arg(hue_light).arg(hue_dark));
|
||||
if (ui->buttonBox) {
|
||||
for (auto* button : ui->buttonBox->findChildren<QPushButton*>()) {
|
||||
if (!button->isDown()) button->setStyleSheet(button_css);
|
||||
}
|
||||
}
|
||||
if (ui->trim_xci_button && !ui->trim_xci_button->isDown()) {
|
||||
ui->trim_xci_button->setStyleSheet(button_css);
|
||||
}
|
||||
|
||||
// 4. Tab Content Area
|
||||
@@ -374,7 +375,25 @@ void ConfigurePerGame::UpdateTheme() {
|
||||
});
|
||||
}
|
||||
rainbow_timer->start(33);
|
||||
} else if (rainbow_timer) {
|
||||
}
|
||||
|
||||
// Fix for Gamescope: Style buttons once outside the timer loop
|
||||
if (ui->buttonBox) {
|
||||
ui->buttonBox->setStyleSheet(QStringLiteral(
|
||||
"QPushButton { background-color: %1; color: #ffffff; border-radius: 4px; font-weight: bold; padding: 5px 15px; }"
|
||||
"QPushButton:hover { background-color: %2; }"
|
||||
"QPushButton:pressed { background-color: %3; }"
|
||||
).arg(accent).arg(Theme::GetAccentColorHover()).arg(Theme::GetAccentColorPressed()));
|
||||
}
|
||||
if (ui->trim_xci_button) {
|
||||
ui->trim_xci_button->setStyleSheet(QStringLiteral(
|
||||
"QPushButton { background-color: %1; color: #ffffff; border: none; border-radius: 4px; padding: 10px; }"
|
||||
"QPushButton:hover { background-color: %2; }"
|
||||
"QPushButton:pressed { background-color: %3; }"
|
||||
).arg(accent).arg(Theme::GetAccentColorHover()).arg(Theme::GetAccentColorPressed()));
|
||||
}
|
||||
|
||||
if (UISettings::values.enable_rainbow_mode.GetValue() == false && rainbow_timer) {
|
||||
rainbow_timer->stop();
|
||||
if (ui->tabButtonsContainer) ui->tabButtonsContainer->setStyleSheet({});
|
||||
if (ui->tabButtonsScrollArea) ui->tabButtonsScrollArea->setStyleSheet({});
|
||||
@@ -551,8 +570,7 @@ void ConfigurePerGame::LoadConfiguration() {
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (...) {
|
||||
}
|
||||
} catch (...) {}
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -610,17 +628,18 @@ void ConfigurePerGame::LoadConfiguration() {
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (...) {
|
||||
}
|
||||
} catch (...) {}
|
||||
|
||||
const auto& system_build_id = system.GetApplicationProcessBuildID();
|
||||
const auto system_build_id_hex = Common::HexToString(system_build_id, false);
|
||||
if (system.IsPoweredOn()) {
|
||||
const auto& system_build_id = system.GetApplicationProcessBuildID();
|
||||
const auto system_build_id_hex = Common::HexToString(system_build_id, false);
|
||||
|
||||
if (!system_build_id_hex.empty() && system_build_id_hex != std::string(64, '0')) {
|
||||
if (!base_build_id_hex.empty() && system_build_id_hex != base_build_id_hex) {
|
||||
update_build_id_hex = system_build_id_hex;
|
||||
} else if (base_build_id_hex.empty()) {
|
||||
base_build_id_hex = system_build_id_hex;
|
||||
if (!system_build_id_hex.empty() && system_build_id_hex != std::string(64, '0')) {
|
||||
if (!base_build_id_hex.empty() && system_build_id_hex != base_build_id_hex) {
|
||||
update_build_id_hex = system_build_id_hex;
|
||||
} else if (base_build_id_hex.empty()) {
|
||||
base_build_id_hex = system_build_id_hex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -863,15 +882,15 @@ void ConfigurePerGame::AnimateTabSwitch(int id) {
|
||||
current_widget->hide();
|
||||
current_widget->move(0, 0);
|
||||
|
||||
m_is_tab_animating = false; // Reset the flag
|
||||
m_is_tab_animating = false;
|
||||
for (auto button : button_group->buttons()) {
|
||||
button->setEnabled(true);
|
||||
}
|
||||
});
|
||||
|
||||
m_is_tab_animating = true; // Set the flag
|
||||
m_is_tab_animating = true;
|
||||
for (auto button : button_group->buttons()) {
|
||||
button->setEnabled(false);
|
||||
}
|
||||
animation_group->start(QAbstractAnimation::DeleteWhenStopped);
|
||||
}
|
||||
}
|
||||
@@ -2,17 +2,19 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "citron/controller_overlay.h"
|
||||
#include "citron/uisettings.h"
|
||||
#include "citron/configuration/configure_input_player_widget.h"
|
||||
#include "citron/main.h"
|
||||
#include "core/core.h"
|
||||
#include "hid_core/hid_core.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QGridLayout>
|
||||
#include <QMouseEvent>
|
||||
#include <QPainter>
|
||||
#include <QPainterPath>
|
||||
#include <QSizeGrip>
|
||||
#include <QWindow> // Required for Wayland dragging
|
||||
#include <QWindow>
|
||||
#include <QResizeEvent>
|
||||
|
||||
namespace {
|
||||
@@ -26,27 +28,34 @@ Core::HID::EmulatedController* GetPlayer1Controller(Core::System* system) {
|
||||
}
|
||||
return hid_core.GetEmulatedController(Core::HID::NpadIdType::Player1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ControllerOverlay::ControllerOverlay(GMainWindow* parent)
|
||||
: QWidget(parent, Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint),
|
||||
main_window(parent) {
|
||||
: QWidget(parent), main_window(parent) {
|
||||
|
||||
// Gamescope requires ToolTip to stay visible over the game surface,
|
||||
// but Desktop Wayland/Windows needs Tool to behave correctly in the taskbar/stack.
|
||||
if (UISettings::IsGamescope()) {
|
||||
setWindowFlags(Qt::ToolTip | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::WindowDoesNotAcceptFocus);
|
||||
setAttribute(Qt::WA_ShowWithoutActivating);
|
||||
setMinimumSize(112, 87); // Use the smaller Gamescope-optimized scale
|
||||
} else {
|
||||
setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);
|
||||
setMinimumSize(225, 175); // Desktop standard scale
|
||||
}
|
||||
|
||||
setAttribute(Qt::WA_TranslucentBackground);
|
||||
setAttribute(Qt::WA_NoSystemBackground);
|
||||
|
||||
auto* layout = new QGridLayout(this);
|
||||
setLayout(layout);
|
||||
// Set margins to 0 so the controller can go right to the edge of the resizable window
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
// Create the widget that draws the controller and make it transparent
|
||||
// Create the widget that draws the controller
|
||||
controller_widget = new PlayerControlPreview(this);
|
||||
controller_widget->setAttribute(Qt::WA_TranslucentBackground);
|
||||
|
||||
// Disable the raw joystick (deadzone) visualization
|
||||
controller_widget->SetRawJoystickVisible(false);
|
||||
|
||||
// Allow the widget to expand and shrink with the window
|
||||
controller_widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
layout->addWidget(controller_widget, 0, 0);
|
||||
|
||||
@@ -54,18 +63,46 @@ ControllerOverlay::ControllerOverlay(GMainWindow* parent)
|
||||
size_grip = new QSizeGrip(this);
|
||||
layout->addWidget(size_grip, 0, 0, Qt::AlignBottom | Qt::AlignRight);
|
||||
|
||||
// Start the timer for continuous updates
|
||||
// Timer for updates
|
||||
connect(&update_timer, &QTimer::timeout, this, &ControllerOverlay::UpdateControllerState);
|
||||
update_timer.start(16); // ~60 FPS
|
||||
|
||||
// Set a minimum size and a default starting size
|
||||
setMinimumSize(225, 175);
|
||||
resize(450, 350);
|
||||
// Initial Resize
|
||||
if (UISettings::IsGamescope()) {
|
||||
resize(225, 175);
|
||||
} else {
|
||||
resize(450, 350);
|
||||
}
|
||||
}
|
||||
|
||||
ControllerOverlay::~ControllerOverlay() = default;
|
||||
|
||||
void ControllerOverlay::UpdateControllerState() {
|
||||
if (!main_window || !is_enabled) return;
|
||||
|
||||
if (UISettings::IsGamescope()) {
|
||||
bool ui_active = false;
|
||||
for (QWidget* w : QApplication::topLevelWidgets()) {
|
||||
if (w->isWindow() && w->isVisible() && w != main_window && w != this &&
|
||||
!w->inherits("GRenderWindow") &&
|
||||
!w->inherits("PerformanceOverlay") &&
|
||||
!w->inherits("VramOverlay") &&
|
||||
!w->inherits("ControllerOverlay")) {
|
||||
ui_active = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ui_active) {
|
||||
if (!this->isHidden()) this->hide();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_enabled && this->isHidden()) {
|
||||
this->show();
|
||||
}
|
||||
|
||||
Core::System* system = main_window->GetSystem();
|
||||
Core::HID::EmulatedController* controller = GetPlayer1Controller(system);
|
||||
if (controller_widget && controller) {
|
||||
@@ -75,22 +112,23 @@ void ControllerOverlay::UpdateControllerState() {
|
||||
}
|
||||
}
|
||||
|
||||
// The paint event is now empty, which makes the background fully transparent.
|
||||
void ControllerOverlay::paintEvent(QPaintEvent* event) {
|
||||
Q_UNUSED(event);
|
||||
// Intentionally left blank to achieve a fully transparent window background.
|
||||
}
|
||||
|
||||
// These functions handle dragging the frameless window
|
||||
void ControllerOverlay::mousePressEvent(QMouseEvent* event) {
|
||||
if (event->button() == Qt::LeftButton && !size_grip->geometry().contains(event->pos())) {
|
||||
|
||||
// LOGIC BRANCH: Desktop Linux (Wayland) requires system move.
|
||||
// Gamescope and Windows require manual dragging.
|
||||
#if defined(Q_OS_LINUX)
|
||||
// Use system move on Wayland/Linux for proper dragging
|
||||
if (windowHandle()) {
|
||||
if (!UISettings::IsGamescope() && windowHandle()) {
|
||||
windowHandle()->startSystemMove();
|
||||
} else {
|
||||
is_dragging = true;
|
||||
drag_start_pos = event->globalPosition().toPoint() - this->pos();
|
||||
}
|
||||
#else
|
||||
// Original dragging implementation for other platforms (Windows, etc.)
|
||||
is_dragging = true;
|
||||
drag_start_pos = event->globalPosition().toPoint() - this->pos();
|
||||
#endif
|
||||
@@ -99,15 +137,11 @@ void ControllerOverlay::mousePressEvent(QMouseEvent* event) {
|
||||
}
|
||||
|
||||
void ControllerOverlay::mouseMoveEvent(QMouseEvent* event) {
|
||||
#if !defined(Q_OS_LINUX)
|
||||
// Only handle manual dragging if we aren't using startSystemMove (which handles its own move)
|
||||
if (is_dragging) {
|
||||
move(event->globalPosition().toPoint() - drag_start_pos);
|
||||
event->accept();
|
||||
}
|
||||
#else
|
||||
// On Linux, the window manager handles the move, so we do nothing here.
|
||||
Q_UNUSED(event);
|
||||
#endif
|
||||
}
|
||||
|
||||
void ControllerOverlay::mouseReleaseEvent(QMouseEvent* event) {
|
||||
@@ -119,6 +153,14 @@ void ControllerOverlay::mouseReleaseEvent(QMouseEvent* event) {
|
||||
|
||||
void ControllerOverlay::resizeEvent(QResizeEvent* event) {
|
||||
QWidget::resizeEvent(event);
|
||||
// This ensures the layout and its widgets (like the size grip) are correctly repositioned on resize.
|
||||
layout()->update();
|
||||
}
|
||||
|
||||
void ControllerOverlay::SetVisible(bool visible) {
|
||||
is_enabled = visible;
|
||||
if (visible) {
|
||||
this->show();
|
||||
} else {
|
||||
this->hide();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ class ControllerOverlay : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
void SetVisible(bool visible);
|
||||
bool is_enabled = false;
|
||||
explicit ControllerOverlay(GMainWindow* parent);
|
||||
~ControllerOverlay() override;
|
||||
|
||||
@@ -35,4 +37,4 @@ private:
|
||||
|
||||
bool is_dragging = false;
|
||||
QPoint drag_start_pos;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -84,6 +84,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QFileDialog>
|
||||
#include <QGuiApplication>
|
||||
#include <QInputDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QProgressBar>
|
||||
@@ -94,6 +95,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
|
||||
#include <QStandardPaths>
|
||||
#include <QStatusBar>
|
||||
#include <QString>
|
||||
#include <QStyleFactory>
|
||||
#include <QSysInfo>
|
||||
#include <QUrl>
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
@@ -495,6 +497,12 @@ GMainWindow::GMainWindow(std::unique_ptr<QtConfig> config_, bool has_broken_vulk
|
||||
// Create a non-modal QMessageBox instance with a nullptr parent to make it a top-level window.
|
||||
// This prevents it from blocking the main application window.
|
||||
auto* confirmation_dialog = new QMessageBox(nullptr);
|
||||
const bool is_gamescope = !qgetenv("GAMESCOPE_WIDTH").isEmpty() || qgetenv("XDG_CURRENT_DESKTOP") == "gamescope";
|
||||
if (is_gamescope) {
|
||||
confirmation_dialog->setWindowFlags(Qt::Window | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowStaysOnTopHint);
|
||||
confirmation_dialog->resize(650, 300);
|
||||
confirmation_dialog->setStyleSheet(QStringLiteral("font-size: 11pt;"));
|
||||
}
|
||||
confirmation_dialog->setAttribute(Qt::WA_DeleteOnClose); // This ensures it is deleted automatically on close.
|
||||
confirmation_dialog->setWindowModality(Qt::NonModal); // Explicitly set modality.
|
||||
confirmation_dialog->setWindowTitle(tr("First-Time Setup"));
|
||||
@@ -854,15 +862,13 @@ void GMainWindow::SoftwareKeyboardShowNormal() {
|
||||
}
|
||||
|
||||
const auto& layout = render_window->GetFramebufferLayout();
|
||||
|
||||
const auto x = layout.screen.left;
|
||||
const auto y = layout.screen.top;
|
||||
const auto w = layout.screen.GetWidth();
|
||||
const auto h = layout.screen.GetHeight();
|
||||
const auto scale_ratio = devicePixelRatioF();
|
||||
|
||||
software_keyboard->ShowNormalKeyboard(render_window->mapToGlobal(QPoint(x, y) / scale_ratio),
|
||||
QSize(w, h) / scale_ratio);
|
||||
software_keyboard->ShowNormalKeyboard(render_window->mapToGlobal(QPoint(x, y)),
|
||||
QSize(w, h));
|
||||
}
|
||||
|
||||
void GMainWindow::SoftwareKeyboardShowTextCheck(
|
||||
@@ -895,11 +901,10 @@ void GMainWindow::SoftwareKeyboardShowInline(
|
||||
(1.0f - appear_parameters.key_top_scale_y))));
|
||||
const auto w = static_cast<int>(layout.screen.GetWidth() * appear_parameters.key_top_scale_x);
|
||||
const auto h = static_cast<int>(layout.screen.GetHeight() * appear_parameters.key_top_scale_y);
|
||||
const auto scale_ratio = devicePixelRatioF();
|
||||
|
||||
software_keyboard->ShowInlineKeyboard(std::move(appear_parameters),
|
||||
render_window->mapToGlobal(QPoint(x, y) / scale_ratio),
|
||||
QSize(w, h) / scale_ratio);
|
||||
render_window->mapToGlobal(QPoint(x, y)),
|
||||
QSize(w, h));
|
||||
}
|
||||
|
||||
void GMainWindow::SoftwareKeyboardHideInline() {
|
||||
@@ -979,13 +984,11 @@ void GMainWindow::WebBrowserOpenWebPage(const std::string& main_url,
|
||||
}
|
||||
|
||||
const auto& layout = render_window->GetFramebufferLayout();
|
||||
const auto scale_ratio = devicePixelRatioF();
|
||||
web_applet->resize(layout.screen.GetWidth() / scale_ratio,
|
||||
layout.screen.GetHeight() / scale_ratio);
|
||||
web_applet->move(layout.screen.left / scale_ratio,
|
||||
(layout.screen.top / scale_ratio) + menuBar()->height());
|
||||
web_applet->setZoomFactor(static_cast<qreal>(layout.screen.GetWidth() / scale_ratio) /
|
||||
static_cast<qreal>(Layout::ScreenUndocked::Width));
|
||||
web_applet->resize(layout.screen.GetWidth(), layout.screen.GetHeight());
|
||||
web_applet->move(layout.screen.left,
|
||||
(layout.screen.top) + menuBar()->height());
|
||||
web_applet->setZoomFactor(static_cast<qreal>(layout.screen.GetWidth()) /
|
||||
static_cast<qreal>(Layout::ScreenUndocked::Width));
|
||||
|
||||
web_applet->setFocus();
|
||||
web_applet->show();
|
||||
@@ -1159,9 +1162,6 @@ void GMainWindow::InitializeWidgets() {
|
||||
multiplayer_room_overlay = new MultiplayerRoomOverlay(this);
|
||||
multiplayer_room_overlay->hide();
|
||||
|
||||
connect(this, &GMainWindow::EmulationStarting, multiplayer_room_overlay, &MultiplayerRoomOverlay::OnEmulationStarting);
|
||||
connect(this, &GMainWindow::EmulationStopping, multiplayer_room_overlay, &MultiplayerRoomOverlay::OnEmulationStopping);
|
||||
|
||||
vram_overlay = new VramOverlay(this);
|
||||
vram_overlay->hide();
|
||||
|
||||
@@ -1353,6 +1353,26 @@ void GMainWindow::InitializeWidgets() {
|
||||
|
||||
statusBar()->setVisible(true);
|
||||
setStyleSheet(QStringLiteral("QStatusBar::item{border: none;}"));
|
||||
|
||||
const bool is_gamescope = !qgetenv("GAMESCOPE_WIDTH").isEmpty() || qgetenv("XDG_CURRENT_DESKTOP") == "gamescope";
|
||||
if (is_gamescope) {
|
||||
statusBar()->setSizeGripEnabled(true);
|
||||
this->menuBar()->setNativeMenuBar(false);
|
||||
|
||||
QString gamescope_style = qApp->styleSheet();
|
||||
gamescope_style.append(QStringLiteral("QMenu { background-color: #2b2b2b; border: 1px solid #3d3d3d; padding: 2px; } "
|
||||
"QMenu::item { padding: 5px 25px 5px 20px; } "
|
||||
"QMenu::item:selected { background-color: #3d3d3d; }"));
|
||||
qApp->setStyleSheet(gamescope_style);
|
||||
|
||||
multiplayer_room_overlay->resize(360, 240);
|
||||
|
||||
this->setContentsMargins(0, 0, 0, 0);
|
||||
this->layout()->setContentsMargins(0, 0, 0, 0);
|
||||
this->layout()->setSpacing(0);
|
||||
ui->horizontalLayout->setContentsMargins(0, 0, 0, 0);
|
||||
ui->horizontalLayout->setSpacing(0);
|
||||
}
|
||||
}
|
||||
|
||||
void GMainWindow::InitializeDebugWidgets() {
|
||||
@@ -1483,6 +1503,12 @@ void GMainWindow::InitializeHotkeys() {
|
||||
}
|
||||
|
||||
void GMainWindow::SetDefaultUIGeometry() {
|
||||
const bool is_gamescope = !qgetenv("GAMESCOPE_WIDTH").isEmpty() || qgetenv("XDG_CURRENT_DESKTOP") == "gamescope";
|
||||
|
||||
if (is_gamescope) {
|
||||
this->resize(1280, 800);
|
||||
return;
|
||||
}
|
||||
// geometry: 53% of the window contents are in the upper screen half, 47% in the lower half
|
||||
const QRect screenRect = QGuiApplication::primaryScreen()->geometry();
|
||||
|
||||
@@ -1495,15 +1521,25 @@ void GMainWindow::SetDefaultUIGeometry() {
|
||||
}
|
||||
|
||||
void GMainWindow::RestoreUIState() {
|
||||
const bool is_gamescope = !qgetenv("GAMESCOPE_WIDTH").isEmpty() || qgetenv("XDG_CURRENT_DESKTOP") == "gamescope";
|
||||
|
||||
setWindowFlags(windowFlags() & ~Qt::FramelessWindowHint);
|
||||
restoreGeometry(UISettings::values.geometry);
|
||||
|
||||
if (!is_gamescope) {
|
||||
restoreGeometry(UISettings::values.geometry);
|
||||
}
|
||||
|
||||
// Work-around because the games list isn't supposed to be full screen
|
||||
if (isFullScreen()) {
|
||||
showNormal();
|
||||
}
|
||||
restoreState(UISettings::values.state);
|
||||
render_window->setWindowFlags(render_window->windowFlags() & ~Qt::FramelessWindowHint);
|
||||
render_window->restoreGeometry(UISettings::values.renderwindow_geometry);
|
||||
|
||||
if (!is_gamescope) {
|
||||
render_window->setWindowFlags(render_window->windowFlags() & ~Qt::FramelessWindowHint);
|
||||
render_window->restoreGeometry(UISettings::values.renderwindow_geometry);
|
||||
}
|
||||
|
||||
#if MICROPROFILE_ENABLED
|
||||
microProfileDialog->restoreGeometry(UISettings::values.microprofile_geometry);
|
||||
microProfileDialog->setVisible(UISettings::values.microprofile_visible.GetValue());
|
||||
@@ -1528,13 +1564,12 @@ void GMainWindow::RestoreUIState() {
|
||||
ui->action_Show_Status_Bar->setChecked(UISettings::values.show_status_bar.GetValue());
|
||||
statusBar()->setVisible(ui->action_Show_Status_Bar->isChecked());
|
||||
|
||||
// Force the performance overlay to be off on startup
|
||||
// Force overlays off on startup
|
||||
ui->action_Show_Performance_Overlay->setChecked(false);
|
||||
if (performance_overlay) {
|
||||
performance_overlay->SetVisible(false);
|
||||
}
|
||||
|
||||
// Force the VRAM overlay to be off on startup
|
||||
ui->action_Show_Vram_Overlay->setChecked(false);
|
||||
if (vram_overlay) {
|
||||
vram_overlay->SetVisible(false);
|
||||
@@ -2937,6 +2972,12 @@ void GMainWindow::OnGameListVerifyIntegrity(const std::string& game_path) {
|
||||
};
|
||||
|
||||
QProgressDialog progress(tr("Verifying integrity..."), tr("Cancel"), 0, 100, this);
|
||||
|
||||
const bool is_gamescope = !qgetenv("GAMESCOPE_WIDTH").isEmpty() || qgetenv("XDG_CURRENT_DESKTOP") == "gamescope";
|
||||
if (is_gamescope) {
|
||||
progress.setWindowFlags(Qt::Window | Qt::WindowTitleHint | Qt::WindowStaysOnTopHint);
|
||||
}
|
||||
|
||||
progress.setWindowModality(Qt::WindowModal);
|
||||
progress.setMinimumDuration(100);
|
||||
progress.setAutoClose(false);
|
||||
@@ -4015,14 +4056,16 @@ void GMainWindow::ShowFullscreen() {
|
||||
}
|
||||
|
||||
void GMainWindow::HideFullscreen() {
|
||||
const bool is_gamescope = !qgetenv("GAMESCOPE_WIDTH").isEmpty() || qgetenv("XDG_CURRENT_DESKTOP") == "gamescope";
|
||||
|
||||
if (ui->action_Single_Window_Mode->isChecked()) {
|
||||
if (UsingExclusiveFullscreen()) {
|
||||
showNormal();
|
||||
restoreGeometry(UISettings::values.geometry);
|
||||
if (!is_gamescope) restoreGeometry(UISettings::values.geometry);
|
||||
} else {
|
||||
hide();
|
||||
setWindowFlags(windowFlags() & ~Qt::FramelessWindowHint);
|
||||
restoreGeometry(UISettings::values.geometry);
|
||||
if (!is_gamescope) restoreGeometry(UISettings::values.geometry);
|
||||
raise();
|
||||
show();
|
||||
}
|
||||
@@ -4032,15 +4075,18 @@ void GMainWindow::HideFullscreen() {
|
||||
} else {
|
||||
if (UsingExclusiveFullscreen()) {
|
||||
render_window->showNormal();
|
||||
render_window->restoreGeometry(UISettings::values.renderwindow_geometry);
|
||||
if (!is_gamescope) render_window->restoreGeometry(UISettings::values.renderwindow_geometry);
|
||||
} else {
|
||||
render_window->hide();
|
||||
render_window->setWindowFlags(windowFlags() & ~Qt::FramelessWindowHint);
|
||||
render_window->restoreGeometry(UISettings::values.renderwindow_geometry);
|
||||
if (!is_gamescope) render_window->restoreGeometry(UISettings::values.renderwindow_geometry);
|
||||
render_window->raise();
|
||||
render_window->show();
|
||||
}
|
||||
}
|
||||
|
||||
if (is_gamescope) {
|
||||
}
|
||||
}
|
||||
|
||||
void GMainWindow::ToggleWindowMode() {
|
||||
@@ -4069,9 +4115,14 @@ void GMainWindow::ToggleWindowMode() {
|
||||
}
|
||||
|
||||
void GMainWindow::ResetWindowSize(u32 width, u32 height) {
|
||||
const bool is_gamescope = !qgetenv("GAMESCOPE_WIDTH").isEmpty() || qgetenv("XDG_CURRENT_DESKTOP") == "gamescope";
|
||||
if (is_gamescope) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto aspect_ratio = Layout::EmulationAspectRatio(
|
||||
static_cast<Layout::AspectRatio>(Settings::values.aspect_ratio.GetValue()),
|
||||
static_cast<float>(height) / width);
|
||||
static_cast<float>(height) / width);
|
||||
if (!ui->action_Single_Window_Mode->isChecked()) {
|
||||
render_window->resize(height / aspect_ratio, height);
|
||||
} else {
|
||||
@@ -4441,6 +4492,15 @@ bool GMainWindow::question(QWidget* parent, const QString& title, const QString&
|
||||
QMessageBox::StandardButtons buttons,
|
||||
QMessageBox::StandardButton defaultButton) {
|
||||
QMessageBox* box_dialog = new QMessageBox(parent);
|
||||
|
||||
const bool is_gamescope = UISettings::IsGamescope();
|
||||
if (is_gamescope) {
|
||||
box_dialog->setWindowFlags(Qt::Window | Qt::CustomizeWindowHint | Qt::WindowTitleHint);
|
||||
box_dialog->setWindowModality(Qt::NonModal);
|
||||
box_dialog->setFixedSize(600, 250);
|
||||
box_dialog->setStyleSheet(QStringLiteral("font-size: 11pt;"));
|
||||
}
|
||||
|
||||
box_dialog->setWindowTitle(title);
|
||||
box_dialog->setText(text);
|
||||
box_dialog->setStandardButtons(buttons);
|
||||
@@ -5026,7 +5086,10 @@ void GMainWindow::OnToggleControllerOverlay() {
|
||||
controller_overlay = new ControllerOverlay(this);
|
||||
}
|
||||
if (controller_overlay) {
|
||||
controller_overlay->setVisible(visible);
|
||||
|
||||
controller_overlay->SetVisible(visible);
|
||||
this->update();
|
||||
QCoreApplication::processEvents();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5047,7 +5110,6 @@ void GMainWindow::OnTogglePerformanceOverlay() {
|
||||
if (performance_overlay) {
|
||||
const bool is_checked = ui->action_Show_Performance_Overlay->isChecked();
|
||||
performance_overlay->SetVisible(is_checked);
|
||||
|
||||
UISettings::values.show_performance_overlay = is_checked;
|
||||
}
|
||||
}
|
||||
@@ -5066,7 +5128,6 @@ void GMainWindow::OnToggleVramOverlay() {
|
||||
if (vram_overlay) {
|
||||
const bool is_checked = ui->action_Show_Vram_Overlay->isChecked();
|
||||
vram_overlay->SetVisible(is_checked);
|
||||
|
||||
UISettings::values.show_vram_overlay = is_checked;
|
||||
}
|
||||
}
|
||||
@@ -5564,10 +5625,14 @@ void GMainWindow::UpdateStatusButtons() {
|
||||
}
|
||||
|
||||
void GMainWindow::UpdateUISettings() {
|
||||
if (!ui->action_Fullscreen->isChecked()) {
|
||||
const bool is_gamescope = !qgetenv("GAMESCOPE_WIDTH").isEmpty() || qgetenv("XDG_CURRENT_DESKTOP") == "gamescope";
|
||||
|
||||
// Only save/restore geometry if we are NOT in gamescope to prevent resolution bugs
|
||||
if (!ui->action_Fullscreen->isChecked() && !is_gamescope) {
|
||||
UISettings::values.geometry = saveGeometry();
|
||||
UISettings::values.renderwindow_geometry = render_window->saveGeometry();
|
||||
}
|
||||
|
||||
UISettings::values.state = saveState();
|
||||
#if MICROPROFILE_ENABLED
|
||||
UISettings::values.microprofile_geometry = microProfileDialog->saveGeometry();
|
||||
@@ -6029,80 +6094,69 @@ void VolumeButton::ResetMultiplier() {
|
||||
#endif
|
||||
|
||||
static void SetHighDPIAttributes() {
|
||||
[[maybe_unused]] const bool is_gamescope = !qgetenv("GAMESCOPE_WIDTH").isEmpty() ||
|
||||
qgetenv("XDG_CURRENT_DESKTOP") == "gamescope" ||
|
||||
!qgetenv("STEAM_DECK").isEmpty();
|
||||
|
||||
#ifdef _WIN32
|
||||
// For Windows, we want to avoid scaling artifacts on fractional scaling ratios.
|
||||
// This is done by setting the optimal scaling policy for the primary screen.
|
||||
// Windows logic: Set policy globally.
|
||||
// removed the 'temp QApplication' here because in Qt 6 it locks the DPI logic
|
||||
// before our environment overrides in main() can take effect.
|
||||
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(
|
||||
Qt::HighDpiScaleFactorRoundingPolicy::Round);
|
||||
|
||||
// Create a temporary QApplication.
|
||||
int temp_argc = 0;
|
||||
char** temp_argv = nullptr;
|
||||
QApplication temp{temp_argc, temp_argv};
|
||||
|
||||
// Get the current screen geometry.
|
||||
const QScreen* primary_screen = QGuiApplication::primaryScreen();
|
||||
if (primary_screen == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QRect screen_rect = primary_screen->geometry();
|
||||
const int real_width = screen_rect.width();
|
||||
const int real_height = screen_rect.height();
|
||||
const float real_ratio = primary_screen->logicalDotsPerInch() / 96.0f;
|
||||
|
||||
// Recommended minimum width and height for proper window fit.
|
||||
// Any screen with a lower resolution than this will still have a scale of 1.
|
||||
constexpr float minimum_width = 1350.0f;
|
||||
constexpr float minimum_height = 900.0f;
|
||||
|
||||
const float width_ratio = std::max(1.0f, real_width / minimum_width);
|
||||
const float height_ratio = std::max(1.0f, real_height / minimum_height);
|
||||
|
||||
// Get the lower of the 2 ratios and truncate, this is the maximum integer scale.
|
||||
const float max_ratio = std::trunc(std::min(width_ratio, height_ratio));
|
||||
|
||||
if (max_ratio > real_ratio) {
|
||||
QApplication::setHighDpiScaleFactorRoundingPolicy(
|
||||
Qt::HighDpiScaleFactorRoundingPolicy::Round);
|
||||
} else {
|
||||
QApplication::setHighDpiScaleFactorRoundingPolicy(
|
||||
Qt::HighDpiScaleFactorRoundingPolicy::Floor);
|
||||
}
|
||||
#else
|
||||
// Other OSes should be better than Windows at fractional scaling.
|
||||
QApplication::setHighDpiScaleFactorRoundingPolicy(
|
||||
Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
|
||||
#endif
|
||||
|
||||
// Set the DPI awareness for better scaling on Windows
|
||||
#ifdef _WIN32
|
||||
// Enable Per Monitor DPI Awareness for Windows 8.1+
|
||||
SetProcessDPIAware();
|
||||
|
||||
// For Windows 10+, use Per Monitor v2 DPI Awareness
|
||||
// This provides better scaling for multi-monitor setups
|
||||
HMODULE shcore = LoadLibrary(L"shcore.dll");
|
||||
if (shcore) {
|
||||
typedef HRESULT(WINAPI* SetProcessDpiAwarenessFunc)(int);
|
||||
SetProcessDpiAwarenessFunc setProcessDpiAwareness =
|
||||
(SetProcessDpiAwarenessFunc)GetProcAddress(shcore, "SetProcessDpiAwareness");
|
||||
if (setProcessDpiAwareness) {
|
||||
// PROCESS_PER_MONITOR_DPI_AWARE_V2 = 2
|
||||
setProcessDpiAwareness(2);
|
||||
setProcessDpiAwareness(2); // PROCESS_PER_MONITOR_DPI_AWARE_V2
|
||||
}
|
||||
FreeLibrary(shcore);
|
||||
}
|
||||
#else
|
||||
if (is_gamescope) {
|
||||
// PassThrough prevents Qt6 from recursively expanding layouts to fit rounded DPIs
|
||||
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(
|
||||
Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
// Set environment variables for AppImage compatibility
|
||||
// This must be done before the QApplication is created.
|
||||
// 1. Detect Gamescope/Steam Deck hardware
|
||||
const bool is_gamescope = UISettings::IsGamescope();
|
||||
|
||||
if (is_gamescope) {
|
||||
// Kill the scaling system entirely
|
||||
qputenv("QT_ENABLE_HIGHDPI_SCALING", "0");
|
||||
qputenv("QT_SCALE_FACTOR", "1");
|
||||
qputenv("QT_AUTO_SCREEN_SCALE_FACTOR", "0");
|
||||
|
||||
#ifdef __linux__
|
||||
qputenv("QT_QPA_PLATFORM", "xcb");
|
||||
qputenv("QT_FONT_DPI", "96");
|
||||
#endif
|
||||
|
||||
// Stop Qt from querying physical hardware DPI for text/widgets
|
||||
qputenv("QT_USE_PHYSICAL_DPI", "0");
|
||||
|
||||
// Force the legacy coordinate system for X11/XCB
|
||||
qputenv("QT_SCREEN_SCALE_FACTORS", "1");
|
||||
|
||||
// Ensure Gamescope compositor handles Citron menus correctly
|
||||
QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuBar);
|
||||
QCoreApplication::setAttribute(Qt::AA_DontUseNativeDialogs);
|
||||
qputenv("QT_WAYLAND_SHELL_INTEGRATION", "xdg-shell");
|
||||
}
|
||||
|
||||
// 2. Setup AppImage environment
|
||||
const bool is_appimage = !qgetenv("APPIMAGE").isEmpty();
|
||||
if (is_appimage) {
|
||||
// Fixes Wayland crash with NVIDIA drivers by disabling explicit sync.
|
||||
qputenv("QT_WAYLAND_DISABLE_EXPLICIT_SYNC", "1");
|
||||
|
||||
// Tell the bundled OpenSSL where to find the bundled certificates.
|
||||
const QDir app_dir(QCoreApplication::applicationDirPath());
|
||||
const QString certs_path = app_dir.filePath(QString::fromLatin1("../etc/ssl/certs"));
|
||||
qputenv("SSL_CERT_DIR", certs_path.toUtf8());
|
||||
@@ -6133,73 +6187,73 @@ int main(int argc, char* argv[]) {
|
||||
|
||||
Common::ConfigureNvidiaEnvironmentFlags();
|
||||
|
||||
// Init settings params
|
||||
QCoreApplication::setOrganizationName(QStringLiteral("citron team"));
|
||||
QCoreApplication::setApplicationName(QStringLiteral("citron"));
|
||||
|
||||
#ifdef _WIN32
|
||||
// Increases the maximum open file limit to 8192
|
||||
_setmaxstdio(8192);
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
// If you start a bundle (binary) on OSX without the Terminal, the working directory is "/".
|
||||
// But since we require the working directory to be the executable path for the location of
|
||||
// the user folder in the Qt Frontend, we need to cd into that working directory
|
||||
const auto bin_path = Common::FS::GetBundleDirectory() / "..";
|
||||
chdir(Common::FS::PathToUTF8String(bin_path).c_str());
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
// Set the DISPLAY variable in order to open web browsers
|
||||
// TODO (lat9nq): Find a better solution for AppImages to start external applications
|
||||
if (QString::fromLocal8Bit(qgetenv("DISPLAY")).isEmpty()) {
|
||||
qputenv("DISPLAY", ":0");
|
||||
}
|
||||
|
||||
// Fix the Wayland appId. This needs to match the name of the .desktop file without the .desktop
|
||||
// suffix.
|
||||
QGuiApplication::setDesktopFileName(QStringLiteral("org.citron_emu.citron"));
|
||||
#endif
|
||||
|
||||
// Call policy attributes BEFORE creating the real QApplication instance
|
||||
SetHighDPIAttributes();
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
// Disables the "?" button on all dialogs. Disabled by default on Qt6.
|
||||
QCoreApplication::setAttribute(Qt::AA_DisableWindowContextHelpButton);
|
||||
#endif
|
||||
|
||||
// Enables the core to make the qt created contexts current on std::threads
|
||||
QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity);
|
||||
|
||||
QApplication app(argc, argv);
|
||||
|
||||
#ifdef _WIN32
|
||||
OverrideWindowsFont();
|
||||
#endif
|
||||
|
||||
if (is_gamescope) {
|
||||
app.setStyleSheet(app.styleSheet().append(QStringLiteral(
|
||||
"QDialog { "
|
||||
" font-size: 11pt; "
|
||||
" margin: 0px; "
|
||||
" padding: 0px; "
|
||||
"}"
|
||||
"QLabel { font-size: 10pt; }"
|
||||
)));
|
||||
|
||||
app.setStyle(QStyleFactory::create(QStringLiteral("Fusion")));
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
if (QGuiApplication::platformName().startsWith(QStringLiteral("wayland"))) {
|
||||
Settings::values.is_wayland_platform.SetValue(true);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CITRON_USE_AUTO_UPDATER
|
||||
// Check for and apply staged updates before starting the main application
|
||||
#ifdef CITRON_USE_AUTO_UPDATER
|
||||
std::filesystem::path app_dir = std::filesystem::path(QCoreApplication::applicationDirPath().toStdString());
|
||||
|
||||
#ifdef _WIN32
|
||||
// On Windows, updates are applied by the helper script after the app exits.
|
||||
// If we find a staging directory here, it means the helper script failed.
|
||||
// Clean it up to avoid confusion.
|
||||
std::filesystem::path staging_path = app_dir / "update_staging";
|
||||
if (std::filesystem::exists(staging_path)) {
|
||||
try {
|
||||
std::filesystem::remove_all(staging_path);
|
||||
} catch (...) {
|
||||
// Ignore cleanup errors
|
||||
}
|
||||
} catch (...) {}
|
||||
}
|
||||
#else
|
||||
// On Linux, apply staged updates at startup as before
|
||||
if (Updater::UpdaterService::HasStagedUpdate(app_dir)) {
|
||||
if (Updater::UpdaterService::ApplyStagedUpdate(app_dir)) {
|
||||
// Show a simple message that update was applied
|
||||
QMessageBox::information(nullptr, QObject::tr("Update Applied"),
|
||||
QObject::tr("Citron has been updated successfully!"));
|
||||
}
|
||||
@@ -6207,37 +6261,29 @@ int main(int argc, char* argv[]) {
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
OverrideWindowsFont();
|
||||
#endif
|
||||
|
||||
// Workaround for QTBUG-85409, for Suzhou numerals the number 1 is actually \u3021
|
||||
// so we can see if we get \u3008 instead
|
||||
// TL;DR all other number formats are consecutive in unicode code points
|
||||
// This bug is fixed in Qt6, specifically 6.0.0-alpha1
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
const QLocale locale = QLocale::system();
|
||||
if (QStringLiteral("\u3008") == locale.toString(1)) {
|
||||
QLocale::setDefault(QLocale::system().name());
|
||||
}
|
||||
#endif
|
||||
|
||||
// Qt changes the locale and causes issues in float conversion using std::to_string() when
|
||||
// generating shaders
|
||||
setlocale(LC_ALL, "C");
|
||||
|
||||
GMainWindow main_window{std::move(config), has_broken_vulkan};
|
||||
|
||||
app.setStyle(new RainbowStyle(app.style()));
|
||||
|
||||
main_window.show();
|
||||
|
||||
if (is_gamescope) {
|
||||
QTimer::singleShot(200, &main_window, [&main_window]() {
|
||||
main_window.showMaximized();
|
||||
if (main_window.layout()) {
|
||||
main_window.layout()->activate();
|
||||
}
|
||||
main_window.update();
|
||||
main_window.raise();
|
||||
main_window.activateWindow();
|
||||
});
|
||||
}
|
||||
|
||||
QObject::connect(&app, &QGuiApplication::applicationStateChanged, &main_window,
|
||||
&GMainWindow::OnAppFocusStateChanged);
|
||||
|
||||
int result = app.exec();
|
||||
detached_tasks.WaitForAllTasks();
|
||||
return result;
|
||||
return app.exec();
|
||||
}
|
||||
|
||||
void GMainWindow::OnCheckForUpdates() {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 citron Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <QComboBox>
|
||||
@@ -24,10 +25,22 @@
|
||||
enum class ConnectionType : u8 { TraversalServer, IP };
|
||||
|
||||
DirectConnectWindow::DirectConnectWindow(Core::System& system_, QWidget* parent)
|
||||
: QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint),
|
||||
: QDialog(parent),
|
||||
ui(std::make_unique<Ui::DirectConnect>()), system{system_}, room_network{
|
||||
system.GetRoomNetwork()} {
|
||||
|
||||
const bool is_gamescope = UISettings::IsGamescope();
|
||||
if (is_gamescope) {
|
||||
setWindowFlags(Qt::Window | Qt::CustomizeWindowHint | Qt::WindowTitleHint);
|
||||
setWindowModality(Qt::NonModal);
|
||||
|
||||
int w = 800;
|
||||
int h = 500;
|
||||
setFixedSize(w, h);
|
||||
} else {
|
||||
setWindowFlags(Qt::Dialog | Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint);
|
||||
}
|
||||
|
||||
ui->setupUi(this);
|
||||
|
||||
// setup the watcher for background connections
|
||||
|
||||
@@ -32,10 +32,23 @@
|
||||
HostRoomWindow::HostRoomWindow(QWidget* parent, QStandardItemModel* list,
|
||||
std::shared_ptr<Core::AnnounceMultiplayerSession> session,
|
||||
Core::System& system_)
|
||||
: QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint),
|
||||
: QDialog(parent),
|
||||
ui(std::make_unique<Ui::HostRoom>()),
|
||||
announce_multiplayer_session(session), system{system_}, room_network{
|
||||
system.GetRoomNetwork()} {
|
||||
|
||||
const bool is_gamescope = UISettings::IsGamescope();
|
||||
if (is_gamescope) {
|
||||
setWindowFlags(Qt::Window | Qt::CustomizeWindowHint | Qt::WindowTitleHint);
|
||||
setWindowModality(Qt::NonModal);
|
||||
|
||||
int w = 800;
|
||||
int h = 500;
|
||||
setFixedSize(w, h);
|
||||
} else {
|
||||
setWindowFlags(Qt::Dialog | Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint);
|
||||
}
|
||||
|
||||
ui->setupUi(this);
|
||||
|
||||
// set up validation for all of the fields
|
||||
|
||||
@@ -27,10 +27,23 @@
|
||||
|
||||
Lobby::Lobby(QWidget* parent, QStandardItemModel* list,
|
||||
std::shared_ptr<Core::AnnounceMultiplayerSession> session, Core::System& system_)
|
||||
: QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint),
|
||||
: QDialog(parent),
|
||||
ui(std::make_unique<Ui::Lobby>()),
|
||||
announce_multiplayer_session(session), system{system_}, room_network{
|
||||
system.GetRoomNetwork()} {
|
||||
|
||||
const bool is_gamescope = UISettings::IsGamescope();
|
||||
if (is_gamescope) {
|
||||
setWindowFlags(Qt::Window | Qt::CustomizeWindowHint | Qt::WindowTitleHint);
|
||||
setWindowModality(Qt::NonModal);
|
||||
|
||||
int w = 800;
|
||||
int h = 500;
|
||||
setFixedSize(w, h);
|
||||
} else {
|
||||
setWindowFlags(Qt::Dialog | Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint);
|
||||
}
|
||||
|
||||
ui->setupUi(this);
|
||||
|
||||
// setup the watcher for background connections
|
||||
|
||||
@@ -136,4 +136,15 @@ namespace UISettings {
|
||||
config.value(QStringLiteral("microProfileDialogGeometry")).toByteArray();
|
||||
}
|
||||
|
||||
bool IsGamescope() {
|
||||
#ifdef __linux__
|
||||
static const bool is_gamescope = !qgetenv("GAMESCOPE_WIDTH").isEmpty() ||
|
||||
qgetenv("XDG_CURRENT_DESKTOP") == "gamescope" ||
|
||||
!qgetenv("STEAM_DECK").isEmpty();
|
||||
return is_gamescope;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace UISettings
|
||||
|
||||
@@ -37,6 +37,7 @@ namespace Settings {
|
||||
namespace UISettings {
|
||||
|
||||
bool IsDarkTheme();
|
||||
bool IsGamescope();
|
||||
|
||||
struct ContextualShortcut {
|
||||
std::string keyseq;
|
||||
|
||||
@@ -220,9 +220,18 @@ void UpdaterDialog::OnRestartButtonClicked() {
|
||||
}
|
||||
|
||||
void UpdaterDialog::SetupUI() {
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
const bool is_gamescope = UISettings::IsGamescope();
|
||||
|
||||
setMinimumSize(size());
|
||||
if (is_gamescope) {
|
||||
// Match the behavior of ConfigureDialog to ensure focus and visibility on Steam Deck
|
||||
setWindowFlags(Qt::Window | Qt::CustomizeWindowHint | Qt::WindowTitleHint);
|
||||
setWindowModality(Qt::NonModal);
|
||||
resize(1100, 700);
|
||||
} else {
|
||||
// Desktop remains untouched
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
setMinimumSize(size());
|
||||
}
|
||||
|
||||
ui->currentVersionValue->setText(QString::fromStdString(updater_service->GetCurrentVersion()));
|
||||
ui->appImageSelectorLabel->setVisible(false);
|
||||
|
||||
@@ -121,13 +121,7 @@
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="changelogLayout">
|
||||
<item>
|
||||
<widget class="QTextBrowser" name="changelogText">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>150</height>
|
||||
</size>
|
||||
</property>
|
||||
<widget class="QTextBrowser" name="changelogText">
|
||||
<property name="html">
|
||||
<string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||
@@ -192,7 +186,6 @@ p, li { white-space: pre-wrap; }
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<!-- Start of added widgets -->
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="appImageSelectorLayout">
|
||||
<item>
|
||||
@@ -207,7 +200,6 @@ p, li { white-space: pre-wrap; }
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<!-- End of added widgets -->
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="buttonLayout">
|
||||
<item>
|
||||
|
||||
@@ -18,12 +18,18 @@
|
||||
#include "network/room.h"
|
||||
#include "citron/uisettings.h"
|
||||
|
||||
MultiplayerRoomOverlay::MultiplayerRoomOverlay(GMainWindow* parent)
|
||||
: QWidget(parent), main_window(parent) {
|
||||
MultiplayerRoomOverlay::MultiplayerRoomOverlay(QWidget* parent)
|
||||
: QWidget(parent) {
|
||||
|
||||
setAttribute(Qt::WA_TranslucentBackground, true);
|
||||
setWindowFlags(Qt::FramelessWindowHint | Qt::Tool | Qt::WindowStaysOnTopHint);
|
||||
setFocusPolicy(Qt::ClickFocus);
|
||||
main_window = qobject_cast<GMainWindow*>(parent->window());
|
||||
|
||||
// Switched to Qt::Tool to allow keyboard focus for typing in chat
|
||||
setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);
|
||||
setAttribute(Qt::WA_TranslucentBackground);
|
||||
|
||||
// Set smaller sizes for Steam Deck
|
||||
setMinimumSize(240, 180);
|
||||
resize(260, 220);
|
||||
|
||||
main_layout = new QGridLayout(this);
|
||||
main_layout->setContentsMargins(padding, padding, 0, 0);
|
||||
@@ -58,11 +64,25 @@ MultiplayerRoomOverlay::MultiplayerRoomOverlay(GMainWindow* parent)
|
||||
update_timer.setSingleShot(false);
|
||||
connect(&update_timer, &QTimer::timeout, this, &MultiplayerRoomOverlay::UpdateRoomData);
|
||||
|
||||
connect(parent, &GMainWindow::themeChanged, this, &MultiplayerRoomOverlay::UpdateTheme);
|
||||
if (main_window) {
|
||||
connect(main_window, &GMainWindow::themeChanged, this, &MultiplayerRoomOverlay::UpdateTheme);
|
||||
}
|
||||
UpdateTheme();
|
||||
|
||||
setMinimumSize(280, 220);
|
||||
resize(320, 280);
|
||||
const bool is_gamescope = UISettings::IsGamescope();
|
||||
if (is_gamescope) {
|
||||
setMinimumSize(320, 260);
|
||||
resize(600, 520);
|
||||
|
||||
players_online_label->setFont(QFont(QString::fromUtf8("Segoe UI"), 11, QFont::Bold));
|
||||
|
||||
this->padding = 12;
|
||||
main_layout->setContentsMargins(padding, padding, padding, padding);
|
||||
} else {
|
||||
setMinimumSize(280, 220);
|
||||
resize(320, 280);
|
||||
}
|
||||
|
||||
UpdatePosition();
|
||||
}
|
||||
|
||||
@@ -108,48 +128,35 @@ void MultiplayerRoomOverlay::resizeEvent(QResizeEvent* event) { QWidget::resizeE
|
||||
bool MultiplayerRoomOverlay::eventFilter(QObject* watched, QEvent* event) { if (event->type() == QEvent::MouseButtonPress) { if (chat_room_widget->hasFocus()) { chat_room_widget->clearFocus(); } } return QObject::eventFilter(watched, event); }
|
||||
|
||||
|
||||
#if defined(Q_OS_LINUX)
|
||||
void MultiplayerRoomOverlay::mousePressEvent(QMouseEvent* event) {
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
if (size_grip->geometry().contains(event->pos())) {
|
||||
// Let the size grip handle the event
|
||||
} else if (!childAt(event->pos()) || childAt(event->pos()) == this) {
|
||||
if (windowHandle()) {
|
||||
QTimer::singleShot(0, this, [this] { windowHandle()->startSystemMove(); });
|
||||
}
|
||||
}
|
||||
}
|
||||
QWidget::mousePressEvent(event);
|
||||
}
|
||||
|
||||
void MultiplayerRoomOverlay::mouseMoveEvent(QMouseEvent* event) {
|
||||
QWidget::mouseMoveEvent(event);
|
||||
}
|
||||
|
||||
#else // Windows and other platforms
|
||||
void MultiplayerRoomOverlay::mousePressEvent(QMouseEvent* event) {
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
if (size_grip->geometry().contains(event->pos())) {
|
||||
// Let the size grip handle the event
|
||||
} else if (!childAt(event->pos()) || childAt(event->pos()) == this) {
|
||||
if (event->button() == Qt::LeftButton && !size_grip->geometry().contains(event->pos())) {
|
||||
const bool is_gamescope = !qgetenv("GAMESCOPE_WIDTH").isEmpty() || qgetenv("XDG_CURRENT_DESKTOP") == "gamescope";
|
||||
if (is_gamescope) {
|
||||
is_dragging = true;
|
||||
drag_start_pos = event->globalPosition().toPoint();
|
||||
widget_start_pos = this->pos();
|
||||
drag_start_pos = event->globalPosition().toPoint() - this->pos();
|
||||
setCursor(Qt::ClosedHandCursor);
|
||||
} else {
|
||||
#if defined(Q_OS_LINUX)
|
||||
if (windowHandle()) windowHandle()->startSystemMove();
|
||||
#else
|
||||
is_dragging = true;
|
||||
drag_start_pos = event->globalPosition().toPoint() - this->pos();
|
||||
setCursor(Qt::ClosedHandCursor);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
QWidget::mousePressEvent(event);
|
||||
}
|
||||
|
||||
void MultiplayerRoomOverlay::mouseMoveEvent(QMouseEvent* event) {
|
||||
if (is_dragging) {
|
||||
QPoint delta = event->globalPosition().toPoint() - drag_start_pos;
|
||||
move(widget_start_pos + delta);
|
||||
has_been_moved = true;
|
||||
if (is_dragging && main_window) {
|
||||
QPoint new_pos = event->globalPosition().toPoint() - drag_start_pos;
|
||||
QPoint win_origin = main_window->mapToGlobal(QPoint(0, 0));
|
||||
move(std::clamp(new_pos.x(), win_origin.x(), win_origin.x() + main_window->width() - width()),
|
||||
std::clamp(new_pos.y(), win_origin.y(), win_origin.y() + main_window->height() - height()));
|
||||
}
|
||||
QWidget::mouseMoveEvent(event);
|
||||
}
|
||||
#endif
|
||||
|
||||
void MultiplayerRoomOverlay::mouseReleaseEvent(QMouseEvent* event) {
|
||||
if (event->button() == Qt::LeftButton && is_dragging) {
|
||||
|
||||
@@ -22,7 +22,7 @@ class MultiplayerRoomOverlay : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MultiplayerRoomOverlay(GMainWindow* parent);
|
||||
explicit MultiplayerRoomOverlay(QWidget* parent);
|
||||
~MultiplayerRoomOverlay() override;
|
||||
|
||||
void SetVisible(bool visible);
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#include <QPainter>
|
||||
#include <QPainterPath>
|
||||
#include <QScreen>
|
||||
#include <QSizeGrip>
|
||||
#include <QGridLayout>
|
||||
#include <QTimer>
|
||||
#include <QMouseEvent>
|
||||
#include <QtMath>
|
||||
@@ -22,7 +24,7 @@
|
||||
#include <Windows.h>
|
||||
#include <comdef.h>
|
||||
#include <WbemIdl.h>
|
||||
#pragma comment(lib, "wbemuuid.lib") // For MSVC, helps the linker find the library
|
||||
#pragma comment(lib, "wbemuuid.lib")
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
@@ -37,85 +39,91 @@
|
||||
#include "video_core/gpu.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
|
||||
PerformanceOverlay::PerformanceOverlay(GMainWindow* parent)
|
||||
: QWidget(parent), main_window(parent) {
|
||||
PerformanceOverlay::PerformanceOverlay(QWidget* parent) : QWidget(UISettings::IsGamescope() ? nullptr : parent) {
|
||||
if (parent) {
|
||||
main_window = qobject_cast<GMainWindow*>(parent);
|
||||
}
|
||||
|
||||
if (UISettings::IsGamescope()) {
|
||||
setWindowFlags(Qt::ToolTip | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::WindowDoesNotAcceptFocus);
|
||||
setAttribute(Qt::WA_ShowWithoutActivating);
|
||||
} else {
|
||||
setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);
|
||||
}
|
||||
|
||||
// Set up the widget properties
|
||||
setAttribute(Qt::WA_TranslucentBackground, true);
|
||||
setWindowFlags(Qt::FramelessWindowHint | Qt::Tool | Qt::WindowStaysOnTopHint);
|
||||
setAttribute(Qt::WA_NoSystemBackground);
|
||||
setAttribute(Qt::WA_WState_ExplicitShowHide);
|
||||
|
||||
// Initialize fonts with better typography
|
||||
title_font = QFont(QString::fromUtf8("Segoe UI"), 9, QFont::Medium);
|
||||
value_font = QFont(QString::fromUtf8("Segoe UI"), 11, QFont::Bold);
|
||||
small_font = QFont(QString::fromUtf8("Segoe UI"), 8, QFont::Normal);
|
||||
if (UISettings::IsGamescope()) {
|
||||
title_font = QFont(QString::fromUtf8("Segoe UI"), 9, QFont::Bold);
|
||||
value_font = QFont(QString::fromUtf8("Segoe UI"), 10, QFont::Bold);
|
||||
small_font = QFont(QString::fromUtf8("Segoe UI"), 8, QFont::Normal);
|
||||
setMinimumSize(160, 130);
|
||||
resize(195, 160);
|
||||
} else {
|
||||
title_font = QFont(QString::fromUtf8("Segoe UI"), 9, QFont::Medium);
|
||||
value_font = QFont(QString::fromUtf8("Segoe UI"), 11, QFont::Bold);
|
||||
small_font = QFont(QString::fromUtf8("Segoe UI"), 8, QFont::Normal);
|
||||
setMinimumSize(220, 180);
|
||||
resize(220, 180);
|
||||
}
|
||||
|
||||
temperature_color = QColor(76, 175, 80, 255); // Default to green
|
||||
auto* layout = new QGridLayout(this);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
size_grip = new QSizeGrip(this);
|
||||
layout->addWidget(size_grip, 0, 0, Qt::AlignBottom | Qt::AlignRight);
|
||||
|
||||
// Graph colors
|
||||
temperature_color = QColor(76, 175, 80, 255);
|
||||
graph_background_color = QColor(40, 40, 40, 100);
|
||||
graph_line_color = QColor(76, 175, 80, 200);
|
||||
graph_fill_color = QColor(76, 175, 80, 60);
|
||||
|
||||
// Set up timer for updates
|
||||
update_timer.setSingleShot(false);
|
||||
connect(&update_timer, &QTimer::timeout, this, &PerformanceOverlay::UpdatePerformanceStats);
|
||||
|
||||
// Connect to the main window's theme change signal
|
||||
connect(parent, &GMainWindow::themeChanged, this, &PerformanceOverlay::UpdateTheme);
|
||||
// Set the initial theme colors
|
||||
if (main_window) {
|
||||
connect(main_window, &GMainWindow::themeChanged, this, &PerformanceOverlay::UpdateTheme);
|
||||
}
|
||||
|
||||
UpdateTheme();
|
||||
|
||||
// Set initial size - larger to accommodate the graph
|
||||
resize(220, 180);
|
||||
|
||||
// Position in top-left corner
|
||||
UpdatePosition();
|
||||
}
|
||||
|
||||
PerformanceOverlay::~PerformanceOverlay() = default;
|
||||
|
||||
void PerformanceOverlay::SetVisible(bool visible) {
|
||||
if (is_visible == visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
is_visible = visible;
|
||||
is_enabled = visible;
|
||||
is_visible = visible; // Update the state so the check works next time
|
||||
|
||||
if (visible) {
|
||||
show();
|
||||
update_timer.start(500); // Update every 500ms for more accurate data
|
||||
update_timer.start(500);
|
||||
} else {
|
||||
update_timer.stop(); // Stop the timer first
|
||||
hide();
|
||||
update_timer.stop();
|
||||
}
|
||||
}
|
||||
|
||||
void PerformanceOverlay::paintEvent(QPaintEvent* event) {
|
||||
Q_UNUSED(event)
|
||||
|
||||
QPainter painter(this);
|
||||
painter.setRenderHint(QPainter::Antialiasing, true);
|
||||
painter.setRenderHint(QPainter::TextAntialiasing, true);
|
||||
|
||||
// Draw background with rounded corners and subtle shadow effect
|
||||
QPainterPath background_path;
|
||||
background_path.addRoundedRect(rect(), corner_radius, corner_radius);
|
||||
|
||||
// Draw subtle shadow
|
||||
QPainterPath shadow_path = background_path.translated(1, 1);
|
||||
painter.fillPath(shadow_path, QColor(0, 0, 0, 40));
|
||||
if (!UISettings::IsGamescope()) {
|
||||
QPainterPath shadow_path = background_path.translated(1, 1);
|
||||
painter.fillPath(shadow_path, QColor(0, 0, 0, 40));
|
||||
}
|
||||
|
||||
// Draw main background
|
||||
painter.fillPath(background_path, background_color);
|
||||
|
||||
// Draw subtle border
|
||||
painter.setPen(QPen(border_color, border_width));
|
||||
painter.drawPath(background_path);
|
||||
|
||||
// Draw performance information
|
||||
DrawPerformanceInfo(painter);
|
||||
|
||||
// Draw frame graph
|
||||
DrawFrameGraph(painter);
|
||||
}
|
||||
|
||||
@@ -124,128 +132,110 @@ void PerformanceOverlay::resizeEvent(QResizeEvent* event) {
|
||||
UpdatePosition();
|
||||
}
|
||||
|
||||
void PerformanceOverlay::mousePressEvent(QMouseEvent* event) {
|
||||
if (event->button() == Qt::LeftButton && !size_grip->geometry().contains(event->pos())) {
|
||||
#if defined(Q_OS_LINUX)
|
||||
// LINUX-SPECIFIC IMPLEMENTATION (Wayland Fix)
|
||||
void PerformanceOverlay::mousePressEvent(QMouseEvent* event) {
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
// Hand off window moving responsibility to the OS compositor.
|
||||
if (windowHandle()) {
|
||||
if (!UISettings::IsGamescope() && windowHandle()) {
|
||||
windowHandle()->startSystemMove();
|
||||
} else {
|
||||
is_dragging = true;
|
||||
drag_start_pos = event->globalPosition().toPoint() - this->pos();
|
||||
}
|
||||
}
|
||||
QWidget::mousePressEvent(event);
|
||||
}
|
||||
|
||||
void PerformanceOverlay::mouseMoveEvent(QMouseEvent* event) {
|
||||
// This function is intentionally left blank for dragging, as the
|
||||
// system compositor now handles the entire move operation.
|
||||
QWidget::mouseMoveEvent(event);
|
||||
}
|
||||
|
||||
#else
|
||||
// ORIGINAL IMPLEMENTATION
|
||||
void PerformanceOverlay::mousePressEvent(QMouseEvent* event) {
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
is_dragging = true;
|
||||
drag_start_pos = event->globalPosition().toPoint();
|
||||
widget_start_pos = this->pos();
|
||||
setCursor(Qt::ClosedHandCursor);
|
||||
drag_start_pos = event->globalPosition().toPoint() - this->pos();
|
||||
#endif
|
||||
event->accept();
|
||||
}
|
||||
QWidget::mousePressEvent(event);
|
||||
}
|
||||
|
||||
void PerformanceOverlay::mouseMoveEvent(QMouseEvent* event) {
|
||||
if (is_dragging) {
|
||||
QPoint delta = event->globalPosition().toPoint() - drag_start_pos;
|
||||
move(widget_start_pos + delta);
|
||||
move(event->globalPosition().toPoint() - drag_start_pos);
|
||||
event->accept();
|
||||
}
|
||||
QWidget::mouseMoveEvent(event);
|
||||
}
|
||||
#endif
|
||||
|
||||
void PerformanceOverlay::mouseReleaseEvent(QMouseEvent* event) {
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
is_dragging = false;
|
||||
has_been_moved = true;
|
||||
setCursor(Qt::ArrowCursor);
|
||||
event->accept();
|
||||
}
|
||||
QWidget::mouseReleaseEvent(event);
|
||||
}
|
||||
|
||||
|
||||
void PerformanceOverlay::UpdatePerformanceStats() {
|
||||
if (!main_window) {
|
||||
return;
|
||||
if (!main_window || !is_enabled) return;
|
||||
|
||||
if (UISettings::IsGamescope()) {
|
||||
bool ui_active = (QApplication::activePopupWidget() != nullptr);
|
||||
|
||||
if (!ui_active) {
|
||||
for (QWidget* w : QApplication::topLevelWidgets()) {
|
||||
if (w->isVisible() && w != main_window && w != this &&
|
||||
!w->inherits("GRenderWindow") &&
|
||||
!w->inherits("VramOverlay") &&
|
||||
!w->inherits("ControllerOverlay") &&
|
||||
!w->inherits("PerformanceOverlay")) {
|
||||
ui_active = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ui_active) {
|
||||
if (!this->isHidden()) this->hide();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->isHidden()) {
|
||||
this->show();
|
||||
}
|
||||
} else {
|
||||
// Desktop: Only force a show if the user actually has it enabled in the menu
|
||||
if (is_enabled && this->isHidden()) {
|
||||
this->show();
|
||||
}
|
||||
}
|
||||
|
||||
// Get shader building info (this is safe to call)
|
||||
shaders_building = main_window->GetShadersBuilding();
|
||||
|
||||
// Use a static counter to only call the performance methods occasionally
|
||||
// This reduces the chance of conflicts with the status bar updates
|
||||
static int update_counter = 0;
|
||||
update_counter++;
|
||||
|
||||
// Try to get performance data every 2nd update (every 1 second)
|
||||
if (update_counter % 2 == 0) {
|
||||
try {
|
||||
current_fps = main_window->GetCurrentFPS();
|
||||
current_frame_time = main_window->GetCurrentFrameTime();
|
||||
emulation_speed = main_window->GetEmulationSpeed();
|
||||
|
||||
// Validate the values
|
||||
if (std::isnan(current_fps) || current_fps < 0.0 || current_fps > 1000.0) {
|
||||
current_fps = 60.0;
|
||||
}
|
||||
if (std::isnan(current_frame_time) || current_frame_time < 0.0 || current_frame_time > 100.0) {
|
||||
current_frame_time = 16.67;
|
||||
}
|
||||
if (std::isnan(emulation_speed) || emulation_speed < 0.0 || emulation_speed > 1000.0) {
|
||||
emulation_speed = 100.0;
|
||||
}
|
||||
if (std::isnan(current_fps) || current_fps < 0.0 || current_fps > 1000.0) current_fps = 60.0;
|
||||
if (std::isnan(current_frame_time) || current_frame_time < 0.0 || current_frame_time > 100.0) current_frame_time = 16.67;
|
||||
if (std::isnan(emulation_speed) || emulation_speed < 0.0 || emulation_speed > 1000.0) emulation_speed = 100.0;
|
||||
|
||||
// Ensure FPS and frame time are consistent
|
||||
if (current_fps > 0.0 && current_frame_time > 0.0) {
|
||||
// Recalculate frame time from FPS to ensure consistency
|
||||
current_frame_time = 1000.0 / current_fps;
|
||||
}
|
||||
} catch (...) {
|
||||
// If we get an exception, use the last known good values
|
||||
// Don't reset to defaults immediately
|
||||
}
|
||||
if (current_fps > 0.0) current_frame_time = 1000.0 / current_fps;
|
||||
} catch (...) {}
|
||||
}
|
||||
|
||||
// Update hardware temperatures every 4th update (every 2 seconds)
|
||||
if (update_counter % 4 == 0) {
|
||||
UpdateHardwareTemperatures();
|
||||
}
|
||||
|
||||
// If we don't have valid data yet, use defaults
|
||||
if (std::isnan(current_fps) || current_fps <= 0.0) {
|
||||
current_fps = 60.0;
|
||||
}
|
||||
if (std::isnan(current_frame_time) || current_frame_time <= 0.0) {
|
||||
current_frame_time = 16.67; // 60 FPS
|
||||
}
|
||||
if (std::isnan(emulation_speed) || emulation_speed <= 0.0) {
|
||||
emulation_speed = 100.0;
|
||||
}
|
||||
if (std::isnan(current_fps) || current_fps <= 0.0) current_fps = 60.0;
|
||||
if (std::isnan(current_frame_time) || current_frame_time <= 0.0) current_frame_time = 16.67;
|
||||
if (std::isnan(emulation_speed) || emulation_speed <= 0.0) emulation_speed = 100.0;
|
||||
|
||||
// Add frame time to graph history (only if it's valid)
|
||||
if (current_frame_time > 0.0) {
|
||||
AddFrameTime(current_frame_time);
|
||||
}
|
||||
if (current_frame_time > 0.0) AddFrameTime(current_frame_time);
|
||||
|
||||
// Update FPS and Temperature colors based on performance
|
||||
fps_color = GetFpsColor(current_fps);
|
||||
temperature_color = GetTemperatureColor(std::max({cpu_temperature, gpu_temperature, battery_temperature}));
|
||||
|
||||
// Trigger a repaint
|
||||
update();
|
||||
}
|
||||
|
||||
void PerformanceOverlay::UpdateHardwareTemperatures() {
|
||||
// Reset data
|
||||
cpu_temperature = 0.0f;
|
||||
gpu_temperature = 0.0f;
|
||||
cpu_sensor_type.clear();
|
||||
@@ -254,41 +244,79 @@ void PerformanceOverlay::UpdateHardwareTemperatures() {
|
||||
battery_temperature = 0.0f;
|
||||
|
||||
#if defined(Q_OS_LINUX)
|
||||
// --- Standard Linux Thermal Zone Reading ---
|
||||
QDir thermal_dir(QString::fromUtf8("/sys/class/thermal/"));
|
||||
QStringList filters{QString::fromUtf8("thermal_zone*")};
|
||||
QStringList thermal_zones = thermal_dir.entryList(filters, QDir::Dirs);
|
||||
// 1. Read Battery Data (Steam Deck / Laptops)
|
||||
QDir bat_dir(QStringLiteral("/sys/class/power_supply/"));
|
||||
QStringList bats = bat_dir.entryList({QStringLiteral("BAT*")}, QDir::Dirs);
|
||||
for (const QString& node : bats) {
|
||||
QFile cap_file(bat_dir.filePath(node + QStringLiteral("/capacity")));
|
||||
if (cap_file.open(QIODevice::ReadOnly)) {
|
||||
battery_percentage = cap_file.readAll().trimmed().toInt();
|
||||
cap_file.close();
|
||||
|
||||
for (const QString& zone_name : thermal_zones) {
|
||||
QFile type_file(thermal_dir.filePath(zone_name + QString::fromUtf8("/type")));
|
||||
if (!type_file.open(QIODevice::ReadOnly | QIODevice::Text)) continue;
|
||||
QString type = QString::fromUtf8(type_file.readAll()).trimmed();
|
||||
type_file.close();
|
||||
|
||||
QFile temp_file(thermal_dir.filePath(zone_name + QString::fromUtf8("/temp")));
|
||||
if (!temp_file.open(QIODevice::ReadOnly | QIODevice::Text)) continue;
|
||||
float temp = temp_file.readAll().trimmed().toFloat() / 1000.0f;
|
||||
temp_file.close();
|
||||
|
||||
if (type.contains(QString::fromUtf8("x86_pkg_temp")) || type.contains(QString::fromUtf8("cpu"))) {
|
||||
if (temp > cpu_temperature) {
|
||||
cpu_temperature = temp;
|
||||
cpu_sensor_type = QString::fromUtf8("CPU");
|
||||
QFile btemp_file(bat_dir.filePath(node + QStringLiteral("/temp")));
|
||||
if (btemp_file.open(QIODevice::ReadOnly)) {
|
||||
float raw_temp = btemp_file.readAll().trimmed().toFloat();
|
||||
// Detect millidegrees (35000) or tenths (350)
|
||||
battery_temperature = (raw_temp > 1000) ? raw_temp / 1000.0f : raw_temp / 10.0f;
|
||||
btemp_file.close();
|
||||
}
|
||||
} else if (type.contains(QString::fromUtf8("radeon")) || type.contains(QString::fromUtf8("amdgpu")) || type.contains(QString::fromUtf8("nvidia")) || type.contains(QString::fromUtf8("nouveau"))) {
|
||||
if (temp > gpu_temperature) {
|
||||
gpu_temperature = temp;
|
||||
gpu_sensor_type = QString::fromUtf8("GPU");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Read APU/CPU Temperatures
|
||||
QDir hwmon_dir(QStringLiteral("/sys/class/hwmon/"));
|
||||
QStringList hwmons = hwmon_dir.entryList({QStringLiteral("hwmon*")}, QDir::Dirs);
|
||||
for (const QString& h_node : hwmons) {
|
||||
QFile name_file(hwmon_dir.filePath(h_node + QStringLiteral("/name")));
|
||||
if (!name_file.open(QIODevice::ReadOnly)) continue;
|
||||
QString hw_name = QString::fromUtf8(name_file.readAll().trimmed());
|
||||
name_file.close();
|
||||
|
||||
// GPU Portion (Standard Steam Deck & Desktop AMD)
|
||||
if (hw_name == QStringLiteral("amdgpu")) {
|
||||
QFile t_file(hwmon_dir.filePath(h_node + QStringLiteral("/temp1_input")));
|
||||
if (t_file.open(QIODevice::ReadOnly)) {
|
||||
gpu_temperature = t_file.readAll().trimmed().toFloat() / 1000.0f;
|
||||
gpu_sensor_type = QStringLiteral("GPU");
|
||||
t_file.close();
|
||||
}
|
||||
}
|
||||
// CPU Portion (k10temp = AMD Deck/Desktop, coretemp = Intel Desktop)
|
||||
else if (hw_name == QStringLiteral("k10temp") || hw_name == QStringLiteral("coretemp") || hw_name == QStringLiteral("zenpower")) {
|
||||
// Check for temp1_input (AMD) or temp2_input (Intel coretemp usually starts at 2 for package)
|
||||
QStringList input_candidates = {QStringLiteral("temp1_input"), QStringLiteral("temp2_input")};
|
||||
for (const auto& input : input_candidates) {
|
||||
QFile t_file(hwmon_dir.filePath(h_node + QStringLiteral("/") + input));
|
||||
if (t_file.open(QIODevice::ReadOnly)) {
|
||||
cpu_temperature = t_file.readAll().trimmed().toFloat() / 1000.0f;
|
||||
cpu_sensor_type = QStringLiteral("CPU");
|
||||
t_file.close();
|
||||
if (cpu_temperature > 0) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Fallback to generic thermal_zones
|
||||
if (cpu_temperature <= 0.0f) {
|
||||
QDir thermal_dir(QStringLiteral("/sys/class/thermal/"));
|
||||
QStringList thermal_zones = thermal_dir.entryList({QStringLiteral("thermal_zone*")}, QDir::Dirs);
|
||||
for (const QString& zone_name : thermal_zones) {
|
||||
QFile temp_file(thermal_dir.filePath(zone_name + QStringLiteral("/temp")));
|
||||
if (temp_file.open(QIODevice::ReadOnly)) {
|
||||
cpu_temperature = temp_file.readAll().trimmed().toFloat() / 1000.0f;
|
||||
cpu_sensor_type = QStringLiteral("CPU");
|
||||
temp_file.close();
|
||||
if (cpu_temperature > 0) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_ANDROID)
|
||||
// This uses QtAndroid Extras to get battery info from the Android system.
|
||||
// NOTE: This requires the QtAndroidExtras module to be linked in the build.
|
||||
QJniObject battery_status = QJniObject::callStaticObjectMethod(
|
||||
"android/content/CONTEXT", "registerReceiver",
|
||||
"android/content/Context", "registerReceiver",
|
||||
"(Landroid/content/BroadcastReceiver;Landroid/content/IntentFilter;)Landroid/content/Intent;",
|
||||
nullptr, new QJniObject("android.content.IntentFilter", "(Ljava/lang/String;)V", "android.intent.action.BATTERY_CHANGED"));
|
||||
|
||||
@@ -300,12 +328,8 @@ void PerformanceOverlay::UpdateHardwareTemperatures() {
|
||||
int temp_tenths = battery_status.callMethod<jint>("getIntExtra", "(Ljava/lang/String;I)I",
|
||||
QJniObject::fromString("temperature").object<jstring>(), -1);
|
||||
|
||||
if (scale > 0) {
|
||||
battery_percentage = (level * 100) / scale;
|
||||
}
|
||||
if (temp_tenths > 0) {
|
||||
battery_temperature = static_cast<float>(temp_tenths) / 10.0f;
|
||||
}
|
||||
if (scale > 0) battery_percentage = (level * 100) / scale;
|
||||
if (temp_tenths > 0) battery_temperature = static_cast<float>(temp_tenths) / 10.0f;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -313,9 +337,7 @@ void PerformanceOverlay::UpdateHardwareTemperatures() {
|
||||
HRESULT hres;
|
||||
IWbemLocator* pLoc = nullptr;
|
||||
IWbemServices* pSvc = nullptr;
|
||||
|
||||
hres = CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID*)&pLoc);
|
||||
|
||||
if (SUCCEEDED(hres)) {
|
||||
hres = pLoc->ConnectServer(_bstr_t(L"ROOT\\WMI"), NULL, NULL, 0, NULL, 0, 0, &pSvc);
|
||||
if (SUCCEEDED(hres)) {
|
||||
@@ -331,12 +353,11 @@ void PerformanceOverlay::UpdateHardwareTemperatures() {
|
||||
while (pEnumerator) {
|
||||
pEnumerator->Next(WBEM_INFINITE, 1, &pclsObj, &uReturn);
|
||||
if (uReturn == 0) break;
|
||||
|
||||
VARIANT vtProp;
|
||||
pclsObj->Get(L"CurrentTemperature", 0, &vtProp, 0, 0);
|
||||
float temp_kelvin = vtProp.uintVal / 10.0f;
|
||||
cpu_temperature = temp_kelvin - 273.15f;
|
||||
cpu_sensor_type = QString::fromUtf8("CPU");
|
||||
cpu_sensor_type = QStringLiteral("CPU");
|
||||
VariantClear(&vtProp);
|
||||
pclsObj->Release();
|
||||
}
|
||||
@@ -351,12 +372,7 @@ void PerformanceOverlay::UpdateHardwareTemperatures() {
|
||||
}
|
||||
|
||||
void PerformanceOverlay::UpdatePosition() {
|
||||
if (!main_window) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only position in top-left corner if we haven't been moved by the user
|
||||
if (!has_been_moved) {
|
||||
if (main_window && !has_been_moved) {
|
||||
QPoint main_window_pos = main_window->mapToGlobal(QPoint(0, 0));
|
||||
move(main_window_pos.x() + 10, main_window_pos.y() + 10);
|
||||
}
|
||||
@@ -365,92 +381,85 @@ void PerformanceOverlay::UpdatePosition() {
|
||||
void PerformanceOverlay::DrawPerformanceInfo(QPainter& painter) {
|
||||
painter.setRenderHint(QPainter::TextAntialiasing, true);
|
||||
|
||||
int y_offset = padding;
|
||||
const int line_height = 20;
|
||||
// Dynamic spacing based on font size to prevent squishing
|
||||
const int title_step = painter.fontMetrics().height() + 2;
|
||||
const int stat_step = painter.fontMetrics().height() + 2;
|
||||
|
||||
// Draw title
|
||||
int y_left = (padding / 2) + painter.fontMetrics().ascent();
|
||||
int y_right = y_left + 10;
|
||||
|
||||
// 1. Draw Title (Left)
|
||||
painter.setFont(title_font);
|
||||
painter.setPen(text_color);
|
||||
painter.drawText(padding, y_offset + 12, QString::fromUtf8("CITRON"));
|
||||
painter.drawText(padding, y_left, QStringLiteral("CITRON PERFORMANCE"));
|
||||
|
||||
int y_offset_right = padding;
|
||||
const int line_height_right = 18;
|
||||
|
||||
// Draw Temperatures
|
||||
// 2. Draw Hardware Stats (Right Column)
|
||||
painter.setFont(small_font);
|
||||
const int hw_step = UISettings::IsGamescope() ? 16 : 20;
|
||||
|
||||
float core_temp_to_display = std::max(cpu_temperature, gpu_temperature);
|
||||
if (core_temp_to_display > 0.0f) {
|
||||
QString core_label = gpu_temperature > cpu_temperature ? gpu_sensor_type : cpu_sensor_type;
|
||||
QString core_temp_text = QString::fromUtf8("%1: %2°C").arg(core_label).arg(core_temp_to_display, 0, 'f', 0);
|
||||
painter.setPen(GetTemperatureColor(core_temp_to_display));
|
||||
int text_width = painter.fontMetrics().horizontalAdvance(core_temp_text);
|
||||
painter.drawText(width() - padding - text_width, y_offset_right + 12, core_temp_text);
|
||||
if (cpu_temperature > 0.0f) {
|
||||
QString cpu_text = QStringLiteral("CPU:%1°C").arg(cpu_temperature, 0, 'f', 0);
|
||||
painter.setPen(GetTemperatureColor(cpu_temperature));
|
||||
int tw = painter.fontMetrics().horizontalAdvance(cpu_text);
|
||||
painter.drawText(width() - padding - tw, y_right, cpu_text);
|
||||
y_right += hw_step;
|
||||
}
|
||||
|
||||
if (gpu_temperature > 0.0f) {
|
||||
QString gpu_text = QStringLiteral("GPU:%1°C").arg(gpu_temperature, 0, 'f', 0);
|
||||
painter.setPen(GetTemperatureColor(gpu_temperature));
|
||||
int tw = painter.fontMetrics().horizontalAdvance(gpu_text);
|
||||
painter.drawText(width() - padding - tw, y_right, gpu_text);
|
||||
y_right += hw_step;
|
||||
}
|
||||
y_offset_right += line_height_right;
|
||||
|
||||
// Draw Battery info
|
||||
if (battery_percentage > 0) {
|
||||
QString batt_text = QString::fromUtf8("Batt: %1%").arg(battery_percentage);
|
||||
QString batt_text = QStringLiteral("Battery %:%1%").arg(battery_percentage);
|
||||
if (battery_temperature > 0.0f) {
|
||||
batt_text += QString::fromUtf8(" (%1°C)").arg(battery_temperature, 0, 'f', 0);
|
||||
batt_text += QStringLiteral(" (%1°C)").arg(battery_temperature, 0, 'f', 0);
|
||||
}
|
||||
painter.setPen(text_color);
|
||||
int text_width = painter.fontMetrics().horizontalAdvance(batt_text);
|
||||
painter.drawText(width() - padding - text_width, y_offset_right + 12, batt_text);
|
||||
int tw = painter.fontMetrics().horizontalAdvance(batt_text);
|
||||
painter.drawText(width() - padding - tw, y_right, batt_text);
|
||||
}
|
||||
|
||||
y_offset += line_height + 4;
|
||||
|
||||
// Draw FPS
|
||||
// 3. Draw FPS (Left Column)
|
||||
y_left += title_step;
|
||||
painter.setFont(value_font);
|
||||
painter.setPen(fps_color);
|
||||
QString fps_text = QString::fromUtf8("%1 FPS").arg(FormatFps(current_fps));
|
||||
painter.drawText(padding, y_offset, fps_text);
|
||||
y_offset += line_height;
|
||||
painter.drawText(padding, y_left, QStringLiteral("%1 FPS").arg(FormatFps(current_fps)));
|
||||
|
||||
// Draw frame time
|
||||
// 4. Draw Small Stats (Left Column)
|
||||
y_left += title_step;
|
||||
painter.setFont(small_font);
|
||||
painter.setPen(text_color);
|
||||
QString frame_time_text = QString::fromUtf8("Frame: %1 ms").arg(FormatFrameTime(current_frame_time));
|
||||
painter.drawText(padding, y_offset, frame_time_text);
|
||||
y_offset += line_height - 2;
|
||||
painter.drawText(padding, y_left, QStringLiteral("Frame:%1 ms").arg(FormatFrameTime(current_frame_time)));
|
||||
|
||||
// Draw emulation speed
|
||||
QString speed_text = QString::fromUtf8("Speed: %1%").arg(emulation_speed, 0, 'f', 0);
|
||||
painter.drawText(padding, y_offset, speed_text);
|
||||
y_offset += line_height - 2;
|
||||
y_left += stat_step;
|
||||
painter.drawText(padding, y_left, QStringLiteral("Speed:%1%").arg(emulation_speed, 0, 'f', 0));
|
||||
|
||||
// Draw shader building info with accent color
|
||||
if (shaders_building > 0) {
|
||||
painter.setPen(QColor(255, 152, 0, 255)); // Material Design orange
|
||||
QString shader_text = QString::fromUtf8("Building: %1 shader(s)").arg(shaders_building);
|
||||
painter.drawText(padding, y_offset, shader_text);
|
||||
y_left += stat_step;
|
||||
painter.setPen(QColor(255, 152, 0));
|
||||
painter.drawText(padding, y_left, QStringLiteral("Building:%1").arg(shaders_building));
|
||||
}
|
||||
}
|
||||
|
||||
void PerformanceOverlay::DrawFrameGraph(QPainter& painter) {
|
||||
if (frame_times.empty()) {
|
||||
return;
|
||||
}
|
||||
if (frame_times.empty()) return;
|
||||
|
||||
const int graph_y = height() - graph_height - padding;
|
||||
const int graph_width = width() - (padding * 2);
|
||||
const QRect graph_rect(padding, graph_y, graph_width, graph_height);
|
||||
|
||||
// Draw graph background
|
||||
painter.fillRect(graph_rect, graph_background_color);
|
||||
|
||||
// Calculate graph bounds
|
||||
const double min_val = std::max(0.0, min_frame_time - 1.0);
|
||||
const double max_val = std::max(16.67, max_frame_time + 1.0); // 16.67ms = 60 FPS
|
||||
const double max_val = std::max(16.67, max_frame_time + 1.0);
|
||||
const double range = max_val - min_val;
|
||||
if (range <= 0.0) return;
|
||||
|
||||
if (range <= 0.0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Draw grid lines
|
||||
// Grid lines
|
||||
painter.setPen(QPen(QColor(80, 80, 80, 100), 1));
|
||||
const int grid_lines = 4;
|
||||
for (int i = 1; i < grid_lines; ++i) {
|
||||
@@ -458,12 +467,11 @@ void PerformanceOverlay::DrawFrameGraph(QPainter& painter) {
|
||||
painter.drawLine(graph_rect.left(), y, graph_rect.right(), y);
|
||||
}
|
||||
|
||||
// Draw 60 FPS line (16.67ms)
|
||||
// 60 FPS Target line
|
||||
const int fps60_y = graph_y + graph_height - static_cast<int>((16.67 - min_val) / range * graph_height);
|
||||
painter.setPen(QPen(QColor(255, 255, 255, 80), 1, Qt::DashLine));
|
||||
painter.drawLine(graph_rect.left(), fps60_y, graph_rect.right(), fps60_y);
|
||||
|
||||
// Draw frame time line
|
||||
painter.setPen(QPen(graph_line_color, 2));
|
||||
painter.setBrush(graph_fill_color);
|
||||
|
||||
@@ -476,45 +484,39 @@ void PerformanceOverlay::DrawFrameGraph(QPainter& painter) {
|
||||
const double normalized_y = (frame_time - min_val) / range;
|
||||
const int x = graph_rect.left() + static_cast<int>(i * x_step);
|
||||
const int y = graph_y + graph_height - static_cast<int>(normalized_y * graph_height);
|
||||
|
||||
if (i == 0) {
|
||||
graph_path.moveTo(x, y);
|
||||
} else {
|
||||
graph_path.lineTo(x, y);
|
||||
}
|
||||
if (i == 0) graph_path.moveTo(x, y); else graph_path.lineTo(x, y);
|
||||
}
|
||||
|
||||
// Close the path for filling
|
||||
graph_path.lineTo(graph_rect.right(), graph_rect.bottom());
|
||||
graph_path.lineTo(graph_rect.left(), graph_rect.bottom());
|
||||
graph_path.closeSubpath();
|
||||
|
||||
painter.drawPath(graph_path);
|
||||
|
||||
// Draw statistics text
|
||||
painter.setFont(small_font);
|
||||
painter.setPen(text_color);
|
||||
|
||||
const QString min_text = QString::fromUtf8("Min: %1ms").arg(FormatFrameTime(min_frame_time));
|
||||
const QString avg_text = QString::fromUtf8("Avg: %1ms").arg(FormatFrameTime(avg_frame_time));
|
||||
const QString max_text = QString::fromUtf8("Max: %1ms").arg(FormatFrameTime(max_frame_time));
|
||||
const QString min_str = QStringLiteral("Min:%1ms").arg(FormatFrameTime(min_frame_time));
|
||||
const QString avg_str = QStringLiteral("Avg:%2ms").arg(FormatFrameTime(avg_frame_time));
|
||||
const QString max_str = QStringLiteral("Max:%1ms").arg(FormatFrameTime(max_frame_time));
|
||||
|
||||
painter.drawText(graph_rect.left(), graph_y - 5, min_text);
|
||||
painter.drawText(graph_rect.center().x() - painter.fontMetrics().horizontalAdvance(avg_text) / 2,
|
||||
graph_y - 5, avg_text);
|
||||
painter.drawText(graph_rect.right() - painter.fontMetrics().horizontalAdvance(max_text),
|
||||
graph_y - 5, max_text);
|
||||
// Combine into one line for measurement
|
||||
const QString full_line = QStringLiteral("%1 %2 %3").arg(min_str, avg_str, max_str);
|
||||
int total_width = painter.fontMetrics().horizontalAdvance(full_line);
|
||||
|
||||
// If there is enough room, flatten it across the top. Otherwise, stack it.
|
||||
if (total_width < graph_width - 10) {
|
||||
// Flat layout
|
||||
painter.drawText(graph_rect.left(), graph_y - 6, full_line);
|
||||
} else {
|
||||
// Stacked layout (Fallback for small windows/High-DPI scaling)
|
||||
painter.drawText(graph_rect.left(), graph_y - 18, QStringLiteral("%1 %2").arg(min_str, avg_str));
|
||||
painter.drawText(graph_rect.left(), graph_y - 4, max_str);
|
||||
}
|
||||
}
|
||||
|
||||
void PerformanceOverlay::AddFrameTime(double frame_time_ms) {
|
||||
frame_times.push_back(frame_time_ms);
|
||||
|
||||
// Keep only the last MAX_FRAME_HISTORY frames
|
||||
if (frame_times.size() > MAX_FRAME_HISTORY) {
|
||||
frame_times.pop_front();
|
||||
}
|
||||
|
||||
// Update statistics
|
||||
if (frame_times.size() > MAX_FRAME_HISTORY) frame_times.pop_front();
|
||||
if (!frame_times.empty()) {
|
||||
min_frame_time = *std::min_element(frame_times.begin(), frame_times.end());
|
||||
max_frame_time = *std::max_element(frame_times.begin(), frame_times.end());
|
||||
@@ -523,54 +525,39 @@ void PerformanceOverlay::AddFrameTime(double frame_time_ms) {
|
||||
}
|
||||
|
||||
QColor PerformanceOverlay::GetFpsColor(double fps) const {
|
||||
if (fps >= 55.0) {
|
||||
return QColor(76, 175, 80, 255); // Material Design green - Good performance
|
||||
} else if (fps >= 45.0) {
|
||||
return QColor(255, 152, 0, 255); // Material Design orange - Moderate performance
|
||||
} else if (fps >= 30.0) {
|
||||
return QColor(255, 87, 34, 255); // Material Design deep orange - Poor performance
|
||||
} else {
|
||||
return QColor(244, 67, 54, 255); // Material Design red - Very poor performance
|
||||
}
|
||||
if (fps >= 55.0) return QColor(76, 175, 80, 255);
|
||||
if (fps >= 45.0) return QColor(255, 152, 0, 255);
|
||||
if (fps >= 30.0) return QColor(255, 87, 34, 255);
|
||||
return QColor(244, 67, 54, 255);
|
||||
}
|
||||
|
||||
QColor PerformanceOverlay::GetTemperatureColor(float temperature) const {
|
||||
if (temperature > 70.0f) {
|
||||
return QColor(244, 67, 54, 255); // Material Design red
|
||||
} else if (temperature > 60.0f) {
|
||||
return QColor(255, 152, 0, 255); // Material Design orange
|
||||
} else {
|
||||
return QColor(76, 175, 80, 255); // Material Design green
|
||||
}
|
||||
if (temperature > 85.0f) return QColor(244, 67, 54, 255);
|
||||
if (temperature > 75.0f) return QColor(255, 152, 0, 255);
|
||||
return QColor(76, 175, 80, 255);
|
||||
}
|
||||
|
||||
QString PerformanceOverlay::FormatFps(double fps) const {
|
||||
if (std::isnan(fps) || fps < 0.0) {
|
||||
return QString::fromUtf8("0.0");
|
||||
}
|
||||
if (std::isnan(fps) || fps < 0.0) return QString::fromUtf8("0.0");
|
||||
return QString::number(fps, 'f', 1);
|
||||
}
|
||||
|
||||
QString PerformanceOverlay::FormatFrameTime(double frame_time_ms) const {
|
||||
if (std::isnan(frame_time_ms) || frame_time_ms < 0.0) {
|
||||
return QString::fromUtf8("0.00");
|
||||
}
|
||||
if (std::isnan(frame_time_ms) || frame_time_ms < 0.0) return QString::fromUtf8("0.00");
|
||||
return QString::number(frame_time_ms, 'f', 2);
|
||||
}
|
||||
|
||||
void PerformanceOverlay::UpdateTheme() {
|
||||
if (UISettings::IsDarkTheme()) {
|
||||
// Dark Theme Colors (your original values)
|
||||
background_color = QColor(20, 20, 20, 200); // Slightly more opaque
|
||||
background_color = QColor(20, 20, 20, 200);
|
||||
border_color = QColor(60, 60, 60, 120);
|
||||
text_color = QColor(220, 220, 220, 255);
|
||||
graph_background_color = QColor(40, 40, 40, 100);
|
||||
} else {
|
||||
// Light Theme Colors
|
||||
background_color = QColor(245, 245, 245, 220);
|
||||
border_color = QColor(200, 200, 200, 120);
|
||||
text_color = QColor(20, 20, 20, 255);
|
||||
graph_background_color = QColor(220, 220, 220, 100);
|
||||
}
|
||||
update(); // Force a repaint with the new colors
|
||||
update();
|
||||
}
|
||||
|
||||
@@ -15,16 +15,18 @@
|
||||
#include "citron/uisettings.h"
|
||||
|
||||
class GMainWindow;
|
||||
class QSizeGrip;
|
||||
|
||||
class PerformanceOverlay : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit PerformanceOverlay(GMainWindow* parent);
|
||||
explicit PerformanceOverlay(QWidget* parent);
|
||||
~PerformanceOverlay() override;
|
||||
|
||||
void SetVisible(bool visible);
|
||||
bool IsVisible() const { return is_visible; }
|
||||
void setMainWindow(GMainWindow* window) { main_window = window; }
|
||||
|
||||
public slots:
|
||||
void UpdateTheme();
|
||||
@@ -40,6 +42,9 @@ private slots:
|
||||
void UpdatePerformanceStats();
|
||||
|
||||
private:
|
||||
bool is_enabled = false;
|
||||
bool is_visible = false;
|
||||
|
||||
void UpdatePosition();
|
||||
void UpdateHardwareTemperatures();
|
||||
void DrawPerformanceInfo(QPainter& painter);
|
||||
@@ -51,6 +56,7 @@ private:
|
||||
void AddFrameTime(double frame_time_ms);
|
||||
|
||||
GMainWindow* main_window;
|
||||
QSizeGrip* size_grip;
|
||||
QTimer update_timer;
|
||||
|
||||
// Performance data
|
||||
@@ -66,14 +72,13 @@ private:
|
||||
float battery_temperature = 0.0f;
|
||||
|
||||
// Frame graph data
|
||||
static constexpr size_t MAX_FRAME_HISTORY = 120; // 2 seconds at 60 FPS
|
||||
static constexpr size_t MAX_FRAME_HISTORY = 120;
|
||||
std::deque<double> frame_times;
|
||||
double min_frame_time = 0.0;
|
||||
double max_frame_time = 0.0;
|
||||
double avg_frame_time = 0.0;
|
||||
|
||||
// Display settings
|
||||
bool is_visible = false;
|
||||
QFont title_font;
|
||||
QFont value_font;
|
||||
QFont small_font;
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#include <QPainter>
|
||||
#include <QPainterPath>
|
||||
#include <QScreen>
|
||||
#include <QSizeGrip>
|
||||
#include <QGridLayout>
|
||||
#include <QTimer>
|
||||
#include <QMouseEvent>
|
||||
#include <QtMath>
|
||||
@@ -25,58 +27,72 @@
|
||||
#include "video_core/renderer_vulkan/vk_rasterizer.h"
|
||||
#include "common/settings.h"
|
||||
|
||||
VramOverlay::VramOverlay(GMainWindow* parent)
|
||||
: QWidget(parent), main_window(parent) {
|
||||
VramOverlay::VramOverlay(QWidget* parent) : QWidget(UISettings::IsGamescope() ? nullptr : parent) {
|
||||
if (parent) {
|
||||
main_window = qobject_cast<GMainWindow*>(parent);
|
||||
}
|
||||
|
||||
if (UISettings::IsGamescope()) {
|
||||
setWindowFlags(Qt::ToolTip | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::WindowDoesNotAcceptFocus);
|
||||
setAttribute(Qt::WA_ShowWithoutActivating);
|
||||
} else {
|
||||
setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);
|
||||
}
|
||||
|
||||
// Set up the widget properties
|
||||
setAttribute(Qt::WA_TranslucentBackground, true);
|
||||
setWindowFlags(Qt::FramelessWindowHint | Qt::Tool | Qt::WindowStaysOnTopHint);
|
||||
setAttribute(Qt::WA_NoSystemBackground);
|
||||
setAttribute(Qt::WA_WState_ExplicitShowHide);
|
||||
|
||||
// Initialize fonts with clean typography
|
||||
title_font = QFont(QString::fromUtf8("Segoe UI"), 11, QFont::Bold);
|
||||
value_font = QFont(QString::fromUtf8("Segoe UI"), 10, QFont::Medium);
|
||||
small_font = QFont(QString::fromUtf8("Segoe UI"), 9, QFont::Normal);
|
||||
warning_font = QFont(QString::fromUtf8("Segoe UI"), 10, QFont::Bold);
|
||||
// Branching Typography and Sizing
|
||||
if (UISettings::IsGamescope()) {
|
||||
title_font = QFont(QString::fromUtf8("Segoe UI"), 7, QFont::Bold);
|
||||
value_font = QFont(QString::fromUtf8("Segoe UI"), 7, QFont::Medium);
|
||||
small_font = QFont(QString::fromUtf8("Segoe UI"), 6, QFont::Normal);
|
||||
warning_font = QFont(QString::fromUtf8("Segoe UI"), 8, QFont::Bold);
|
||||
setMinimumSize(180, 140);
|
||||
resize(200, 160);
|
||||
} else {
|
||||
title_font = QFont(QString::fromUtf8("Segoe UI"), 11, QFont::Bold);
|
||||
value_font = QFont(QString::fromUtf8("Segoe UI"), 10, QFont::Medium);
|
||||
small_font = QFont(QString::fromUtf8("Segoe UI"), 9, QFont::Normal);
|
||||
warning_font = QFont(QString::fromUtf8("Segoe UI"), 10, QFont::Bold);
|
||||
setMinimumSize(250, 180);
|
||||
resize(250, 180);
|
||||
}
|
||||
|
||||
// VRAM usage colors - modern palette
|
||||
vram_safe_color = QColor(76, 175, 80, 255);
|
||||
vram_warning_color = QColor(255, 193, 7, 255);
|
||||
vram_danger_color = QColor(244, 67, 54, 255);
|
||||
leak_warning_color = QColor(255, 152, 0, 255);
|
||||
|
||||
// Graph colors - clean and modern
|
||||
graph_background_color = QColor(25, 25, 25, 255);
|
||||
graph_grid_color = QColor(60, 60, 60, 100);
|
||||
graph_line_color = QColor(76, 175, 80, 255);
|
||||
graph_fill_color = QColor(76, 175, 80, 40);
|
||||
auto* layout = new QGridLayout(this);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
size_grip = new QSizeGrip(this);
|
||||
layout->addWidget(size_grip, 0, 0, Qt::AlignBottom | Qt::AlignRight);
|
||||
|
||||
// Set up timer for updates
|
||||
update_timer.setSingleShot(false);
|
||||
connect(&update_timer, &QTimer::timeout, this, &VramOverlay::UpdateVramStats);
|
||||
|
||||
connect(parent, &GMainWindow::themeChanged, this, &VramOverlay::UpdateTheme);
|
||||
if (main_window) {
|
||||
connect(main_window, &GMainWindow::themeChanged, this, &VramOverlay::UpdateTheme);
|
||||
}
|
||||
|
||||
UpdateTheme();
|
||||
|
||||
// Set clean, compact size
|
||||
resize(250, 180);
|
||||
|
||||
// Position in top-right corner
|
||||
UpdatePosition();
|
||||
}
|
||||
|
||||
VramOverlay::~VramOverlay() = default;
|
||||
|
||||
void VramOverlay::SetVisible(bool visible) {
|
||||
if (is_visible == visible) {
|
||||
return;
|
||||
}
|
||||
is_visible = visible;
|
||||
is_enabled = visible;
|
||||
is_visible = visible; // Properly sync the internal state
|
||||
|
||||
if (visible) {
|
||||
show();
|
||||
update_timer.start(1000); // Update every 1 second
|
||||
update_timer.start(1000);
|
||||
} else {
|
||||
update_timer.stop(); // Ensure the background loop stops updating
|
||||
hide();
|
||||
update_timer.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,9 +125,9 @@ void VramOverlay::paintEvent(QPaintEvent* event) {
|
||||
}
|
||||
|
||||
void VramOverlay::DrawVramInfo(QPainter& painter) {
|
||||
const int section_padding = 12;
|
||||
const int line_height = 14;
|
||||
const int section_spacing = 6;
|
||||
const int section_padding = UISettings::IsGamescope() ? 5 : 12;
|
||||
const int line_height = UISettings::IsGamescope() ? 11 : 14;
|
||||
const int section_spacing = UISettings::IsGamescope() ? 2 : 6;
|
||||
int y_offset = section_padding + 4;
|
||||
|
||||
painter.setFont(title_font);
|
||||
@@ -123,22 +139,19 @@ void VramOverlay::DrawVramInfo(QPainter& painter) {
|
||||
QColor vram_color = GetVramColor(current_vram_data.vram_percentage);
|
||||
painter.setPen(vram_color);
|
||||
QString vram_text = QString::fromUtf8("%1 / %2 (%3%)")
|
||||
.arg(FormatMemorySize(current_vram_data.used_vram))
|
||||
.arg(FormatMemorySize(current_vram_data.total_vram))
|
||||
.arg(FormatPercentage(current_vram_data.vram_percentage));
|
||||
.arg(FormatMemorySize(current_vram_data.used_vram))
|
||||
.arg(FormatMemorySize(current_vram_data.total_vram))
|
||||
.arg(FormatPercentage(current_vram_data.vram_percentage));
|
||||
painter.drawText(section_padding, y_offset, vram_text);
|
||||
y_offset += line_height + section_spacing;
|
||||
|
||||
painter.setFont(small_font);
|
||||
painter.setPen(secondary_text_color);
|
||||
QString buffer_text = QString::fromUtf8("Buffers: %1").arg(FormatMemorySize(current_vram_data.buffer_memory));
|
||||
painter.drawText(section_padding, y_offset, buffer_text);
|
||||
y_offset += line_height - 1;
|
||||
QString texture_text = QString::fromUtf8("Textures: %1").arg(FormatMemorySize(current_vram_data.texture_memory));
|
||||
painter.drawText(section_padding, y_offset, texture_text);
|
||||
y_offset += line_height - 1;
|
||||
QString staging_text = QString::fromUtf8("Staging: %1").arg(FormatMemorySize(current_vram_data.staging_memory));
|
||||
painter.drawText(section_padding, y_offset, staging_text);
|
||||
painter.drawText(section_padding, y_offset, QString::fromUtf8("Buffers: %1").arg(FormatMemorySize(current_vram_data.buffer_memory)));
|
||||
y_offset += line_height - (UISettings::IsGamescope() ? 0 : 1);
|
||||
painter.drawText(section_padding, y_offset, QString::fromUtf8("Textures: %1").arg(FormatMemorySize(current_vram_data.texture_memory)));
|
||||
y_offset += line_height - (UISettings::IsGamescope() ? 0 : 1);
|
||||
painter.drawText(section_padding, y_offset, QString::fromUtf8("Staging: %1").arg(FormatMemorySize(current_vram_data.staging_memory)));
|
||||
y_offset += line_height + section_spacing;
|
||||
|
||||
painter.setPen(secondary_text_color);
|
||||
@@ -157,9 +170,9 @@ void VramOverlay::DrawVramGraph(QPainter& painter) {
|
||||
if (vram_usage_history.empty()) return;
|
||||
|
||||
const int graph_padding = 12;
|
||||
const int graph_y = height() - 60;
|
||||
const int graph_y = height() - (UISettings::IsGamescope() ? 50 : 60);
|
||||
const int graph_width = width() - (graph_padding * 2);
|
||||
const int local_graph_height = 40;
|
||||
const int local_graph_height = UISettings::IsGamescope() ? 30 : 40;
|
||||
|
||||
QRect graph_rect(graph_padding, graph_y, graph_width, local_graph_height);
|
||||
QPainterPath graph_path;
|
||||
@@ -169,13 +182,10 @@ void VramOverlay::DrawVramGraph(QPainter& painter) {
|
||||
painter.setPen(QPen(graph_grid_color, 1));
|
||||
painter.drawPath(graph_path);
|
||||
|
||||
for (int i = 1; i < 4; ++i) {
|
||||
int y = graph_y + (i * local_graph_height / 4);
|
||||
painter.drawLine(graph_padding + 1, y, graph_padding + graph_width - 1, y);
|
||||
}
|
||||
|
||||
if (vram_usage_history.size() > 1) {
|
||||
painter.setPen(QPen(graph_line_color, 2));
|
||||
QColor dynamic_color = current_vram_data.leak_detected ? leak_warning_color : GetVramColor(current_vram_data.vram_percentage);
|
||||
|
||||
painter.setPen(QPen(dynamic_color, 2));
|
||||
QPainterPath line_path;
|
||||
for (size_t i = 0; i < vram_usage_history.size(); ++i) {
|
||||
double x = graph_padding + 2 + (static_cast<double>(i) / (vram_usage_history.size() - 1)) * (graph_width - 4);
|
||||
@@ -187,7 +197,9 @@ void VramOverlay::DrawVramGraph(QPainter& painter) {
|
||||
line_path.lineTo(graph_padding + graph_width - 2, graph_y + local_graph_height - 2);
|
||||
line_path.lineTo(graph_padding + 2, graph_y + local_graph_height - 2);
|
||||
line_path.closeSubpath();
|
||||
painter.fillPath(line_path, graph_fill_color);
|
||||
|
||||
// Fill using the dynamic color with transparency
|
||||
painter.fillPath(line_path, QColor(dynamic_color.red(), dynamic_color.green(), dynamic_color.blue(), 40));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,54 +220,74 @@ void VramOverlay::resizeEvent(QResizeEvent* event) {
|
||||
UpdatePosition();
|
||||
}
|
||||
|
||||
void VramOverlay::mousePressEvent(QMouseEvent* event) {
|
||||
if (event->button() == Qt::LeftButton && !size_grip->geometry().contains(event->pos())) {
|
||||
#if defined(Q_OS_LINUX)
|
||||
// LINUX-SPECIFIC IMPLEMENTATION (Wayland Fix)
|
||||
void VramOverlay::mousePressEvent(QMouseEvent* event) {
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
if (windowHandle()) {
|
||||
if (!UISettings::IsGamescope() && windowHandle()) {
|
||||
windowHandle()->startSystemMove();
|
||||
} else {
|
||||
is_dragging = true;
|
||||
drag_start_pos = event->globalPosition().toPoint() - this->pos();
|
||||
}
|
||||
}
|
||||
QWidget::mousePressEvent(event);
|
||||
}
|
||||
|
||||
void VramOverlay::mouseMoveEvent(QMouseEvent* event) {
|
||||
// Intentionally blank, the system compositor handles the move.
|
||||
QWidget::mouseMoveEvent(event);
|
||||
}
|
||||
|
||||
#else
|
||||
// ORIGINAL IMPLEMENTATION (For Windows, Android, etc.)
|
||||
void VramOverlay::mousePressEvent(QMouseEvent* event) {
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
is_dragging = true;
|
||||
drag_start_pos = event->globalPosition().toPoint();
|
||||
widget_start_pos = pos();
|
||||
setCursor(Qt::ClosedHandCursor);
|
||||
drag_start_pos = event->globalPosition().toPoint() - this->pos();
|
||||
#endif
|
||||
event->accept();
|
||||
}
|
||||
QWidget::mousePressEvent(event);
|
||||
}
|
||||
|
||||
void VramOverlay::mouseMoveEvent(QMouseEvent* event) {
|
||||
if (is_dragging) {
|
||||
QPoint delta = event->globalPosition().toPoint() - drag_start_pos;
|
||||
move(widget_start_pos + delta);
|
||||
move(event->globalPosition().toPoint() - drag_start_pos);
|
||||
event->accept();
|
||||
}
|
||||
QWidget::mouseMoveEvent(event);
|
||||
}
|
||||
#endif
|
||||
|
||||
void VramOverlay::mouseReleaseEvent(QMouseEvent* event) {
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
is_dragging = false;
|
||||
has_been_moved = true;
|
||||
setCursor(Qt::ArrowCursor);
|
||||
event->accept();
|
||||
}
|
||||
QWidget::mouseReleaseEvent(event);
|
||||
}
|
||||
|
||||
void VramOverlay::UpdateVramStats() {
|
||||
if (!main_window) return;
|
||||
if (!main_window || !is_enabled) return;
|
||||
|
||||
if (UISettings::IsGamescope()) {
|
||||
bool ui_active = (QApplication::activePopupWidget() != nullptr);
|
||||
|
||||
if (!ui_active) {
|
||||
for (QWidget* w : QApplication::topLevelWidgets()) {
|
||||
if (w->isVisible() && w != main_window && w != this &&
|
||||
!w->inherits("GRenderWindow") &&
|
||||
!w->inherits("PerformanceOverlay") &&
|
||||
!w->inherits("ControllerOverlay") &&
|
||||
!w->inherits("VramOverlay")) {
|
||||
ui_active = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ui_active) {
|
||||
if (!this->isHidden()) this->hide();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->isHidden()) {
|
||||
this->show();
|
||||
}
|
||||
} else {
|
||||
// Desktop: Respect the menu toggle strictly
|
||||
if (is_enabled && this->isHidden()) {
|
||||
this->show();
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
current_vram_data.total_vram = main_window->GetTotalVram();
|
||||
current_vram_data.used_vram = main_window->GetUsedVram();
|
||||
@@ -282,11 +314,12 @@ void VramOverlay::UpdateVramStats() {
|
||||
}
|
||||
last_vram_usage = current_vram_data.used_vram;
|
||||
}
|
||||
AddVramUsage(current_vram_data.vram_percentage);
|
||||
|
||||
vram_usage_history.push_back(current_vram_data.vram_percentage);
|
||||
if (vram_usage_history.size() > MAX_VRAM_HISTORY) vram_usage_history.pop_front();
|
||||
|
||||
update();
|
||||
} catch (...) {
|
||||
// Ignore
|
||||
}
|
||||
} catch (...) {}
|
||||
}
|
||||
|
||||
QColor VramOverlay::GetVramColor(double percentage) const {
|
||||
@@ -306,24 +339,8 @@ QString VramOverlay::FormatPercentage(double percentage) const {
|
||||
return QString::number(percentage, 'f', 1);
|
||||
}
|
||||
|
||||
void VramOverlay::AddVramUsage(double percentage) {
|
||||
vram_usage_history.push_back(percentage);
|
||||
if (vram_usage_history.size() > MAX_VRAM_HISTORY) {
|
||||
vram_usage_history.pop_front();
|
||||
}
|
||||
if (!vram_usage_history.empty()) {
|
||||
min_vram_usage = *std::min_element(vram_usage_history.begin(), vram_usage_history.end());
|
||||
max_vram_usage = *std::max_element(vram_usage_history.begin(), vram_usage_history.end());
|
||||
double range = max_vram_usage - min_vram_usage;
|
||||
if (range < 10.0) range = 10.0;
|
||||
min_vram_usage = std::max(0.0, min_vram_usage - range * 0.1);
|
||||
max_vram_usage = std::min(100.0, max_vram_usage + range * 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
void VramOverlay::UpdateTheme() {
|
||||
if (UISettings::IsDarkTheme()) {
|
||||
// Dark Theme Colors (your original values)
|
||||
background_color = QColor(15, 15, 15, 220);
|
||||
border_color = QColor(45, 45, 45, 255);
|
||||
text_color = QColor(240, 240, 240, 255);
|
||||
@@ -331,7 +348,6 @@ void VramOverlay::UpdateTheme() {
|
||||
graph_background_color = QColor(25, 25, 25, 255);
|
||||
graph_grid_color = QColor(60, 60, 60, 100);
|
||||
} else {
|
||||
// Light Theme Colors
|
||||
background_color = QColor(245, 245, 245, 220);
|
||||
border_color = QColor(200, 200, 200, 255);
|
||||
text_color = QColor(20, 20, 20, 255);
|
||||
@@ -339,5 +355,5 @@ void VramOverlay::UpdateTheme() {
|
||||
graph_background_color = QColor(225, 225, 225, 255);
|
||||
graph_grid_color = QColor(190, 190, 190, 100);
|
||||
}
|
||||
update(); // Force a repaint
|
||||
update();
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "citron/uisettings.h"
|
||||
|
||||
class GMainWindow;
|
||||
class QSizeGrip;
|
||||
|
||||
struct VramUsageData {
|
||||
u64 total_vram = 0;
|
||||
@@ -33,7 +34,7 @@ class VramOverlay : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit VramOverlay(GMainWindow* parent);
|
||||
explicit VramOverlay(QWidget* parent);
|
||||
~VramOverlay() override;
|
||||
|
||||
void SetVisible(bool visible);
|
||||
@@ -53,6 +54,9 @@ private slots:
|
||||
void UpdateVramStats();
|
||||
|
||||
private:
|
||||
bool is_enabled = false;
|
||||
bool is_visible = false;
|
||||
|
||||
void UpdatePosition();
|
||||
void DrawVramInfo(QPainter& painter);
|
||||
void DrawVramGraph(QPainter& painter);
|
||||
@@ -63,6 +67,7 @@ private:
|
||||
void AddVramUsage(double percentage);
|
||||
|
||||
GMainWindow* main_window;
|
||||
QSizeGrip* size_grip;
|
||||
QTimer update_timer;
|
||||
|
||||
// VRAM data
|
||||
@@ -71,13 +76,12 @@ private:
|
||||
u32 frame_counter = 0;
|
||||
|
||||
// VRAM graph data
|
||||
static constexpr size_t MAX_VRAM_HISTORY = 120; // 2 seconds at 60 FPS
|
||||
static constexpr size_t MAX_VRAM_HISTORY = 120;
|
||||
std::deque<double> vram_usage_history;
|
||||
double min_vram_usage = 0.0;
|
||||
double max_vram_usage = 100.0;
|
||||
|
||||
// Display settings
|
||||
bool is_visible = false;
|
||||
bool is_dragging = false;
|
||||
bool has_been_moved = false;
|
||||
QPoint drag_start_pos;
|
||||
|
||||
Reference in New Issue
Block a user