From 3487423d78cd5f80e48a73bee6c22559435c8567 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Wed, 15 Apr 2020 02:13:11 -0400 Subject: [PATCH] Initial Options tab, CVar dialog --- hecl-gui/ArgumentEditor.cpp | 25 +- hecl-gui/ArgumentEditor.hpp | 5 +- hecl-gui/ArgumentEditor.ui | 45 ++-- hecl-gui/CMakeLists.txt | 6 +- hecl-gui/CVarDialog.cpp | 79 ++++++ hecl-gui/CVarDialog.hpp | 32 +++ hecl-gui/CVarDialog.ui | 139 ++++++++++ hecl-gui/DownloadManager.cpp | 6 +- hecl-gui/LaunchMenu.cpp | 68 +++-- hecl-gui/LaunchMenu.hpp | 12 +- hecl-gui/MainWindow.cpp | 13 +- hecl-gui/MainWindow.ui | 504 ++++++++++++++++++++++++++--------- 12 files changed, 742 insertions(+), 192 deletions(-) create mode 100644 hecl-gui/CVarDialog.cpp create mode 100644 hecl-gui/CVarDialog.hpp create mode 100644 hecl-gui/CVarDialog.ui diff --git a/hecl-gui/ArgumentEditor.cpp b/hecl-gui/ArgumentEditor.cpp index 7fb210990..09dc434a1 100644 --- a/hecl-gui/ArgumentEditor.cpp +++ b/hecl-gui/ArgumentEditor.cpp @@ -1,12 +1,11 @@ #include "ArgumentEditor.hpp" #include "ui_ArgumentEditor.h" +#include "CVarDialog.hpp" #include #include #include -ArgumentEditor::ArgumentEditor(QWidget* parent) -: QDialog(parent) -, m_ui(std::make_unique()) { +ArgumentEditor::ArgumentEditor(QWidget* parent) : QDialog(parent), m_ui(std::make_unique()) { m_ui->setupUi(this); m_model.setStringList(QSettings().value(QStringLiteral("urde_arguments")).toStringList()); m_ui->argumentEditor->setModel(&m_model); @@ -24,10 +23,21 @@ void ArgumentEditor::on_addButton_clicked() { } } +void ArgumentEditor::on_addCvarButton_clicked() { + CVarDialog input(this); + int code = input.exec(); + if (code == DialogCode::Accepted) { + QStringList list = m_model.stringList(); + list << input.textValue(); + m_model.setStringList(list); + } +} + void ArgumentEditor::on_editButton_clicked() { QModelIndex index = m_ui->argumentEditor->currentIndex(); - if (!index.isValid()) + if (!index.isValid()) { return; + } QInputDialog input(this); input.setTextValue(m_model.stringList().value(index.row())); @@ -41,16 +51,17 @@ void ArgumentEditor::on_editButton_clicked() { void ArgumentEditor::on_deleteButton_clicked() { QModelIndex index = m_ui->argumentEditor->currentIndex(); - if (index.isValid()) + if (index.isValid()) { m_model.removeRows(index.row(), 1); + } } void ArgumentEditor::on_buttonBox_clicked(QAbstractButton* button) { - QDialogButtonBox* buttonBox = qobject_cast(sender()); + auto* buttonBox = qobject_cast(sender()); if (button == buttonBox->button(QDialogButtonBox::Ok)) { QSettings().setValue(QStringLiteral("urde_arguments"), m_model.stringList()); accept(); } else { reject(); } -} \ No newline at end of file +} diff --git a/hecl-gui/ArgumentEditor.hpp b/hecl-gui/ArgumentEditor.hpp index 27ea7706e..ce0e1e0fa 100644 --- a/hecl-gui/ArgumentEditor.hpp +++ b/hecl-gui/ArgumentEditor.hpp @@ -9,21 +9,22 @@ class QAbstractButton; namespace Ui { class ArgumentEditor; -} +} // namespace Ui class ArgumentEditor : public QDialog { Q_OBJECT std::unique_ptr m_ui; QStringListModel m_model; + public: explicit ArgumentEditor(QWidget* parent = nullptr); ~ArgumentEditor() override; private slots: void on_addButton_clicked(); + void on_addCvarButton_clicked(); void on_editButton_clicked(); void on_deleteButton_clicked(); void on_buttonBox_clicked(QAbstractButton*); }; - diff --git a/hecl-gui/ArgumentEditor.ui b/hecl-gui/ArgumentEditor.ui index aef4a721c..5dff1500c 100644 --- a/hecl-gui/ArgumentEditor.ui +++ b/hecl-gui/ArgumentEditor.ui @@ -14,14 +14,38 @@ Argument Editor + + + + + + + &Add + + + + + + + Add &CVar + + + + + + &Edit + + + + &Delete - + Qt::Vertical @@ -34,7 +58,7 @@ - + QDialogButtonBox::Cancel|QDialogButtonBox::Ok @@ -44,23 +68,6 @@ - - - - - - - &Add - - - - - - - &Edit - - - diff --git a/hecl-gui/CMakeLists.txt b/hecl-gui/CMakeLists.txt index 68db1c3c6..131bc54f3 100644 --- a/hecl-gui/CMakeLists.txt +++ b/hecl-gui/CMakeLists.txt @@ -21,6 +21,9 @@ add_executable(hecl-gui WIN32 MACOSX_BUNDLE ArgumentEditor.ui Common.cpp Common.hpp + CVarDialog.cpp + CVarDialog.hpp + CVarDialog.ui DownloadManager.cpp DownloadManager.hpp ErrorLabel.hpp @@ -66,7 +69,7 @@ target_compile_definitions(hecl-gui PRIVATE -DQT_NO_URL_CAST_FROM_STRING # Allows for more efficient string concatenation, resulting in less temporaries. - -DQT_USE_QSTRINGBUILDER + -DQT_USE_QSTRINGBUILDER ) target_link_libraries(hecl-gui PRIVATE @@ -80,6 +83,7 @@ target_link_libraries(hecl-gui PRIVATE xxhash z zeus + RetroDataSpec ) target_include_directories(hecl-gui PRIVATE quazip/quazip) diff --git a/hecl-gui/CVarDialog.cpp b/hecl-gui/CVarDialog.cpp new file mode 100644 index 000000000..72738c70a --- /dev/null +++ b/hecl-gui/CVarDialog.cpp @@ -0,0 +1,79 @@ +#include "CVarDialog.hpp" +#include "ui_CVarDialog.h" +#include +#include + +enum class CVarType { + String, + Boolean, +}; + +struct CVarItem { + QString m_name; + CVarType m_type; + QVariant m_defaultValue; + + CVarItem(QString name, CVarType type, QVariant defaultValue) + : m_name(std::move(name)), m_type(type), m_defaultValue(std::move(defaultValue)) {} +}; + +static std::array cvarList{ + CVarItem{QStringLiteral("tweak.game.FieldOfView"), CVarType::String, 55}, + CVarItem{QStringLiteral("debugOverlay.playerInfo"), CVarType::Boolean, false}, + CVarItem{QStringLiteral("debugOverlay.areaInfo"), CVarType::Boolean, false}, + // TODO expand +}; + +CVarDialog::CVarDialog(QWidget* parent) : QDialog(parent), m_ui(std::make_unique()) { + m_ui->setupUi(this); + QStringList list; + for (const auto& item : cvarList) { + list << item.m_name; + } + m_model.setStringList(list); + m_ui->cvarList->setModel(&m_model); + connect(m_ui->cvarList->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), this, + SLOT(handleSelectionChanged(QItemSelection))); +} + +CVarDialog::~CVarDialog() = default; + +void CVarDialog::on_buttonBox_accepted() { + const QModelIndexList& list = m_ui->cvarList->selectionModel()->selectedIndexes(); + if (list.isEmpty()) { + reject(); + } else { + accept(); + } +} + +void CVarDialog::on_buttonBox_rejected() { reject(); } + +void CVarDialog::handleSelectionChanged(const QItemSelection& selection) { + const QModelIndexList& list = selection.indexes(); + if (list.isEmpty()) { + return; + } + const auto item = cvarList[(*list.begin()).row()]; + m_ui->valueStack->setCurrentIndex(static_cast(item.m_type)); + if (item.m_type == CVarType::String) { + m_ui->stringValueField->setText(item.m_defaultValue.toString()); + } else if (item.m_type == CVarType::Boolean) { + m_ui->booleanValueField->setChecked(item.m_defaultValue.toBool()); + } +} + +QString CVarDialog::textValue() { + const QModelIndexList& list = m_ui->cvarList->selectionModel()->selectedIndexes(); + if (list.isEmpty()) { + return QStringLiteral(""); + } + const auto item = cvarList[(*list.begin()).row()]; + QVariant value; + if (item.m_type == CVarType::String) { + value = m_ui->stringValueField->text(); + } else if (item.m_type == CVarType::Boolean) { + value = m_ui->booleanValueField->isChecked(); + } + return QStringLiteral("+") + item.m_name + QStringLiteral("=") + value.toString(); +} diff --git a/hecl-gui/CVarDialog.hpp b/hecl-gui/CVarDialog.hpp new file mode 100644 index 000000000..1fdd81771 --- /dev/null +++ b/hecl-gui/CVarDialog.hpp @@ -0,0 +1,32 @@ +#ifndef CVARDIALOG_H +#define CVARDIALOG_H + +#include +#include +#include +#include + +namespace Ui { +class CVarDialog; +} // namespace Ui + +class CVarDialog : public QDialog { + Q_OBJECT + +public: + explicit CVarDialog(QWidget* parent = nullptr); + ~CVarDialog() override; + + QString textValue(); + +private: + std::unique_ptr m_ui; + QStringListModel m_model; + +private slots: + void on_buttonBox_accepted(); + void on_buttonBox_rejected(); + void handleSelectionChanged(const QItemSelection& selection); +}; + +#endif // CVARDIALOG_H diff --git a/hecl-gui/CVarDialog.ui b/hecl-gui/CVarDialog.ui new file mode 100644 index 000000000..6d44604e9 --- /dev/null +++ b/hecl-gui/CVarDialog.ui @@ -0,0 +1,139 @@ + + + CVarDialog + + + + 0 + 0 + 613 + 525 + + + + Add CVar + + + + + + + 0 + 0 + + + + Qt::Vertical + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + QAbstractItemView::NoEditTriggers + + + true + + + + + + + + 0 + 0 + + + + 0 + + + + + + + + 0 + 0 + + + + Value + + + Qt::PlainText + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + + + + + + + + + true + + + + 0 + 0 + + + + Enabled + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/hecl-gui/DownloadManager.cpp b/hecl-gui/DownloadManager.cpp index a782d41fa..9f243ca9d 100644 --- a/hecl-gui/DownloadManager.cpp +++ b/hecl-gui/DownloadManager.cpp @@ -47,7 +47,7 @@ static const QString Domain = QStringLiteral("https://releases.axiodl.com/"); static const QString Index = QStringLiteral("index.txt"); void DownloadManager::fetchIndex() { - if (m_indexInProgress) { + if (m_indexInProgress != nullptr) { return; } @@ -64,7 +64,7 @@ void DownloadManager::fetchIndex() { } void DownloadManager::fetchBinary(const QString& str, const QString& outPath) { - if (m_binaryInProgress) { + if (m_binaryInProgress != nullptr) { return; } @@ -80,7 +80,7 @@ void DownloadManager::fetchBinary(const QString& str, const QString& outPath) { connect(m_binaryInProgress, &QNetworkReply::encrypted, this, &DownloadManager::binaryValidateCert); connect(m_binaryInProgress, &QNetworkReply::downloadProgress, this, &DownloadManager::binaryDownloadProgress); - if (m_progBar) { + if (m_progBar != nullptr) { m_progBar->setEnabled(true); m_progBar->setValue(0); } diff --git a/hecl-gui/LaunchMenu.cpp b/hecl-gui/LaunchMenu.cpp index 028c0b4b0..dc8453c8c 100644 --- a/hecl-gui/LaunchMenu.cpp +++ b/hecl-gui/LaunchMenu.cpp @@ -3,8 +3,6 @@ #include "ArgumentEditor.hpp" #include -extern hecl::CVar* hecl::com_developer; - LaunchMenu::LaunchMenu(hecl::CVarCommons& commons, QWidget* parent) : QMenu(tr("Launch Menu"), parent) , m_commons(commons) @@ -12,6 +10,7 @@ LaunchMenu::LaunchMenu(hecl::CVarCommons& commons, QWidget* parent) , m_msaaMenu(tr("Anti-Aliasing"), this) , m_anisoMenu(tr("Anisotropic Filtering"), this) , m_experimentalMenu(tr("Experimental Features"), this) +, m_developerMenu(tr("Developer Options"), this) , m_apiGroup(this) , m_msaaGroup(this) , m_anisoGroup(this) { @@ -40,11 +39,14 @@ LaunchMenu::LaunchMenu(hecl::CVarCommons& commons, QWidget* parent) m_apiMenu.addActions(m_apiGroup.actions()); m_msaaMenu.addActions(m_msaaGroup.actions()); m_anisoMenu.addActions(m_anisoGroup.actions()); - initExperimental(); + initExperimentalMenu(); + initDeveloperMenu(); addMenu(&m_apiMenu)->setToolTip(QString::fromUtf8(m_commons.m_graphicsApi->rawHelp().data())); addMenu(&m_msaaMenu)->setToolTip(QString::fromUtf8(m_commons.m_drawSamples->rawHelp().data())); addMenu(&m_anisoMenu)->setToolTip(QString::fromUtf8(m_commons.m_texAnisotropy->rawHelp().data())); - addMenu(&m_experimentalMenu)->setToolTip(QString::fromUtf8(hecl::com_variableDt->rawHelp().data())); + addMenu(&m_experimentalMenu); + m_developerMenuAction = addMenu(&m_developerMenu); + m_developerMenuAction->setVisible(hecl::com_developer->toBoolean()); const QAction* argumentEditor = addAction(tr("Edit Runtime Arguments")); connect(argumentEditor, &QAction::triggered, this, &LaunchMenu::editRuntimeArgs); initDeepColor(); @@ -58,24 +60,27 @@ void LaunchMenu::initApiAction(const QString& action) { QAction* act = m_apiGroup.addAction(action); connect(act, &QAction::triggered, this, &LaunchMenu::apiTriggered); act->setCheckable(true); - if (!action.compare(QString::fromStdString(m_commons.getGraphicsApi()), Qt::CaseInsensitive)) + if (action.compare(QString::fromStdString(m_commons.getGraphicsApi()), Qt::CaseInsensitive) == 0) { act->setChecked(true); + } } void LaunchMenu::initMsaaAction(const QString& action) { QAction* act = m_msaaGroup.addAction(action); connect(act, &QAction::triggered, this, &LaunchMenu::msaaTriggered); act->setCheckable(true); - if (!action.compare(QString::number(m_commons.getSamples()), Qt::CaseInsensitive)) + if (action.compare(QString::number(m_commons.getSamples()), Qt::CaseInsensitive) == 0) { act->setChecked(true); + } } void LaunchMenu::initAnisoAction(const QString& action) { QAction* act = m_anisoGroup.addAction(action); connect(act, &QAction::triggered, this, &LaunchMenu::anisoTriggered); act->setCheckable(true); - if (!action.compare(QString::number(m_commons.getAnisotropy()), Qt::CaseInsensitive)) + if (action.compare(QString::number(m_commons.getAnisotropy()), Qt::CaseInsensitive) == 0) { act->setChecked(true); + } } void LaunchMenu::initDeepColor() { @@ -102,12 +107,17 @@ void LaunchMenu::initCheats() { connect(m_enableCheats, &QAction::triggered, this, &LaunchMenu::cheatsTriggered); } -void LaunchMenu::initExperimental() { - m_variableDt = m_experimentalMenu.addAction(tr("Variable delta time")); - m_variableDt->setToolTip(QString::fromUtf8(hecl::com_variableDt->rawHelp().data())); - m_variableDt->setCheckable(true); - m_variableDt->setChecked(hecl::com_variableDt->toBoolean()); - connect(m_variableDt, &QAction::triggered, this, &LaunchMenu::variableDtTriggered); +void LaunchMenu::initExperimentalMenu() { + initCVarAction(m_experimentalMenu.addAction(tr("Variable delta time")), m_commons.m_variableDt); +} + +void LaunchMenu::initDeveloperMenu() { + initCVarAction(m_developerMenu.addAction(tr("Area Info Overlay")), m_commons.m_debugOverlayAreaInfo); + initCVarAction(m_developerMenu.addAction(tr("Player Info Overlay")), m_commons.m_debugOverlayPlayerInfo); + initCVarAction(m_developerMenu.addAction(tr("World Info Overlay")), m_commons.m_debugOverlayWorldInfo); + initCVarAction(m_developerMenu.addAction(tr("Show Frame Counter")), m_commons.m_debugOverlayShowFrameCounter); + initCVarAction(m_developerMenu.addAction(tr("Show In-Game time")), m_commons.m_debugOverlayShowInGameTime); + initCVarAction(m_developerMenu.addAction(tr("Show Resource Stats")), m_commons.m_debugOverlayShowResourceStats); } void LaunchMenu::apiTriggered() { @@ -138,6 +148,7 @@ void LaunchMenu::developerModeTriggered() { if (hecl::com_enableCheats->toBoolean() && !isChecked) { m_enableCheats->setChecked(false); } + m_developerMenuAction->setVisible(isChecked); hecl::CVarManager::instance()->setDeveloperMode(isChecked, true); m_commons.serialize(); @@ -147,25 +158,32 @@ void LaunchMenu::cheatsTriggered() { const bool isChecked = qobject_cast(sender())->isChecked(); if (!hecl::com_developer->toBoolean() && isChecked) { - m_developerMode->setChecked(false); + m_developerMode->setChecked(true); + m_developerMenuAction->setVisible(true); } hecl::CVarManager::instance()->setCheatsEnabled(isChecked, true); m_commons.serialize(); } -void LaunchMenu::variableDtTriggered() { - const bool isChecked = qobject_cast(sender())->isChecked(); - - if (!hecl::com_variableDt->toBoolean() && isChecked) { - m_variableDt->setChecked(false); - } - - hecl::CVarManager::instance()->setVariableDtEnabled(isChecked, true); - m_commons.serialize(); -} - void LaunchMenu::editRuntimeArgs() { ArgumentEditor editor(this); editor.exec(); } + +void LaunchMenu::initCVarAction(QAction* action, hecl::CVar* cvar) const { + action->setData(QString::fromUtf8(cvar->name().data())); + action->setToolTip(QString::fromUtf8(cvar->rawHelp().data())); + action->setCheckable(true); + action->setChecked(cvar->toBoolean()); + connect(action, &QAction::triggered, this, &LaunchMenu::cvarTriggered); +} + +void LaunchMenu::cvarTriggered() { + auto* action = qobject_cast(sender()); + hecl::CVar* cVar = hecl::CVarManager::instance()->findCVar(action->data().toString().toStdString()); + if (cVar != nullptr) { + cVar->fromBoolean(action->isChecked()); + m_commons.serialize(); + } +} diff --git a/hecl-gui/LaunchMenu.hpp b/hecl-gui/LaunchMenu.hpp index d7d8a00c4..eb311fb21 100644 --- a/hecl-gui/LaunchMenu.hpp +++ b/hecl-gui/LaunchMenu.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include namespace hecl { struct CVarCommons; @@ -16,22 +17,29 @@ class LaunchMenu : public QMenu { QMenu m_msaaMenu; QMenu m_anisoMenu; QMenu m_experimentalMenu; + QMenu m_developerMenu; QActionGroup m_apiGroup; QActionGroup m_msaaGroup; QActionGroup m_anisoGroup; QAction* m_developerMode = nullptr; + QAction* m_developerMenuAction = nullptr; QAction* m_enableCheats = nullptr; QAction* m_variableDt = nullptr; + QAction* m_debugOverlayAreaInfo = nullptr; + QAction* m_debugOverlayPlayerInfo = nullptr; + void initApiAction(const QString& action); void initMsaaAction(const QString& action); void initAnisoAction(const QString& action); void initDeepColor(); void initDeveloperMode(); void initCheats(); - void initExperimental(); + void initExperimentalMenu(); + void initDeveloperMenu(); + void initCVarAction(QAction* action, hecl::CVar* cvar) const; public: explicit LaunchMenu(hecl::CVarCommons& commons, QWidget* parent = Q_NULLPTR); @@ -44,6 +52,6 @@ public slots: void deepColorTriggered(); void developerModeTriggered(); void cheatsTriggered(); - void variableDtTriggered(); void editRuntimeArgs(); + void cvarTriggered(); }; diff --git a/hecl-gui/MainWindow.cpp b/hecl-gui/MainWindow.cpp index efb37f2ff..493af62f3 100644 --- a/hecl-gui/MainWindow.cpp +++ b/hecl-gui/MainWindow.cpp @@ -299,6 +299,7 @@ void MainWindow::disableOperations() { m_ui->pathEdit->setEnabled(false); m_ui->browseBtn->setEnabled(false); m_ui->downloadButton->setEnabled(false); + m_ui->warpBtn->setEnabled(false); } void MainWindow::enableOperations() { @@ -306,6 +307,12 @@ void MainWindow::enableOperations() { m_ui->pathEdit->setEnabled(true); m_ui->browseBtn->setEnabled(true); + if (hecl::com_enableCheats->toBoolean()) { + m_ui->warpBtn->show(); + } else { + m_ui->warpBtn->hide(); + } + if (m_path.isEmpty()) return; @@ -321,8 +328,12 @@ void MainWindow::enableOperations() { m_ui->extractBtn->setEnabled(true); if (QFile::exists(m_path + QStringLiteral("/MP1/URDE/texture_cache.yaml"))) { m_ui->packageBtn->setEnabled(true); - if (isPackageComplete()) + if (isPackageComplete()) { m_ui->launchBtn->setEnabled(true); + if (hecl::com_enableCheats->toBoolean()) { + m_ui->warpBtn->setEnabled(true); + } + } } if (!m_ui->sysReqTable->isBlenderVersionOk()) { diff --git a/hecl-gui/MainWindow.ui b/hecl-gui/MainWindow.ui index baed905cc..a036b88ac 100644 --- a/hecl-gui/MainWindow.ui +++ b/hecl-gui/MainWindow.ui @@ -21,133 +21,7 @@ - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - false - - - - 16777215 - 16777215 - - - - &Launch - - - - - - - - 0 - 0 - - - - - 15 - 16777215 - - - - - - - - - - - - - - Qt::Horizontal - - - - 166 - 20 - - - - - - - - false - - - - 16777215 - 16777215 - - - - &Extract - - - - - - - false - - - - 16777215 - 16777215 - - - - &Package - - - - - - - Qt::Horizontal - - - - 167 - 20 - - - - - + @@ -158,7 +32,7 @@ - Wor&king Directory: + Extract Directory: pathEdit @@ -202,11 +76,8 @@ - + - - 0 - &Data @@ -315,6 +186,235 @@ + + + Options + + + + + + + 0 + 0 + + + + Graphics + + + + + + + + + + + Vulkan + + + + + + + Metal + + + + + + + D3D11 + + + + + + + + + + + + Anistropic Filtering + + + + + + + Anti-Aliasing + + + + + + + Deep Color + + + + + + + + + + Game + + + + + + Developer Mode + + + + + + + Enable Cheats + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Developer + + + + + + Area Info Overlay + + + + + + + Player Info Overlay + + + + + + + World Info Overlay + + + + + + + Show Frame Counter + + + + + + + Show In-Game Time + + + + + + + Show Resource Stats + + + + + + + + + + + 0 + 0 + + + + Experimental + + + + + + Variable Delta Time + + + + + + + + + + + 0 + 0 + + + + Launch Options + + + + + + Delete + + + + + + + + 0 + 0 + + + + + + + + Add + + + + + + + Edit + + + + + + + + @@ -919,6 +1019,146 @@ p, li { white-space: pre-wrap; } + + + + + + Qt::Horizontal + + + + 167 + 20 + + + + + + + + false + + + + 16777215 + 16777215 + + + + &Extract + + + + + + + false + + + + 16777215 + 16777215 + + + + &Package + + + + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + false + + + + 16777215 + 16777215 + + + + &Launch + + + + + + + + 0 + 0 + + + + + 15 + 16777215 + + + + + + + + + + + + + + false + + + Launch && &Warp + + + + + + + Qt::Horizontal + + + + 166 + 20 + + + + + +