Merge pull request #2034 from jroweboy/loading-widget
QT Frontend: Add a Loading screen with progressbar
This commit is contained in:
commit
1c733bf175
@ -45,5 +45,8 @@ function(copy_yuzu_Qt5_deps target_dir)
|
|||||||
|
|
||||||
windows_copy_files(yuzu ${Qt5_PLATFORMS_DIR} ${PLATFORMS} qwindows$<$<CONFIG:Debug>:d>.*)
|
windows_copy_files(yuzu ${Qt5_PLATFORMS_DIR} ${PLATFORMS} qwindows$<$<CONFIG:Debug>:d>.*)
|
||||||
windows_copy_files(yuzu ${Qt5_STYLES_DIR} ${STYLES} qwindowsvistastyle$<$<CONFIG:Debug>:d>.*)
|
windows_copy_files(yuzu ${Qt5_STYLES_DIR} ${STYLES} qwindowsvistastyle$<$<CONFIG:Debug>:d>.*)
|
||||||
windows_copy_files(yuzu ${Qt5_IMAGEFORMATS_DIR} ${IMAGEFORMATS} qjpeg$<$<CONFIG:Debug>:d>.*)
|
windows_copy_files(yuzu ${Qt5_IMAGEFORMATS_DIR} ${IMAGEFORMATS}
|
||||||
|
qjpeg$<$<CONFIG:Debug>:d>.*
|
||||||
|
qgif$<$<CONFIG:Debug>:d>.*
|
||||||
|
)
|
||||||
endfunction(copy_yuzu_Qt5_deps)
|
endfunction(copy_yuzu_Qt5_deps)
|
||||||
|
@ -68,6 +68,8 @@ add_executable(yuzu
|
|||||||
game_list_p.h
|
game_list_p.h
|
||||||
game_list_worker.cpp
|
game_list_worker.cpp
|
||||||
game_list_worker.h
|
game_list_worker.h
|
||||||
|
loading_screen.cpp
|
||||||
|
loading_screen.h
|
||||||
hotkeys.cpp
|
hotkeys.cpp
|
||||||
hotkeys.h
|
hotkeys.h
|
||||||
main.cpp
|
main.cpp
|
||||||
@ -102,9 +104,10 @@ set(UIS
|
|||||||
configuration/configure_system.ui
|
configuration/configure_system.ui
|
||||||
configuration/configure_touchscreen_advanced.ui
|
configuration/configure_touchscreen_advanced.ui
|
||||||
configuration/configure_web.ui
|
configuration/configure_web.ui
|
||||||
hotkeys.ui
|
|
||||||
main.ui
|
|
||||||
compatdb.ui
|
compatdb.ui
|
||||||
|
hotkeys.ui
|
||||||
|
loading_screen.ui
|
||||||
|
main.ui
|
||||||
)
|
)
|
||||||
|
|
||||||
file(GLOB COMPAT_LIST
|
file(GLOB COMPAT_LIST
|
||||||
|
@ -3,9 +3,7 @@
|
|||||||
#include <QKeyEvent>
|
#include <QKeyEvent>
|
||||||
#include <QScreen>
|
#include <QScreen>
|
||||||
#include <QWindow>
|
#include <QWindow>
|
||||||
|
|
||||||
#include <fmt/format.h>
|
#include <fmt/format.h>
|
||||||
|
|
||||||
#include "common/microprofile.h"
|
#include "common/microprofile.h"
|
||||||
#include "common/scm_rev.h"
|
#include "common/scm_rev.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
@ -17,6 +15,7 @@
|
|||||||
#include "video_core/renderer_base.h"
|
#include "video_core/renderer_base.h"
|
||||||
#include "video_core/video_core.h"
|
#include "video_core/video_core.h"
|
||||||
#include "yuzu/bootmanager.h"
|
#include "yuzu/bootmanager.h"
|
||||||
|
#include "yuzu/main.h"
|
||||||
|
|
||||||
EmuThread::EmuThread(GRenderWindow* render_window) : render_window(render_window) {}
|
EmuThread::EmuThread(GRenderWindow* render_window) : render_window(render_window) {}
|
||||||
|
|
||||||
@ -114,6 +113,8 @@ GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread)
|
|||||||
|
|
||||||
InputCommon::Init();
|
InputCommon::Init();
|
||||||
InputCommon::StartJoystickEventHandler();
|
InputCommon::StartJoystickEventHandler();
|
||||||
|
connect(this, &GRenderWindow::FirstFrameDisplayed, static_cast<GMainWindow*>(parent),
|
||||||
|
&GMainWindow::OnLoadComplete);
|
||||||
}
|
}
|
||||||
|
|
||||||
GRenderWindow::~GRenderWindow() {
|
GRenderWindow::~GRenderWindow() {
|
||||||
@ -141,6 +142,10 @@ void GRenderWindow::SwapBuffers() {
|
|||||||
child->makeCurrent();
|
child->makeCurrent();
|
||||||
|
|
||||||
child->swapBuffers();
|
child->swapBuffers();
|
||||||
|
if (!first_frame) {
|
||||||
|
emit FirstFrameDisplayed();
|
||||||
|
first_frame = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GRenderWindow::MakeCurrent() {
|
void GRenderWindow::MakeCurrent() {
|
||||||
@ -309,6 +314,8 @@ void GRenderWindow::InitRenderTarget() {
|
|||||||
delete layout();
|
delete layout();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
first_frame = false;
|
||||||
|
|
||||||
// TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground,
|
// TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground,
|
||||||
// WA_DontShowOnScreen, WA_DeleteOnClose
|
// WA_DontShowOnScreen, WA_DeleteOnClose
|
||||||
QGLFormat fmt;
|
QGLFormat fmt;
|
||||||
|
@ -152,6 +152,7 @@ public slots:
|
|||||||
signals:
|
signals:
|
||||||
/// Emitted when the window is closed
|
/// Emitted when the window is closed
|
||||||
void Closed();
|
void Closed();
|
||||||
|
void FirstFrameDisplayed();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::pair<unsigned, unsigned> ScaleTouch(const QPointF pos) const;
|
std::pair<unsigned, unsigned> ScaleTouch(const QPointF pos) const;
|
||||||
@ -171,6 +172,8 @@ private:
|
|||||||
/// Temporary storage of the screenshot taken
|
/// Temporary storage of the screenshot taken
|
||||||
QImage screenshot_image;
|
QImage screenshot_image;
|
||||||
|
|
||||||
|
bool first_frame = false;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void showEvent(QShowEvent* event) override;
|
void showEvent(QShowEvent* event) override;
|
||||||
};
|
};
|
||||||
|
84
src/yuzu/loading_screen.cpp
Normal file
84
src/yuzu/loading_screen.cpp
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
// Copyright 2019 yuzu Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <QBuffer>
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
#include <QIODevice>
|
||||||
|
#include <QImage>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QPainter>
|
||||||
|
#include <QPalette>
|
||||||
|
#include <QPixmap>
|
||||||
|
#include <QProgressBar>
|
||||||
|
#include <QStyleOption>
|
||||||
|
#include <QWindow>
|
||||||
|
#include "common/logging/log.h"
|
||||||
|
#include "core/loader/loader.h"
|
||||||
|
#include "ui_loading_screen.h"
|
||||||
|
#include "yuzu/loading_screen.h"
|
||||||
|
|
||||||
|
// Mingw seems to not have QMovie at all. If QMovie is missing then use a single frame instead of an
|
||||||
|
// showing the full animation
|
||||||
|
#if !YUZU_QT_MOVIE_MISSING
|
||||||
|
#include <QMovie>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
LoadingScreen::LoadingScreen(QWidget* parent)
|
||||||
|
: QWidget(parent), ui(std::make_unique<Ui::LoadingScreen>()) {
|
||||||
|
ui->setupUi(this);
|
||||||
|
// Progress bar is hidden until we have a use for it.
|
||||||
|
ui->progress_bar->hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
LoadingScreen::~LoadingScreen() = default;
|
||||||
|
|
||||||
|
void LoadingScreen::Prepare(Loader::AppLoader& loader) {
|
||||||
|
std::vector<u8> buffer;
|
||||||
|
if (loader.ReadBanner(buffer) == Loader::ResultStatus::Success) {
|
||||||
|
#ifdef YUZU_QT_MOVIE_MISSING
|
||||||
|
QPixmap map;
|
||||||
|
map.loadFromData(buffer.data(), buffer.size());
|
||||||
|
ui->banner->setPixmap(map);
|
||||||
|
#else
|
||||||
|
backing_mem =
|
||||||
|
std::make_unique<QByteArray>(reinterpret_cast<char*>(buffer.data()), buffer.size());
|
||||||
|
backing_buf = std::make_unique<QBuffer>(backing_mem.get());
|
||||||
|
backing_buf->open(QIODevice::ReadOnly);
|
||||||
|
animation = std::make_unique<QMovie>(backing_buf.get(), QByteArray("GIF"));
|
||||||
|
animation->start();
|
||||||
|
ui->banner->setMovie(animation.get());
|
||||||
|
#endif
|
||||||
|
buffer.clear();
|
||||||
|
}
|
||||||
|
if (loader.ReadLogo(buffer) == Loader::ResultStatus::Success) {
|
||||||
|
QPixmap map;
|
||||||
|
map.loadFromData(buffer.data(), buffer.size());
|
||||||
|
ui->logo->setPixmap(map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoadingScreen::OnLoadProgress(std::size_t value, std::size_t total) {
|
||||||
|
if (total != previous_total) {
|
||||||
|
ui->progress_bar->setMaximum(total);
|
||||||
|
previous_total = total;
|
||||||
|
}
|
||||||
|
ui->progress_bar->setValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoadingScreen::paintEvent(QPaintEvent* event) {
|
||||||
|
QStyleOption opt;
|
||||||
|
opt.init(this);
|
||||||
|
QPainter p(this);
|
||||||
|
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
|
||||||
|
QWidget::paintEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoadingScreen::Clear() {
|
||||||
|
#ifndef YUZU_QT_MOVIE_MISSING
|
||||||
|
animation.reset();
|
||||||
|
backing_buf.reset();
|
||||||
|
backing_mem.reset();
|
||||||
|
#endif
|
||||||
|
}
|
56
src/yuzu/loading_screen.h
Normal file
56
src/yuzu/loading_screen.h
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
// Copyright 2019 yuzu Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
#if !QT_CONFIG(movie)
|
||||||
|
#define YUZU_QT_MOVIE_MISSING 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace Loader {
|
||||||
|
class AppLoader;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class LoadingScreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
class QBuffer;
|
||||||
|
class QByteArray;
|
||||||
|
class QMovie;
|
||||||
|
|
||||||
|
class LoadingScreen : public QWidget {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit LoadingScreen(QWidget* parent = nullptr);
|
||||||
|
|
||||||
|
~LoadingScreen();
|
||||||
|
|
||||||
|
/// Call before showing the loading screen to load the widgets with the logo and banner for the
|
||||||
|
/// currently loaded application.
|
||||||
|
void Prepare(Loader::AppLoader& loader);
|
||||||
|
|
||||||
|
/// After the loading screen is hidden, the owner of this class can call this to clean up any
|
||||||
|
/// used resources such as the logo and banner.
|
||||||
|
void Clear();
|
||||||
|
|
||||||
|
// In order to use a custom widget with a stylesheet, you need to override the paintEvent
|
||||||
|
// See https://wiki.qt.io/How_to_Change_the_Background_Color_of_QWidget
|
||||||
|
void paintEvent(QPaintEvent* event) override;
|
||||||
|
|
||||||
|
void OnLoadProgress(std::size_t value, std::size_t total);
|
||||||
|
|
||||||
|
private:
|
||||||
|
#ifndef YUZU_QT_MOVIE_MISSING
|
||||||
|
std::unique_ptr<QMovie> animation;
|
||||||
|
std::unique_ptr<QBuffer> backing_buf;
|
||||||
|
std::unique_ptr<QByteArray> backing_mem;
|
||||||
|
#endif
|
||||||
|
std::unique_ptr<Ui::LoadingScreen> ui;
|
||||||
|
std::size_t previous_total = 0;
|
||||||
|
};
|
79
src/yuzu/loading_screen.ui
Normal file
79
src/yuzu/loading_screen.ui
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>LoadingScreen</class>
|
||||||
|
<widget class="QWidget" name="LoadingScreen">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>746</width>
|
||||||
|
<height>495</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="styleSheet">
|
||||||
|
<string notr="true">background-color: rgb(0, 0, 0);</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="logo">
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||||
|
</property>
|
||||||
|
<property name="margin">
|
||||||
|
<number>30</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QProgressBar" name="progress_bar">
|
||||||
|
<property name="styleSheet">
|
||||||
|
<string notr="true">font-size: 26px;</string>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="format">
|
||||||
|
<string>Loading Shaders %v out of %m</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item alignment="Qt::AlignRight|Qt::AlignBottom">
|
||||||
|
<widget class="QLabel" name="banner">
|
||||||
|
<property name="styleSheet">
|
||||||
|
<string notr="true">background-color: black;</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
<property name="margin">
|
||||||
|
<number>30</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
@ -92,6 +92,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
|
|||||||
#include "yuzu/game_list.h"
|
#include "yuzu/game_list.h"
|
||||||
#include "yuzu/game_list_p.h"
|
#include "yuzu/game_list_p.h"
|
||||||
#include "yuzu/hotkeys.h"
|
#include "yuzu/hotkeys.h"
|
||||||
|
#include "yuzu/loading_screen.h"
|
||||||
#include "yuzu/main.h"
|
#include "yuzu/main.h"
|
||||||
#include "yuzu/ui_settings.h"
|
#include "yuzu/ui_settings.h"
|
||||||
|
|
||||||
@ -411,6 +412,10 @@ void GMainWindow::InitializeWidgets() {
|
|||||||
game_list = new GameList(vfs, this);
|
game_list = new GameList(vfs, this);
|
||||||
ui.horizontalLayout->addWidget(game_list);
|
ui.horizontalLayout->addWidget(game_list);
|
||||||
|
|
||||||
|
loading_screen = new LoadingScreen(this);
|
||||||
|
loading_screen->hide();
|
||||||
|
ui.horizontalLayout->addWidget(loading_screen);
|
||||||
|
|
||||||
// Create status bar
|
// Create status bar
|
||||||
message_label = new QLabel();
|
message_label = new QLabel();
|
||||||
// Configured separately for left alignment
|
// Configured separately for left alignment
|
||||||
@ -897,8 +902,9 @@ void GMainWindow::BootGame(const QString& filename) {
|
|||||||
.arg(Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc,
|
.arg(Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc,
|
||||||
QString::fromStdString(title_name)));
|
QString::fromStdString(title_name)));
|
||||||
|
|
||||||
render_window->show();
|
loading_screen->Prepare(Core::System::GetInstance().GetAppLoader());
|
||||||
render_window->setFocus();
|
loading_screen->show();
|
||||||
|
loading_screen->setFocus();
|
||||||
|
|
||||||
emulation_running = true;
|
emulation_running = true;
|
||||||
if (ui.action_Fullscreen->isChecked()) {
|
if (ui.action_Fullscreen->isChecked()) {
|
||||||
@ -932,6 +938,8 @@ void GMainWindow::ShutdownGame() {
|
|||||||
ui.action_Load_Amiibo->setEnabled(false);
|
ui.action_Load_Amiibo->setEnabled(false);
|
||||||
ui.action_Capture_Screenshot->setEnabled(false);
|
ui.action_Capture_Screenshot->setEnabled(false);
|
||||||
render_window->hide();
|
render_window->hide();
|
||||||
|
loading_screen->hide();
|
||||||
|
loading_screen->Clear();
|
||||||
game_list->show();
|
game_list->show();
|
||||||
game_list->setFilterFocus();
|
game_list->setFilterFocus();
|
||||||
setWindowTitle(QString("yuzu %1| %2-%3")
|
setWindowTitle(QString("yuzu %1| %2-%3")
|
||||||
@ -1505,6 +1513,13 @@ void GMainWindow::OnStopGame() {
|
|||||||
ShutdownGame();
|
ShutdownGame();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GMainWindow::OnLoadComplete() {
|
||||||
|
loading_screen->hide();
|
||||||
|
loading_screen->Clear();
|
||||||
|
render_window->show();
|
||||||
|
render_window->setFocus();
|
||||||
|
}
|
||||||
|
|
||||||
void GMainWindow::OnMenuReportCompatibility() {
|
void GMainWindow::OnMenuReportCompatibility() {
|
||||||
if (!Settings::values.yuzu_token.empty() && !Settings::values.yuzu_username.empty()) {
|
if (!Settings::values.yuzu_token.empty() && !Settings::values.yuzu_username.empty()) {
|
||||||
CompatDB compatdb{this};
|
CompatDB compatdb{this};
|
||||||
@ -1771,9 +1786,8 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
|
|||||||
this, tr("Confirm Key Rederivation"),
|
this, tr("Confirm Key Rederivation"),
|
||||||
tr("You are about to force rederive all of your keys. \nIf you do not know what this "
|
tr("You are about to force rederive all of your keys. \nIf you do not know what this "
|
||||||
"means or what you are doing, \nthis is a potentially destructive action. \nPlease "
|
"means or what you are doing, \nthis is a potentially destructive action. \nPlease "
|
||||||
"make "
|
"make sure this is what you want \nand optionally make backups.\n\nThis will delete "
|
||||||
"sure this is what you want \nand optionally make backups.\n\nThis will delete your "
|
"your autogenerated key files and re-run the key derivation module."),
|
||||||
"autogenerated key files and re-run the key derivation module."),
|
|
||||||
QMessageBox::StandardButtons{QMessageBox::Ok, QMessageBox::Cancel});
|
QMessageBox::StandardButtons{QMessageBox::Ok, QMessageBox::Cancel});
|
||||||
|
|
||||||
if (res == QMessageBox::Cancel)
|
if (res == QMessageBox::Cancel)
|
||||||
|
@ -25,6 +25,7 @@ class GImageInfo;
|
|||||||
class GraphicsBreakPointsWidget;
|
class GraphicsBreakPointsWidget;
|
||||||
class GraphicsSurfaceWidget;
|
class GraphicsSurfaceWidget;
|
||||||
class GRenderWindow;
|
class GRenderWindow;
|
||||||
|
class LoadingScreen;
|
||||||
class MicroProfileDialog;
|
class MicroProfileDialog;
|
||||||
class ProfilerWidget;
|
class ProfilerWidget;
|
||||||
class QLabel;
|
class QLabel;
|
||||||
@ -109,10 +110,10 @@ signals:
|
|||||||
void WebBrowserFinishedBrowsing();
|
void WebBrowserFinishedBrowsing();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
void OnLoadComplete();
|
||||||
void ProfileSelectorSelectProfile();
|
void ProfileSelectorSelectProfile();
|
||||||
void SoftwareKeyboardGetText(const Core::Frontend::SoftwareKeyboardParameters& parameters);
|
void SoftwareKeyboardGetText(const Core::Frontend::SoftwareKeyboardParameters& parameters);
|
||||||
void SoftwareKeyboardInvokeCheckDialog(std::u16string error_message);
|
void SoftwareKeyboardInvokeCheckDialog(std::u16string error_message);
|
||||||
|
|
||||||
void WebBrowserOpenPage(std::string_view filename, std::string_view arguments);
|
void WebBrowserOpenPage(std::string_view filename, std::string_view arguments);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -212,6 +213,7 @@ private:
|
|||||||
|
|
||||||
GRenderWindow* render_window;
|
GRenderWindow* render_window;
|
||||||
GameList* game_list;
|
GameList* game_list;
|
||||||
|
LoadingScreen* loading_screen;
|
||||||
|
|
||||||
// Status bar elements
|
// Status bar elements
|
||||||
QLabel* message_label = nullptr;
|
QLabel* message_label = nullptr;
|
||||||
|
Loading…
Reference in New Issue
Block a user