diff --git a/.ci/Arch/Dockerfile b/.ci/Arch/Dockerfile index f37315262..36cf5c4ae 100644 --- a/.ci/Arch/Dockerfile +++ b/.ci/Arch/Dockerfile @@ -10,10 +10,8 @@ RUN pacman --sync --refresh --sysupgrade --needed --noconfirm \ ninja \ protobuf \ qt6-base \ - qt6-declarative \ qt6-imageformats \ qt6-multimedia \ - qt6-shadertools \ qt6-svg \ qt6-tools \ qt6-translations \ diff --git a/.ci/Debian12/Dockerfile b/.ci/Debian12/Dockerfile index 0fa227d6f..202405b84 100644 --- a/.ci/Debian12/Dockerfile +++ b/.ci/Debian12/Dockerfile @@ -20,9 +20,7 @@ RUN apt-get update && \ qt6-image-formats-plugins \ qt6-l10n-tools \ qt6-multimedia-dev \ - qt6-declarative-dev \ qt6-svg-dev \ - qt6-shadertools-dev \ qt6-tools-dev \ qt6-tools-dev-tools \ qt6-websockets-dev \ diff --git a/.ci/Debian13/Dockerfile b/.ci/Debian13/Dockerfile index 13e8b35c7..d7ab6ac86 100644 --- a/.ci/Debian13/Dockerfile +++ b/.ci/Debian13/Dockerfile @@ -21,9 +21,7 @@ RUN apt-get update && \ qt6-image-formats-plugins \ qt6-l10n-tools \ qt6-multimedia-dev \ - qt6-declarative-dev \ qt6-svg-dev \ - qt6-shadertools-dev \ qt6-tools-dev \ qt6-tools-dev-tools \ qt6-websockets-dev \ diff --git a/.ci/Fedora43/Dockerfile b/.ci/Fedora43/Dockerfile index 68e894543..27570cf99 100644 --- a/.ci/Fedora43/Dockerfile +++ b/.ci/Fedora43/Dockerfile @@ -8,7 +8,7 @@ RUN dnf install -y \ mariadb-devel \ ninja-build \ protobuf-devel \ - qt6-{qtdeclarative,qtshadertools,qttools,qtsvg,qtmultimedia,qtwebsockets}-devel \ + qt6-{qttools,qtsvg,qtmultimedia,qtwebsockets}-devel \ qt6-qtimageformats \ rpm-build \ xz-devel \ diff --git a/.ci/Fedora44/Dockerfile b/.ci/Fedora44/Dockerfile index ffd7c1b9b..e6c8da7f3 100644 --- a/.ci/Fedora44/Dockerfile +++ b/.ci/Fedora44/Dockerfile @@ -8,7 +8,7 @@ RUN dnf install -y \ mariadb-devel \ ninja-build \ protobuf-devel \ - qt6-{qtdeclarative,qtshadertools,qttools,qtsvg,qtmultimedia,qtwebsockets}-devel \ + qt6-{qttools,qtsvg,qtmultimedia,qtwebsockets}-devel \ qt6-qtimageformats \ rpm-build \ xz-devel \ diff --git a/.ci/Ubuntu24.04/Dockerfile b/.ci/Ubuntu24.04/Dockerfile index 12320c276..809b2e43a 100644 --- a/.ci/Ubuntu24.04/Dockerfile +++ b/.ci/Ubuntu24.04/Dockerfile @@ -20,9 +20,7 @@ RUN apt-get update && \ qt6-image-formats-plugins \ qt6-l10n-tools \ qt6-multimedia-dev \ - qt6-declarative-dev \ qt6-svg-dev \ - qt6-shadertools-dev \ qt6-tools-dev \ qt6-tools-dev-tools \ qt6-websockets-dev \ diff --git a/.ci/Ubuntu26.04/Dockerfile b/.ci/Ubuntu26.04/Dockerfile index ce3d9cd6c..7b0cd389f 100644 --- a/.ci/Ubuntu26.04/Dockerfile +++ b/.ci/Ubuntu26.04/Dockerfile @@ -21,9 +21,7 @@ RUN apt-get update && \ qt6-image-formats-plugins \ qt6-l10n-tools \ qt6-multimedia-dev \ - qt6-declarative-dev \ qt6-svg-dev \ - qt6-shadertools-dev \ qt6-tools-dev \ qt6-tools-dev-tools \ qt6-websockets-dev \ diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index e895e2220..58ca87573 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -68,9 +68,7 @@ jobs: libprotobuf-dev \ ninja-build \ protobuf-compiler \ - qt6-declarative-dev \ qt6-multimedia-dev \ - qt6-shadertools-dev \ qt6-svg-dev \ qt6-tools-dev \ qt6-tools-dev-tools \ diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index 04037a74e..744f9e70a 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -269,7 +269,7 @@ jobs: override_target: 13 package_suffix: "-macOS13_Intel" qt_version: 6.11.1 - qt_modules: qtimageformats qtmultimedia qtwebsockets qtshadertools + qt_modules: qtimageformats qtmultimedia qtwebsockets soc: Intel type: Release use_ccache: 1 @@ -285,7 +285,7 @@ jobs: override_target: 14 package_suffix: "-macOS14" qt_version: 6.11.1 - qt_modules: qtimageformats qtmultimedia qtwebsockets qtshadertools + qt_modules: qtimageformats qtmultimedia qtwebsockets soc: Apple type: Release use_ccache: 1 @@ -301,7 +301,7 @@ jobs: override_target: 15 package_suffix: "-macOS15" qt_version: 6.11.1 - qt_modules: qtimageformats qtmultimedia qtwebsockets qtshadertools + qt_modules: qtimageformats qtmultimedia qtwebsockets soc: Apple type: Release use_ccache: 1 @@ -314,7 +314,7 @@ jobs: ccache_eviction_age: 7d cmake_generator: Ninja qt_version: 6.11.1 - qt_modules: qtimageformats qtmultimedia qtwebsockets qtshadertools + qt_modules: qtimageformats qtmultimedia qtwebsockets soc: Apple type: Debug use_ccache: 1 @@ -329,7 +329,7 @@ jobs: make_package: 1 package_suffix: "-Win10" qt_version: 6.11.1 - qt_modules: qtimageformats qtmultimedia qtwebsockets qtshadertools + qt_modules: qtimageformats qtmultimedia qtwebsockets type: Release name: ${{ matrix.os }} ${{ matrix.target }}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }} diff --git a/cmake/FindQtRuntime.cmake b/cmake/FindQtRuntime.cmake index 0259d12e1..8a3050813 100644 --- a/cmake/FindQtRuntime.cmake +++ b/cmake/FindQtRuntime.cmake @@ -18,13 +18,10 @@ if(WITH_CLIENT) Multimedia Network PrintSupport - ShaderTools Svg WebSockets Widgets Xml - Quick - QuickWidgets ) endif() if(WITH_ORACLE) diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index 7690fb32a..1924d86bf 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -397,27 +397,6 @@ set(cockatrice_SOURCES src/interface/widgets/tabs/api/commander_spellbook/commander_bracket_service.cpp src/interface/widgets/tabs/api/commander_spellbook/commander_bracket_widget.cpp src/interface/widgets/tabs/api/commander_spellbook/handle_commander_brackets.cpp - src/interface/widgets/onboarding/banner_shader_config.h - src/interface/widgets/onboarding/first_run_wizard.cpp - src/interface/widgets/onboarding/first_run_wizard.h - src/interface/widgets/onboarding/first_run_wizard_page.cpp - src/interface/widgets/onboarding/first_run_wizard_page.h - src/interface/widgets/onboarding/pages/account_setup_page.cpp - src/interface/widgets/onboarding/pages/account_setup_page.h - src/interface/widgets/onboarding/pages/card_database_setup_page.cpp - src/interface/widgets/onboarding/pages/card_database_setup_page.h - src/interface/widgets/onboarding/pages/finish_page.cpp - src/interface/widgets/onboarding/pages/finish_page.h - src/interface/widgets/onboarding/pages/preferences_setup_page.cpp - src/interface/widgets/onboarding/pages/preferences_setup_page.h - src/interface/widgets/onboarding/pages/theme_setup_page.cpp - src/interface/widgets/onboarding/pages/theme_setup_page.h - src/interface/widgets/onboarding/pages/welcome_page.cpp - src/interface/widgets/onboarding/pages/welcome_page.h - src/interface/widgets/onboarding/shader_banner_widget.cpp - src/interface/widgets/onboarding/shader_banner_widget.h - src/interface/widgets/onboarding/step_indicator_widget.cpp - src/interface/widgets/onboarding/step_indicator_widget.h src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.cpp src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.h src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.cpp @@ -514,28 +493,6 @@ qt6_add_executable( MANUAL_FINALIZATION ) -qt6_add_shaders( - cockatrice - "onboarding_shaders" - PREFIX - "/onboarding/shaders" - BASE - "src/interface/widgets/onboarding/shaders" - FILES - src/interface/widgets/onboarding/shaders/brand_banner.frag -) - -qt6_add_resources( - cockatrice - "onboarding_qml" - PREFIX - "/onboarding/qml" - BASE - "src/interface/widgets/onboarding/qml" - FILES - src/interface/widgets/onboarding/qml/BrandBanner.qml -) - target_link_libraries( cockatrice PUBLIC libcockatrice_card diff --git a/cockatrice/cockatrice.qrc b/cockatrice/cockatrice.qrc index e21bdb0be..9c34929b7 100644 --- a/cockatrice/cockatrice.qrc +++ b/cockatrice/cockatrice.qrc @@ -2,7 +2,6 @@ resources/cardback.svg resources/cockatrice.svg - resources/cockatrice-logo-white.svg resources/hand.svg resources/hr.jpg diff --git a/cockatrice/resources/cockatrice-logo-white.svg b/cockatrice/resources/cockatrice-logo-white.svg deleted file mode 100644 index b3b31077f..000000000 --- a/cockatrice/resources/cockatrice-logo-white.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - diff --git a/cockatrice/resources/cockatrice.svg b/cockatrice/resources/cockatrice.svg index 89ba62dcf..d2e22da31 100644 --- a/cockatrice/resources/cockatrice.svg +++ b/cockatrice/resources/cockatrice.svg @@ -2,20 +2,20 @@ + sodipodi:docname="cockatrice.svg"> - - + inkscape:current-layer="g3026" /> + + + + + + + + + + + + + + + + + - - - - - - - - + + + + + + + + + + + + + + @@ -159,7 +199,7 @@ image/svg+xml - + @@ -173,60 +213,170 @@ inkscape:export-xdpi="91.459999" inkscape:export-ydpi="91.459999"> + + + + + + + + + + + + + + diff --git a/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp b/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp index 9cde72c01..adae6e152 100644 --- a/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp +++ b/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp @@ -290,35 +290,17 @@ void PaletteEditorDialog::onSave() // Persist every scheme that changed, not just the one on screen. Each scheme // has its own file, so edits to the non-active scheme would otherwise be // silently discarded when the dialog closes. - // - // Save the loaded scheme last so commitPalette's global colour-scheme - // update (ThemeConfig::colorScheme) points at the active scheme. for (auto it = workingConfig.begin(); it != workingConfig.end(); ++it) { - if (it.key() == loadedScheme) { - continue; + const QString &scheme = it.key(); + if (it.value().colors == savedConfig.value(scheme).colors) { + continue; // unchanged — leave the on-disk file alone } - if (it.value().colors == savedConfig.value(it.key()).colors) { - continue; - } - if (!ThemeManager::commitPalette(saveDir, it.key(), it.value())) { - QMessageBox::warning(this, tr("Save failed"), - tr("Could not write %1 to:\n%2").arg(PaletteConfig::fileName(it.key()), saveDir)); - return; - } - } - // Commit the active scheme last so the global colour scheme matches. - if (workingConfig[loadedScheme].colors != savedConfig.value(loadedScheme).colors) { - if (!ThemeManager::commitPalette(saveDir, loadedScheme, workingConfig[loadedScheme])) { + if (!ThemeManager::savePaletteConfig(saveDir, scheme, it.value())) { QMessageBox::warning(this, tr("Save failed"), - tr("Could not write %1 to:\n%2").arg(PaletteConfig::fileName(loadedScheme), saveDir)); + tr("Could not write %1 to:\n%2").arg(PaletteConfig::fileName(scheme), saveDir)); return; } - } else { - // No palette change but scheme may have switched -- still update global config. - ThemeConfig globalCfg = ThemeConfig::fromThemeDir(saveDir); - globalCfg.colorScheme = loadedScheme; - globalCfg.save(saveDir); } // Keep the saved snapshot in sync so Reset behaves correctly afterwards. @@ -326,6 +308,10 @@ void PaletteEditorDialog::onSave() savedConfig[it.key()] = it.value(); } + ThemeConfig globalCfg = ThemeConfig::fromThemeDir(saveDir); + globalCfg.colorScheme = loadedScheme; + globalCfg.save(saveDir); + themeManager->reloadCurrentTheme(); accept(); } diff --git a/cockatrice/src/interface/theme_manager.cpp b/cockatrice/src/interface/theme_manager.cpp index e6b4b3c7f..8986a9f00 100644 --- a/cockatrice/src/interface/theme_manager.cpp +++ b/cockatrice/src/interface/theme_manager.cpp @@ -272,19 +272,6 @@ PaletteConfig ThemeManager::loadDefaultPaletteConfig(const QString &themeDirPath return cfg; } -bool ThemeManager::commitPalette(const QString &themeDirPath, const QString &colorScheme, const PaletteConfig &cfg) -{ - if (!savePaletteConfig(themeDirPath, colorScheme, cfg)) { - return false; - } - - ThemeConfig globalCfg = ThemeConfig::fromThemeDir(themeDirPath); - globalCfg.colorScheme = colorScheme; - globalCfg.save(themeDirPath); - - return true; -} - void ThemeManager::setColorScheme(const QString &scheme) { const QString dirPath = getAvailableThemes().value(SettingsCache::instance().getThemeName()); diff --git a/cockatrice/src/interface/theme_manager.h b/cockatrice/src/interface/theme_manager.h index 79a1b6470..e3a40660b 100644 --- a/cockatrice/src/interface/theme_manager.h +++ b/cockatrice/src/interface/theme_manager.h @@ -91,10 +91,6 @@ public: // theme directory when it is absent from the resolved (user) directory. static PaletteConfig loadDefaultPaletteConfig(const QString &themeDirPath, const QString &themeName, const QString &colorScheme); - /** @brief Writes cfg to disk as the theme's palette-.toml and updates the - * theme's stored colour scheme to match. Shared by PaletteEditorDialog::onSave - * and FirstRunWizard's theme step so the two "generate + keep" paths can't drift. */ - static bool commitPalette(const QString &themeDirPath, const QString &colorScheme, const PaletteConfig &cfg); void setColorScheme(const QString &scheme); void setStyleName(const QString &styleName); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_register.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_register.cpp index 6ae8c9adb..fce99a1a7 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_register.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_register.cpp @@ -1,62 +1,18 @@ #include "dlg_register.h" #include "../../../client/settings/cache_settings.h" -#include "../server/handle_public_servers.h" -#include "../server/user/user_info_connection.h" -#include +#include #include #include -#include #include #include #include -#include -#include -#include #include #include DlgRegister::DlgRegister(QWidget *parent) : QDialog(parent) { - // ── Server picker ────────────────────────────────────────────────── - previousHostButton = new QRadioButton(tr("Known Hosts"), this); - previousHosts = new QComboBox(this); - - btnDeleteServer = new QPushButton(this); - btnDeleteServer->setIcon(QPixmap("theme:icons/remove_row")); - btnDeleteServer->setToolTip(tr("Delete the currently selected saved server")); - btnDeleteServer->setFixedWidth(30); - - connect(btnDeleteServer, &QPushButton::clicked, this, &DlgRegister::actRemoveSavedServer); - - hps = new HandlePublicServers(this); - btnRefreshServers = new QPushButton(this); - btnRefreshServers->setIcon(QPixmap("theme:icons/sync")); - btnRefreshServers->setToolTip(tr("Refresh the server list with known public servers")); - btnRefreshServers->setFixedWidth(30); - - connect(hps, &HandlePublicServers::sigPublicServersDownloadedSuccessfully, this, [this] { rebuildComboBoxList(); }); - connect(hps, &HandlePublicServers::sigPublicServersDownloadedUnsuccessfully, this, - &DlgRegister::rebuildComboBoxList); - connect(btnRefreshServers, &QPushButton::released, this, &DlgRegister::downloadThePublicServers); - - newHostButton = new QRadioButton(tr("New Host"), this); - - auto *serverPickerRow = new QHBoxLayout; - serverPickerRow->addWidget(previousHosts); - serverPickerRow->addWidget(btnDeleteServer); - serverPickerRow->addWidget(btnRefreshServers); - - auto *serverGroupLayout = new QVBoxLayout; - serverGroupLayout->addWidget(previousHostButton); - serverGroupLayout->addLayout(serverPickerRow); - serverGroupLayout->addWidget(newHostButton); - - auto *serverGroupBox = new QGroupBox(tr("Server")); - serverGroupBox->setLayout(serverGroupLayout); - - // ── Registration fields ──────────────────────────────────────────── ServersSettings &servers = SettingsCache::instance().servers(); infoLabel = new QLabel(tr("Enter your information and the information of the server you'd like to register to.\n" "Your email will be used to verify your account.")); @@ -365,28 +321,26 @@ DlgRegister::DlgRegister(QWidget *parent) : QDialog(parent) realnameEdit->setMaxLength(MAX_NAME_LENGTH); realnameLabel->setBuddy(realnameEdit); - // ── Layout ───────────────────────────────────────────────────────── auto *grid = new QGridLayout; - grid->addWidget(serverGroupBox, 0, 0, 1, 2); - grid->addWidget(infoLabel, 1, 0, 1, 2); - grid->addWidget(hostLabel, 2, 0); - grid->addWidget(hostEdit, 2, 1); - grid->addWidget(portLabel, 3, 0); - grid->addWidget(portEdit, 3, 1); - grid->addWidget(playernameLabel, 4, 0); - grid->addWidget(playernameEdit, 4, 1); - grid->addWidget(passwordLabel, 5, 0); - grid->addWidget(passwordEdit, 5, 1); - grid->addWidget(passwordConfirmationLabel, 6, 0); - grid->addWidget(passwordConfirmationEdit, 6, 1); - grid->addWidget(emailLabel, 7, 0); - grid->addWidget(emailEdit, 7, 1); - grid->addWidget(emailConfirmationLabel, 8, 0); - grid->addWidget(emailConfirmationEdit, 8, 1); - grid->addWidget(countryLabel, 10, 0); - grid->addWidget(countryEdit, 10, 1); - grid->addWidget(realnameLabel, 11, 0); - grid->addWidget(realnameEdit, 11, 1); + grid->addWidget(infoLabel, 0, 0, 1, 2); + grid->addWidget(hostLabel, 1, 0); + grid->addWidget(hostEdit, 1, 1); + grid->addWidget(portLabel, 2, 0); + grid->addWidget(portEdit, 2, 1); + grid->addWidget(playernameLabel, 3, 0); + grid->addWidget(playernameEdit, 3, 1); + grid->addWidget(passwordLabel, 4, 0); + grid->addWidget(passwordEdit, 4, 1); + grid->addWidget(passwordConfirmationLabel, 5, 0); + grid->addWidget(passwordConfirmationEdit, 5, 1); + grid->addWidget(emailLabel, 6, 0); + grid->addWidget(emailEdit, 6, 1); + grid->addWidget(emailConfirmationLabel, 7, 0); + grid->addWidget(emailConfirmationEdit, 7, 1); + grid->addWidget(countryLabel, 9, 0); + grid->addWidget(countryEdit, 9, 1); + grid->addWidget(realnameLabel, 10, 0); + grid->addWidget(realnameEdit, 10, 1); auto *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); connect(buttonBox, &QDialogButtonBox::accepted, this, &DlgRegister::actOk); @@ -398,115 +352,13 @@ DlgRegister::DlgRegister(QWidget *parent) : QDialog(parent) setLayout(mainLayout); setWindowTitle(tr("Register to server")); - setMinimumWidth(360); - - connect(previousHostButton, &QRadioButton::toggled, this, &DlgRegister::previousHostSelected); - connect(newHostButton, &QRadioButton::toggled, this, &DlgRegister::newHostSelected); - connect(previousHosts, &QComboBox::currentTextChanged, this, &DlgRegister::updateDisplayInfo); - - previousHostButton->setChecked(true); - - preRebuildComboBoxList(); -} - -DlgRegister::~DlgRegister() = default; - -void DlgRegister::downloadThePublicServers() -{ - btnRefreshServers->setDisabled(true); - previousHosts->clear(); - previousHosts->addItem(placeHolderText); - hps->downloadPublicServers(); -} - -void DlgRegister::preRebuildComboBoxList() -{ - UserConnection_Information uci; - savedHostList = uci.getServerInfo(); - - if (savedHostList.size() == 1) { - downloadThePublicServers(); - } else { - rebuildComboBoxList(); - } -} - -void DlgRegister::rebuildComboBoxList(int failure) -{ - Q_UNUSED(failure); - - previousHosts->clear(); - - UserConnection_Information uci; - savedHostList = uci.getServerInfo(); - - auto &servers = SettingsCache::instance().servers(); - QString previousHostName = servers.getPrevioushostName(); - - for (const auto &pair : savedHostList) { - const auto &tmp = pair.second; - QString saveName = tmp.getSaveName(); - if (saveName.size()) { - previousHosts->addItem(saveName); - if (saveName.compare(previousHostName) == 0) { - previousHosts->setCurrentIndex(previousHosts->count() - 1); - } - } - } - - btnRefreshServers->setDisabled(false); -} - -void DlgRegister::previousHostSelected(bool state) -{ - if (state) { - previousHosts->setDisabled(false); - btnRefreshServers->setDisabled(false); - hostEdit->setDisabled(true); - portEdit->setDisabled(true); - } -} - -void DlgRegister::newHostSelected(bool state) -{ - if (state) { - previousHosts->setDisabled(true); - btnRefreshServers->setDisabled(true); - hostEdit->setDisabled(false); - hostEdit->clear(); - hostEdit->setPlaceholderText(tr("Server URL")); - portEdit->setDisabled(false); - portEdit->clear(); - portEdit->setPlaceholderText(tr("Communication Port")); - playernameEdit->setDisabled(false); - playernameEdit->clear(); - } else { - // Rebuild the list so the previously selected host's details are - // repopulated (mirrors DlgConnect::newHostSelected). - preRebuildComboBoxList(); - } -} - -void DlgRegister::updateDisplayInfo(const QString &saveName) -{ - if (saveName.isEmpty() || saveName == placeHolderText) { - return; - } - - UserConnection_Information uci; - QStringList _data = uci.getServerInfo(saveName); - - if (_data.size() < 7) { - return; - } - - hostEdit->setText(_data.at(1)); - portEdit->setText(_data.at(2)); - playernameEdit->setText(_data.at(3)); + setFixedHeight(sizeHint().height()); + setMinimumWidth(300); } void DlgRegister::actOk() { + //! \todo This stuff should be using QValidators. if (passwordEdit->text().length() < 8) { QMessageBox::critical(this, tr("Registration Warning"), tr("Your password is too short.")); return; @@ -523,29 +375,5 @@ void DlgRegister::actOk() return; } - ServersSettings &servers = SettingsCache::instance().servers(); - - if (newHostButton->isChecked()) { - // Persist the new host so it shows up in the Connect dialog later. - // The password is never stored: the account is not verified yet. - const QString host = hostEdit->text().trimmed(); - if (!host.isEmpty()) { - servers.addNewServer(host, host, portEdit->text().trimmed(), playernameEdit->text().trimmed(), QString(), - false); - servers.setPrevioushostName(host); - } - } else { - const QString saveName = previousHosts->currentText(); - if (!saveName.isEmpty() && saveName != placeHolderText) { - servers.setPrevioushostName(saveName); - } - } - accept(); } - -void DlgRegister::actRemoveSavedServer() -{ - SettingsCache::instance().servers().removeServer(hostEdit->text()); - previousHosts->removeItem(previousHosts->currentIndex()); -} diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_register.h b/cockatrice/src/interface/widgets/dialogs/dlg_register.h index ce14eb427..abed9ff51 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_register.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_register.h @@ -1,24 +1,25 @@ +/** + * @file dlg_register.h + * @ingroup AccountDialogs + */ +//! \todo Document this file. + #ifndef DLG_REGISTER_H #define DLG_REGISTER_H #include #include #include -#include -class HandlePublicServers; class QLabel; class QPushButton; -class QRadioButton; -class UserConnection_Information; +class QCheckBox; class DlgRegister : public QDialog { Q_OBJECT public: explicit DlgRegister(QWidget *parent = nullptr); - ~DlgRegister() override; - [[nodiscard]] QString getHost() const { return hostEdit->text(); @@ -47,35 +48,15 @@ public: { return realnameEdit->text(); } - -public slots: - void downloadThePublicServers(); - private slots: void actOk(); - void previousHostSelected(bool state); - void newHostSelected(bool state); - void updateDisplayInfo(const QString &saveName); - void preRebuildComboBoxList(); - void rebuildComboBoxList(int failure = -1); - void actRemoveSavedServer(); private: - QRadioButton *newHostButton; - QRadioButton *previousHostButton; - QComboBox *previousHosts; - QPushButton *btnDeleteServer; - QPushButton *btnRefreshServers; - HandlePublicServers *hps; - QLabel *infoLabel, *hostLabel, *portLabel, *playernameLabel, *passwordLabel, *passwordConfirmationLabel, *emailLabel, *emailConfirmationLabel, *countryLabel, *realnameLabel; QLineEdit *hostEdit, *portEdit, *playernameEdit, *passwordEdit, *passwordConfirmationEdit, *emailEdit, *emailConfirmationEdit, *realnameEdit; QComboBox *countryEdit; - - QMap> savedHostList; - const QString placeHolderText = tr("Downloading..."); }; -#endif // DLG_REGISTER_H +#endif diff --git a/cockatrice/src/interface/widgets/onboarding/banner_shader_config.h b/cockatrice/src/interface/widgets/onboarding/banner_shader_config.h deleted file mode 100644 index 32f3e89c0..000000000 --- a/cockatrice/src/interface/widgets/onboarding/banner_shader_config.h +++ /dev/null @@ -1,250 +0,0 @@ -#ifndef BANNER_SHADER_CONFIG_H -#define BANNER_SHADER_CONFIG_H - -#include -#include - -/** - * Uniform values fed to brand_banner.frag, exposed to QML as the - * "bannerConfig" context property. - * - * Two independent "banks" (A/B) each carry their own mode/speed/seed so - * BrandBanner.qml can render both simultaneously and crossfade between - * them via opacity -- see frontIsA. The shared palette (colorA/colorB/ - * accent) and clock (time/aspect) apply to both banks identically, since - * only the foreground motif changes between onboarding pages, never the - * brand palette. - * - * Deliberately plain `property` (not `required property`) on the QML side - * -- a required-property shadowing bug bit the home-screen particle - * background before, and there's no reason to reintroduce that risk here. - */ -class BannerShaderConfig : public QObject -{ - Q_OBJECT - Q_PROPERTY(qreal time READ time WRITE setTime NOTIFY timeChanged) - Q_PROPERTY(qreal aspect READ aspect WRITE setAspect NOTIFY aspectChanged) - - Q_PROPERTY(qreal modeA READ modeA WRITE setModeA NOTIFY modeAChanged) - Q_PROPERTY(qreal speedA READ speedA WRITE setSpeedA NOTIFY speedAChanged) - Q_PROPERTY(qreal seedA READ seedA WRITE setSeedA NOTIFY seedAChanged) - - Q_PROPERTY(qreal modeB READ modeB WRITE setModeB NOTIFY modeBChanged) - Q_PROPERTY(qreal speedB READ speedB WRITE setSpeedB NOTIFY speedBChanged) - Q_PROPERTY(qreal seedB READ seedB WRITE setSeedB NOTIFY seedBChanged) - - Q_PROPERTY(bool frontIsA READ frontIsA WRITE setFrontIsA NOTIFY frontIsAChanged) - - Q_PROPERTY(QColor colorA READ colorA WRITE setColorA NOTIFY colorAChanged) - Q_PROPERTY(QColor colorB READ colorB WRITE setColorB NOTIFY colorBChanged) - Q_PROPERTY(QColor accent READ accent WRITE setAccent NOTIFY accentChanged) - - Q_PROPERTY(bool logoVisible READ logoVisible WRITE setLogoVisible NOTIFY logoVisibleChanged) - Q_PROPERTY(qreal logoGlow READ logoGlow WRITE setLogoGlow NOTIFY logoGlowChanged) - -public: - explicit BannerShaderConfig(QObject *parent = nullptr) : QObject(parent) - { - } - - qreal time() const - { - return m_time; - } - void setTime(qreal v) - { - if (v != m_time) { - m_time = v; - emit timeChanged(); - } - } - - qreal aspect() const - { - return m_aspect; - } - void setAspect(qreal v) - { - if (v != m_aspect) { - m_aspect = v; - emit aspectChanged(); - } - } - - qreal modeA() const - { - return m_modeA; - } - void setModeA(qreal v) - { - if (v != m_modeA) { - m_modeA = v; - emit modeAChanged(); - } - } - qreal speedA() const - { - return m_speedA; - } - void setSpeedA(qreal v) - { - if (v != m_speedA) { - m_speedA = v; - emit speedAChanged(); - } - } - qreal seedA() const - { - return m_seedA; - } - void setSeedA(qreal v) - { - if (v != m_seedA) { - m_seedA = v; - emit seedAChanged(); - } - } - - qreal modeB() const - { - return m_modeB; - } - void setModeB(qreal v) - { - if (v != m_modeB) { - m_modeB = v; - emit modeBChanged(); - } - } - qreal speedB() const - { - return m_speedB; - } - void setSpeedB(qreal v) - { - if (v != m_speedB) { - m_speedB = v; - emit speedBChanged(); - } - } - qreal seedB() const - { - return m_seedB; - } - void setSeedB(qreal v) - { - if (v != m_seedB) { - m_seedB = v; - emit seedBChanged(); - } - } - - bool frontIsA() const - { - return m_frontIsA; - } - void setFrontIsA(bool v) - { - if (v != m_frontIsA) { - m_frontIsA = v; - emit frontIsAChanged(); - } - } - - QColor colorA() const - { - return m_colorA; - } - void setColorA(const QColor &c) - { - if (c != m_colorA) { - m_colorA = c; - emit colorAChanged(); - } - } - QColor colorB() const - { - return m_colorB; - } - void setColorB(const QColor &c) - { - if (c != m_colorB) { - m_colorB = c; - emit colorBChanged(); - } - } - QColor accent() const - { - return m_accent; - } - void setAccent(const QColor &c) - { - if (c != m_accent) { - m_accent = c; - emit accentChanged(); - } - } - - bool logoVisible() const - { - return m_logoVisible; - } - void setLogoVisible(bool v) - { - if (v != m_logoVisible) { - m_logoVisible = v; - emit logoVisibleChanged(); - } - } - - qreal logoGlow() const - { - return m_logoGlow; - } - void setLogoGlow(qreal v) - { - if (v != m_logoGlow) { - m_logoGlow = v; - emit logoGlowChanged(); - } - } - -signals: - void timeChanged(); - void aspectChanged(); - void modeAChanged(); - void speedAChanged(); - void seedAChanged(); - void modeBChanged(); - void speedBChanged(); - void seedBChanged(); - void frontIsAChanged(); - void colorAChanged(); - void colorBChanged(); - void accentChanged(); - void logoVisibleChanged(); - void logoGlowChanged(); - -private: - qreal m_time = 0.0; - qreal m_aspect = 16.0 / 9.0; - - qreal m_modeA = 0.0; - qreal m_speedA = 1.0; - qreal m_seedA = 0.0; - - qreal m_modeB = 0.0; - qreal m_speedB = 1.0; - qreal m_seedB = 0.0; - - bool m_frontIsA = true; - - QColor m_colorA{0x1A, 0x1A, 0x20}; - QColor m_colorB{0x0E, 0x0E, 0x12}; - QColor m_accent{0x8B, 0xDD, 0x6B}; - - bool m_logoVisible = false; - qreal m_logoGlow = 1.0; -}; - -#endif // BANNER_SHADER_CONFIG_H diff --git a/cockatrice/src/interface/widgets/onboarding/first_run_wizard.cpp b/cockatrice/src/interface/widgets/onboarding/first_run_wizard.cpp deleted file mode 100644 index 618ac6f26..000000000 --- a/cockatrice/src/interface/widgets/onboarding/first_run_wizard.cpp +++ /dev/null @@ -1,218 +0,0 @@ -#include "first_run_wizard.h" - -#include "first_run_wizard_page.h" -#include "pages/account_setup_page.h" -#include "pages/card_database_setup_page.h" -#include "pages/finish_page.h" -#include "pages/preferences_setup_page.h" -#include "pages/theme_setup_page.h" -#include "pages/welcome_page.h" -#include "shader_banner_widget.h" -#include "step_indicator_widget.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -FirstRunWizard::FirstRunWizard(QWidget *parent) : QDialog(parent) -{ - setWindowFlag(Qt::WindowContextHelpButtonHint, false); - setMinimumSize(640, 490); - resize(720, 550); - - bannerHost = new BannerHost(this); - - titleLabel = new QLabel(this); - QFont titleFont = titleLabel->font(); - titleFont.setPointSizeF(titleFont.pointSizeF() * 1.4); - titleFont.setBold(true); - titleLabel->setFont(titleFont); - - subtitleLabel = new QLabel(this); - subtitleLabel->setWordWrap(true); - - stack = new QStackedWidget(this); - stepIndicator = new StepIndicatorWidget(this); - - backButton = new QPushButton(this); - skipButton = new QPushButton(this); - nextButton = new QPushButton(this); - nextButton->setDefault(true); - - connect(backButton, &QPushButton::clicked, this, &FirstRunWizard::goBack); - connect(skipButton, &QPushButton::clicked, this, &FirstRunWizard::skip); - connect(nextButton, &QPushButton::clicked, this, &FirstRunWizard::goNext); - - auto *headerLayout = new QVBoxLayout; - headerLayout->setContentsMargins(0, 0, 0, 0); - headerLayout->addWidget(bannerHost); - headerLayout->addSpacing(12); - headerLayout->addWidget(titleLabel); - headerLayout->addWidget(subtitleLabel); - - auto *navLayout = new QHBoxLayout; - navLayout->addWidget(backButton); - navLayout->addWidget(skipButton); - navLayout->addStretch(); - navLayout->addWidget(stepIndicator); - navLayout->addStretch(); - navLayout->addWidget(nextButton); - - auto *root = new QVBoxLayout(this); - root->addLayout(headerLayout); - root->addSpacing(8); - root->addWidget(stack, 1); - root->addSpacing(8); - root->addLayout(navLayout); - - auto *welcome = new WelcomePage(this); - auto *cardDb = new CardDatabaseSetupPage(this); - auto *theme = new ThemeSetupPage(this); - auto *account = new AccountSetupPage(this); - auto *prefs = new PreferencesSetupPage(this); - auto *finishPg = new FinishPage(this); - - cardDatabasePage = cardDb; - - connect(cardDb, &CardDatabaseSetupPage::updateRequested, this, &FirstRunWizard::cardDatabaseUpdateRequested); - connect(cardDb, &CardDatabaseSetupPage::manualSetupRequested, this, - &FirstRunWizard::manualCardDatabaseSetupRequested); - connect(account, &AccountSetupPage::registerRequested, this, &FirstRunWizard::registerRequested); - connect(account, &AccountSetupPage::connectRequested, this, &FirstRunWizard::connectRequested); - - connect(cardDb, &CardDatabaseSetupPage::advanceRequested, this, [this] { - if (stack->currentWidget() == cardDatabasePage) { - showPage(currentIndex + 1); - } - }); - - addPage(welcome); - addPage(cardDb); - addPage(theme); - addPage(account); - addPage(prefs); - addPage(finishPg); - - stepIndicator->setStepCount(pages.count()); - retranslateUi(); - showPage(0); -} - -void FirstRunWizard::addPage(FirstRunWizardPage *page) -{ - pages.append(page); - stack->addWidget(page); - connect(page, &FirstRunWizardPage::completeChanged, this, &FirstRunWizard::updateChrome); -} - -void FirstRunWizard::showPage(int index) -{ - if (index < 0 || index >= pages.count()) { - return; - } - currentIndex = index; - stack->setCurrentIndex(index); - pages[index]->initializePage(); - stepIndicator->setCurrentStep(index); - static const QList motifs = { - BannerHost::Motif::Welcome, BannerHost::Motif::CardDatabase, BannerHost::Motif::Theming, - BannerHost::Motif::Account, BannerHost::Motif::Preferences, BannerHost::Motif::Finish, - }; - if (index < motifs.size()) { - bannerHost->setMotif(motifs[index]); - } - titleLabel->setText(pages[index]->stepTitle()); - subtitleLabel->setText(pages[index]->stepSubtitle()); - subtitleLabel->setVisible(!pages[index]->stepSubtitle().isEmpty()); - updateChrome(); -} - -void FirstRunWizard::updateChrome() -{ - if (currentIndex < 0) { - return; - } - FirstRunWizardPage *page = pages[currentIndex]; - const bool isLast = (currentIndex == pages.count() - 1); - - backButton->setVisible(currentIndex > 0); - skipButton->setVisible(page->isSkippable()); - nextButton->setEnabled(page->isComplete()); - - QString customText = page->nextButtonText(); - if (!customText.isEmpty()) { - nextButton->setText(customText); - } else { - nextButton->setText(isLast ? tr("Finish") : tr("Next")); - } -} - -void FirstRunWizard::goNext() -{ - FirstRunWizardPage *page = pages[currentIndex]; - if (!page->validatePage() || !page->handleNextClick()) { - return; - } - if (currentIndex == pages.count() - 1) { - finish(); - return; - } - showPage(currentIndex + 1); -} - -void FirstRunWizard::goBack() -{ - showPage(currentIndex - 1); -} - -void FirstRunWizard::skip() -{ - showPage(currentIndex + 1); -} - -void FirstRunWizard::onCardDatabaseUpdateFinished(bool success) -{ - if (cardDatabasePage) { - cardDatabasePage->onUpdateFinished(success); - } -} - -void FirstRunWizard::finish() -{ - accept(); -} - -void FirstRunWizard::closeEvent(QCloseEvent *event) -{ - // Every step persists its own choice as it's made, so closing early - // isn't destructive -- treat it exactly like reaching the end. - QDialog::closeEvent(event); -} - -void FirstRunWizard::changeEvent(QEvent *event) -{ - if (event->type() == QEvent::LanguageChange) { - retranslateUi(); - } - QDialog::changeEvent(event); -} - -void FirstRunWizard::retranslateUi() -{ - setWindowTitle(tr("Welcome to Cockatrice")); - backButton->setText(tr("Back")); - skipButton->setText(tr("Skip")); - for (FirstRunWizardPage *page : std::as_const(pages)) { - page->retranslateUi(); - } - if (currentIndex >= 0) { - titleLabel->setText(pages[currentIndex]->stepTitle()); - subtitleLabel->setText(pages[currentIndex]->stepSubtitle()); - } - updateChrome(); -} diff --git a/cockatrice/src/interface/widgets/onboarding/first_run_wizard.h b/cockatrice/src/interface/widgets/onboarding/first_run_wizard.h deleted file mode 100644 index 2c186ef95..000000000 --- a/cockatrice/src/interface/widgets/onboarding/first_run_wizard.h +++ /dev/null @@ -1,71 +0,0 @@ -#ifndef FIRST_RUN_WIZARD_H -#define FIRST_RUN_WIZARD_H - -#include -#include - -class BannerHost; -class FirstRunWizardPage; -class StepIndicatorWidget; -class CardDatabaseSetupPage; -class QLabel; -class QPushButton; -class QStackedWidget; - -/** @brief Polished first-run onboarding flow: card database setup, theme - * selection, server account setup, and a handful of key preferences. - * - * Deliberately ignorant of network/registration/download internals -- - * pages that need them emit request signals for MainWindow to fulfill. - * Every choice is written to SettingsCache as it's made (via the pages - * themselves, same as AppearanceSettingsPage does), so "Skip" or closing - * the window never discards anything already confirmed. */ -class FirstRunWizard : public QDialog -{ - Q_OBJECT - -public: - explicit FirstRunWizard(QWidget *parent = nullptr); - -signals: - void registerRequested(); - void connectRequested(); - void cardDatabaseUpdateRequested(); - void manualCardDatabaseSetupRequested(); - -public slots: - /** @brief Forwarded from MainWindow once the background card database update process exits. */ - void onCardDatabaseUpdateFinished(bool success); - -protected: - void closeEvent(QCloseEvent *event) override; - void changeEvent(QEvent *event) override; - -private slots: - void goNext(); - void goBack(); - void skip(); - void updateChrome(); - -private: - void addPage(FirstRunWizardPage *page); - void showPage(int index); - void retranslateUi(); - void finish(); - - QStackedWidget *stack; - StepIndicatorWidget *stepIndicator; - BannerHost *bannerHost; - QLabel *titleLabel; - QLabel *subtitleLabel; - QPushButton *backButton; - QPushButton *skipButton; - QPushButton *nextButton; - - CardDatabaseSetupPage *cardDatabasePage = nullptr; - - QList pages; - int currentIndex = -1; -}; - -#endif // FIRST_RUN_WIZARD_H diff --git a/cockatrice/src/interface/widgets/onboarding/first_run_wizard_page.cpp b/cockatrice/src/interface/widgets/onboarding/first_run_wizard_page.cpp deleted file mode 100644 index 6da8958f2..000000000 --- a/cockatrice/src/interface/widgets/onboarding/first_run_wizard_page.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "first_run_wizard_page.h" diff --git a/cockatrice/src/interface/widgets/onboarding/first_run_wizard_page.h b/cockatrice/src/interface/widgets/onboarding/first_run_wizard_page.h deleted file mode 100644 index bdcd123bd..000000000 --- a/cockatrice/src/interface/widgets/onboarding/first_run_wizard_page.h +++ /dev/null @@ -1,75 +0,0 @@ -#ifndef FIRST_RUN_WIZARD_PAGE_H -#define FIRST_RUN_WIZARD_PAGE_H - -#include - -/** @brief Base class for a single step of FirstRunWizard. - * - * QWidget-based rather than QWizardPage-based: FirstRunWizard is a - * QDialog + QStackedWidget shell (not a QWizard) so it can own the - * banner/step-dot chrome that QWizard's native styles don't give us - * consistent control over. Naming mirrors OracleWizardPage for - * familiarity only -- the two hierarchies are unrelated. */ -class FirstRunWizardPage : public QWidget -{ - Q_OBJECT - -public: - explicit FirstRunWizardPage(QWidget *parent = nullptr) : QWidget(parent) - { - } - - /** @brief Called every time the page becomes visible, including navigating back to it. */ - virtual void initializePage() - { - } - - /** @brief Called before advancing past this page. Return false to block navigation; - the page itself is responsible for telling the user why. */ - virtual bool validatePage() - { - return true; - } - - /** @brief Whether Next/Finish should currently be enabled. Pages doing async work - can flip this mid-step; emit completeChanged() when they do. */ - virtual bool isComplete() const - { - return true; - } - - /** @brief Whether the wizard's "Skip" button should be offered on this page. */ - virtual bool isSkippable() const - { - return false; - } - - virtual QString stepTitle() const = 0; - virtual QString stepSubtitle() const - { - return {}; - } - - /** @brief Override to replace the "Next"/"Finish" button text on this page. - Return an empty string to use the default label. */ - virtual QString nextButtonText() const - { - return {}; - } - - /** @brief Called when the user presses the Next button. Return true to allow - advancing to the next page, false to stay on this page (e.g. to - trigger an async action first). */ - virtual bool handleNextClick() - { - return true; - } - - virtual void retranslateUi() = 0; - -signals: - void completeChanged(); - void advanceRequested(); -}; - -#endif // FIRST_RUN_WIZARD_PAGE_H diff --git a/cockatrice/src/interface/widgets/onboarding/pages/account_setup_page.cpp b/cockatrice/src/interface/widgets/onboarding/pages/account_setup_page.cpp deleted file mode 100644 index 2107ea8bf..000000000 --- a/cockatrice/src/interface/widgets/onboarding/pages/account_setup_page.cpp +++ /dev/null @@ -1,56 +0,0 @@ -#include "account_setup_page.h" - -#include -#include -#include - -AccountSetupPage::AccountSetupPage(QWidget *parent) : FirstRunWizardPage(parent) -{ - bodyLabel = new QLabel(this); - bodyLabel->setWordWrap(true); - bodyLabel->setAlignment(Qt::AlignCenter); - - registerButton = new QPushButton(this); - connectButton = new QPushButton(this); - skipHintLabel = new QLabel(this); - skipHintLabel->setWordWrap(true); - skipHintLabel->setAlignment(Qt::AlignCenter); - - connect(registerButton, &QPushButton::clicked, this, &AccountSetupPage::registerRequested); - connect(connectButton, &QPushButton::clicked, this, &AccountSetupPage::connectRequested); - - auto *layout = new QVBoxLayout(this); - layout->addStretch(); - layout->addWidget(bodyLabel); - layout->addSpacing(16); - layout->addWidget(registerButton, 0, Qt::AlignHCenter); - layout->addWidget(connectButton, 0, Qt::AlignHCenter); - layout->addSpacing(16); - layout->addWidget(skipHintLabel); - layout->addStretch(); - - retranslateUi(); -} - -bool AccountSetupPage::isSkippable() const -{ - return true; -} - -QString AccountSetupPage::stepTitle() const -{ - return tr("Join a Server"); -} - -QString AccountSetupPage::stepSubtitle() const -{ - return tr("Optional — you can always do this later from the menu."); -} - -void AccountSetupPage::retranslateUi() -{ - bodyLabel->setText(tr("Playing online needs a server account.")); - registerButton->setText(tr("Register a new account…")); - connectButton->setText(tr("I already have one — Connect…")); - skipHintLabel->setText(tr("Just want to play locally? Skip this and connect whenever you're ready.")); -} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/onboarding/pages/account_setup_page.h b/cockatrice/src/interface/widgets/onboarding/pages/account_setup_page.h deleted file mode 100644 index 0d9b76699..000000000 --- a/cockatrice/src/interface/widgets/onboarding/pages/account_setup_page.h +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef ACCOUNT_SETUP_PAGE_H -#define ACCOUNT_SETUP_PAGE_H - -#include "../first_run_wizard_page.h" - -class QLabel; -class QPushButton; - -/** @brief First-run account step. Does NOT embed DlgRegister's fields: they exist - * to be handed to ConnectionController's network registration flow, which - * this wizard has no visibility into. Reimplementing the fields here - * without that wiring would look functional and silently do nothing -- - * worse than reuse. So: a friendly landing spot that opens the *existing* - * DlgRegister / connect flow via signals FirstRunWizard forwards. */ -class AccountSetupPage : public FirstRunWizardPage -{ - Q_OBJECT - -public: - explicit AccountSetupPage(QWidget *parent = nullptr); - - bool isSkippable() const override; - QString stepTitle() const override; - QString stepSubtitle() const override; - void retranslateUi() override; - -signals: - void registerRequested(); - void connectRequested(); - -private: - QLabel *bodyLabel; - QPushButton *registerButton; - QPushButton *connectButton; - QLabel *skipHintLabel; -}; - -#endif // ACCOUNT_SETUP_PAGE_H diff --git a/cockatrice/src/interface/widgets/onboarding/pages/card_database_setup_page.cpp b/cockatrice/src/interface/widgets/onboarding/pages/card_database_setup_page.cpp deleted file mode 100644 index 12116de7a..000000000 --- a/cockatrice/src/interface/widgets/onboarding/pages/card_database_setup_page.cpp +++ /dev/null @@ -1,314 +0,0 @@ -#include "card_database_setup_page.h" - -#include "../../client/settings/cache_settings.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -CardDatabaseSetupPage::CardDatabaseSetupPage(QWidget *parent) : FirstRunWizardPage(parent) -{ - statusLabel = new QLabel(this); - statusLabel->setWordWrap(true); - statusLabel->setAlignment(Qt::AlignCenter); - - progressBar = new QProgressBar(this); - progressBar->setRange(0, 0); - progressBar->setTextVisible(false); - progressBar->setFixedWidth(280); - - retryButton = new QPushButton(this); - manualButton = new QPushButton(this); - - connect(retryButton, &QPushButton::clicked, this, [this] { - setState(State::Running); - emit updateRequested(); - }); - connect(manualButton, &QPushButton::clicked, this, &CardDatabaseSetupPage::manualSetupRequested); - - // ── Advanced: custom download source ─────────────────────────────── - advancedToggleButton = new QPushButton(this); - advancedToggleButton->setCheckable(true); - advancedToggleButton->setChecked(false); - advancedToggleButton->setFlat(true); - advancedToggleButton->setStyleSheet("QPushButton { text-align: left; padding: 5px 12px; font-weight: bold; }" - "QPushButton:checked { }"); - - advancedPanel = new QWidget(this); - advancedPanel->setVisible(false); - - urlLineEdit = new QLineEdit(advancedPanel); - urlHintLabel = new QLabel(advancedPanel); - urlHintLabel->setWordWrap(true); - - restoreDefaultUrlButton = new QPushButton(advancedPanel); - applyAndRetryButton = new QPushButton(advancedPanel); - - connect(advancedToggleButton, &QPushButton::toggled, this, &CardDatabaseSetupPage::onToggleAdvanced); - connect(restoreDefaultUrlButton, &QPushButton::clicked, this, &CardDatabaseSetupPage::onRestoreDefaultUrl); - connect(applyAndRetryButton, &QPushButton::clicked, this, &CardDatabaseSetupPage::onApplyCustomUrl); - - auto *advancedButtonRow = new QHBoxLayout; - advancedButtonRow->addWidget(restoreDefaultUrlButton); - advancedButtonRow->addStretch(); - advancedButtonRow->addWidget(applyAndRetryButton); - - auto *advancedLayout = new QVBoxLayout(advancedPanel); - advancedLayout->setContentsMargins(12, 4, 12, 4); - advancedLayout->addWidget(urlLineEdit); - advancedLayout->addWidget(urlHintLabel); - advancedLayout->addLayout(advancedButtonRow); - - // ── Startup card update check ─────────────────────────────────────── - auto &upd = SettingsCache::instance().updates(); - - const auto updateBehavior = [this] { - auto &u = SettingsCache::instance().updates(); - int idx = startupBehaviorCombo->currentIndex(); - u.setStartupCardUpdateCheckPromptForUpdate(idx == 1); - u.setStartupCardUpdateCheckAlwaysUpdate(idx == 2); - }; - - startupBehaviorLabel = new QLabel(this); - startupBehaviorCombo = new QComboBox(this); - startupBehaviorCombo->addItem(QString()); // placeholder, filled in retranslateUi - startupBehaviorCombo->addItem(QString()); - startupBehaviorCombo->addItem(QString()); - if (upd.getStartupCardUpdateCheckPromptForUpdate()) { - startupBehaviorCombo->setCurrentIndex(1); - } else if (upd.getStartupCardUpdateCheckAlwaysUpdate()) { - startupBehaviorCombo->setCurrentIndex(2); - } else { - startupBehaviorCombo->setCurrentIndex(0); - } - connect(startupBehaviorCombo, QOverload::of(&QComboBox::currentIndexChanged), this, updateBehavior); - - checkIntervalLabel = new QLabel(this); - checkIntervalSpinBox = new QSpinBox(this); - checkIntervalSpinBox->setMinimum(1); - checkIntervalSpinBox->setMaximum(30); - checkIntervalSpinBox->setValue(upd.getCardUpdateCheckInterval()); - connect(checkIntervalSpinBox, QOverload::of(&QSpinBox::valueChanged), &upd, - &UpdatesSettings::setCardUpdateCheckInterval); - - auto *checkGrid = new QGridLayout; - checkGrid->addWidget(startupBehaviorLabel, 0, 0); - checkGrid->addWidget(startupBehaviorCombo, 0, 1); - checkGrid->addWidget(checkIntervalLabel, 1, 0); - checkGrid->addWidget(checkIntervalSpinBox, 1, 1); - - auto *layout = new QVBoxLayout(this); - layout->addStretch(); - layout->addWidget(statusLabel); - layout->addSpacing(12); - layout->addWidget(progressBar, 0, Qt::AlignHCenter); - layout->addSpacing(12); - layout->addWidget(retryButton, 0, Qt::AlignHCenter); - layout->addWidget(manualButton, 0, Qt::AlignHCenter); - layout->addSpacing(16); - layout->addWidget(advancedToggleButton); - layout->addWidget(advancedPanel); - layout->addSpacing(8); - layout->addLayout(checkGrid); - layout->addStretch(); - - retranslateUi(); -} - -bool CardDatabaseSetupPage::alreadyHaveDatabase() const -{ - return CardDatabaseManager::getInstance()->getCardList().count() > 0; -} - -QString CardDatabaseSetupPage::oracleSettingsFilePath() const -{ - return SettingsCache::instance().getSettingsPath() + "oracle.ini"; -} - -QString CardDatabaseSetupPage::readCustomUrl() const -{ - QSettings oracleSettings(oracleSettingsFilePath(), QSettings::IniFormat); - return oracleSettings.value("allsetsurl").toString(); -} - -void CardDatabaseSetupPage::writeCustomUrl(const QString &url) -{ - QSettings oracleSettings(oracleSettingsFilePath(), QSettings::IniFormat); - if (url.isEmpty()) { - oracleSettings.remove("allsetsurl"); - } else { - oracleSettings.setValue("allsetsurl", url); - } -} - -void CardDatabaseSetupPage::initializePage() -{ - urlLineEdit->setText(readCustomUrl()); - - if (state != State::NotStarted) { - return; - } - - if (alreadyHaveDatabase()) { - setState(State::Succeeded); - return; - } - - // Don't auto-download — wait for the user to press "Download". - setState(State::NotStarted); - statusLabel->setText(tr("Press Download to fetch the card database, or Skip to do it later.")); -} - -void CardDatabaseSetupPage::onUpdateFinished(bool success) -{ - setState(success ? State::Succeeded : State::Failed); - if (success) { - emit advanceRequested(); - } -} - -QString CardDatabaseSetupPage::nextButtonText() const -{ - return state == State::NotStarted ? tr("Download") : QString(); -} - -bool CardDatabaseSetupPage::handleNextClick() -{ - if (state == State::NotStarted) { - setState(State::Running); - emit updateRequested(); - return false; - } - return true; -} - -void CardDatabaseSetupPage::onToggleAdvanced(bool open) -{ - advancedToggleButton->setText(open ? tr("▼ Advanced: custom download source") - : tr("▶ Advanced: custom download source")); - advancedPanel->setVisible(open); - - QWidget *wizardWindow = window(); - if (!wizardWindow) { - return; - } - - if (open) { - windowSizeBeforeExpansion = wizardWindow->size(); - QTimer::singleShot(0, this, [wizardWindow] { - wizardWindow->resize(wizardWindow->size().expandedTo(wizardWindow->sizeHint())); - }); - } else { - QTimer::singleShot(0, this, [this, wizardWindow] { - wizardWindow->resize(wizardWindow->size().boundedTo(windowSizeBeforeExpansion)); - }); - } -} - -void CardDatabaseSetupPage::onApplyCustomUrl() -{ - const QString text = urlLineEdit->text().trimmed(); - - if (!text.isEmpty()) { - const QUrl url = QUrl::fromUserInput(text); - if (!url.isValid()) { - QMessageBox::warning(this, tr("Invalid URL"), - tr("That doesn't look like a valid URL. Double-check it and try again, " - "or clear the field to use the default source.")); - return; - } - } - - writeCustomUrl(text); - setState(State::Running); - emit updateRequested(); -} - -void CardDatabaseSetupPage::onRestoreDefaultUrl() -{ - urlLineEdit->clear(); - writeCustomUrl(QString()); -} - -void CardDatabaseSetupPage::setState(State newState) -{ - state = newState; - - progressBar->setVisible(state == State::Running); - retryButton->setVisible(state == State::Failed); - manualButton->setVisible(state == State::Failed); - applyAndRetryButton->setEnabled(state != State::Running); - - switch (state) { - case State::NotStarted: - statusLabel->setText(tr("Press Download to fetch the card database, or Skip to do it later.")); - break; - case State::Running: - statusLabel->setText(tr("Downloading the latest card database…")); - break; - case State::Succeeded: - statusLabel->setText(tr("Card database ready ✓")); - break; - case State::Failed: - statusLabel->setText( - tr("Couldn't download the card database automatically. Check your connection and retry, " - "set it up manually, or skip this for now — you can do it later from the Card Database menu.")); - break; - } - - emit completeChanged(); -} - -bool CardDatabaseSetupPage::isComplete() const -{ - return state != State::Running; -} - -bool CardDatabaseSetupPage::isSkippable() const -{ - return state != State::Succeeded; -} - -QString CardDatabaseSetupPage::stepTitle() const -{ - return tr("Card Database"); -} - -QString CardDatabaseSetupPage::stepSubtitle() const -{ - return tr("Cockatrice needs card data to know what you're playing with."); -} - -void CardDatabaseSetupPage::retranslateUi() -{ - retryButton->setText(tr("Retry")); - manualButton->setText(tr("Set up manually…")); - - onToggleAdvanced(advancedToggleButton->isChecked()); - urlLineEdit->setPlaceholderText(tr("Leave blank to use the default source")); - urlHintLabel->setText(tr("Only change this if you know you need a mirror or a custom card data source.")); - restoreDefaultUrlButton->setText(tr("Restore default")); - applyAndRetryButton->setText(tr("Apply && retry")); - - startupBehaviorLabel->setText(tr("Check for card database updates on startup")); - startupBehaviorCombo->setItemText(0, tr("Don't check")); - startupBehaviorCombo->setItemText(1, tr("Prompt for update")); - startupBehaviorCombo->setItemText(2, tr("Always update in the background")); - - checkIntervalLabel->setText(tr("Check for card database updates every")); - checkIntervalSpinBox->setSuffix(tr(" days")); - - setState(state); -} diff --git a/cockatrice/src/interface/widgets/onboarding/pages/card_database_setup_page.h b/cockatrice/src/interface/widgets/onboarding/pages/card_database_setup_page.h deleted file mode 100644 index 0461d11d5..000000000 --- a/cockatrice/src/interface/widgets/onboarding/pages/card_database_setup_page.h +++ /dev/null @@ -1,79 +0,0 @@ -#ifndef CARD_DATABASE_SETUP_PAGE_H -#define CARD_DATABASE_SETUP_PAGE_H - -#include "../first_run_wizard_page.h" - -#include - -class QComboBox; -class QLabel; -class QLineEdit; -class QProgressBar; -class QPushButton; -class QSpinBox; -class QWidget; - -class CardDatabaseSetupPage : public FirstRunWizardPage -{ - Q_OBJECT - -public: - explicit CardDatabaseSetupPage(QWidget *parent = nullptr); - - void initializePage() override; - bool isComplete() const override; - bool isSkippable() const override; - QString stepTitle() const override; - QString stepSubtitle() const override; - QString nextButtonText() const override; - bool handleNextClick() override; - void retranslateUi() override; - - void onUpdateFinished(bool success); - -signals: - void updateRequested(); - void manualSetupRequested(); - -private: - enum class State - { - NotStarted, - Running, - Succeeded, - Failed, - }; - - void setState(State newState); - bool alreadyHaveDatabase() const; - - QString oracleSettingsFilePath() const; - QString readCustomUrl() const; - void writeCustomUrl(const QString &url); - - void onToggleAdvanced(bool open); - void onApplyCustomUrl(); - void onRestoreDefaultUrl(); - - QLabel *statusLabel; - QProgressBar *progressBar; - QPushButton *retryButton; - QPushButton *manualButton; - - QPushButton *advancedToggleButton; - QWidget *advancedPanel; - QLineEdit *urlLineEdit; - QLabel *urlHintLabel; - QPushButton *restoreDefaultUrlButton; - QPushButton *applyAndRetryButton; - - QLabel *startupBehaviorLabel; - QComboBox *startupBehaviorCombo; - QLabel *checkIntervalLabel; - QSpinBox *checkIntervalSpinBox; - - State state = State::NotStarted; - QSize windowSizeBeforeExpansion; -}; - -#endif // CARD_DATABASE_SETUP_PAGE_H diff --git a/cockatrice/src/interface/widgets/onboarding/pages/finish_page.cpp b/cockatrice/src/interface/widgets/onboarding/pages/finish_page.cpp deleted file mode 100644 index 4205fa532..000000000 --- a/cockatrice/src/interface/widgets/onboarding/pages/finish_page.cpp +++ /dev/null @@ -1,30 +0,0 @@ -#include "finish_page.h" - -#include -#include - -FinishPage::FinishPage(QWidget *parent) : FirstRunWizardPage(parent) -{ - bodyLabel = new QLabel(this); - bodyLabel->setWordWrap(true); - bodyLabel->setAlignment(Qt::AlignCenter); - - auto *layout = new QVBoxLayout(this); - layout->addStretch(); - layout->addWidget(bodyLabel); - layout->addStretch(); - - retranslateUi(); -} - -QString FinishPage::stepTitle() const -{ - return tr("You're All Set"); -} - -void FinishPage::retranslateUi() -{ - bodyLabel->setText( - tr("That's everything for now. Jump into Settings any time to change your mind about any of this.\n\n" - "Have fun!")); -} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/onboarding/pages/finish_page.h b/cockatrice/src/interface/widgets/onboarding/pages/finish_page.h deleted file mode 100644 index 40ebc6ed0..000000000 --- a/cockatrice/src/interface/widgets/onboarding/pages/finish_page.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef FINISH_PAGE_H -#define FINISH_PAGE_H - -#include "../first_run_wizard_page.h" - -class QLabel; - -class FinishPage : public FirstRunWizardPage -{ - Q_OBJECT - -public: - explicit FinishPage(QWidget *parent = nullptr); - - QString stepTitle() const override; - void retranslateUi() override; - -private: - QLabel *bodyLabel; -}; - -#endif // FINISH_PAGE_H diff --git a/cockatrice/src/interface/widgets/onboarding/pages/preferences_setup_page.cpp b/cockatrice/src/interface/widgets/onboarding/pages/preferences_setup_page.cpp deleted file mode 100644 index fceb34deb..000000000 --- a/cockatrice/src/interface/widgets/onboarding/pages/preferences_setup_page.cpp +++ /dev/null @@ -1,173 +0,0 @@ -#include "preferences_setup_page.h" - -#include "../../client/settings/cache_settings.h" -#include "../../client/sound_engine.h" -#include "libcockatrice/settings/interface_settings.h" -#include "libcockatrice/settings/sound_settings.h" -#include "libcockatrice/settings/tabs_settings.h" - -#include -#include -#include -#include -#include -#include -#include - -namespace -{ -// The server destinations are omitted: during first run their tabs are not -// open yet, and the wizard offers no way to fill in the server/room details. -QList wizardStartupTabOrder() -{ - return {StartupTabHome, StartupTabVisualDeckStorage, StartupTabDeckStorage, - StartupTabReplays, StartupTabDeckEditor, StartupTabVisualDeckEditor}; -} -} // namespace - -PreferencesSetupPage::PreferencesSetupPage(QWidget *parent) : FirstRunWizardPage(parent) -{ - auto *content = new QWidget; - auto *contentLayout = new QVBoxLayout(content); - - gameplayGroup = new QGroupBox(content); - auto *gameplayLayout = new QVBoxLayout(gameplayGroup); - contentLayout->addWidget(gameplayGroup); - doubleClickToPlayCheckBox = new QCheckBox(gameplayGroup); - horizontalHandCheckBox = new QCheckBox(gameplayGroup); - playToStackCheckBox = new QCheckBox(gameplayGroup); - gameplayLayout->addWidget(doubleClickToPlayCheckBox); - gameplayLayout->addWidget(horizontalHandCheckBox); - gameplayLayout->addWidget(playToStackCheckBox); - - notificationsGroup = new QGroupBox(content); - auto *notificationsLayout = new QVBoxLayout(notificationsGroup); - contentLayout->addWidget(notificationsGroup); - notificationsEnabledCheckBox = new QCheckBox(notificationsGroup); - soundEnabledCheckBox = new QCheckBox(notificationsGroup); - notificationsLayout->addWidget(notificationsEnabledCheckBox); - notificationsLayout->addWidget(soundEnabledCheckBox); - - startupGroup = new QGroupBox(content); - auto *startupForm = new QFormLayout(startupGroup); - contentLayout->addWidget(startupGroup); - startupTabLabel = new QLabel(startupGroup); - startupTabSelector = new QComboBox(startupGroup); - startupTabSelector->setSizeAdjustPolicy(QComboBox::AdjustToContents); - for (StartupTab tab : wizardStartupTabOrder()) { - startupTabSelector->addItem(QString(), tab); // texts set in retranslateUi - } - startupForm->addRow(startupTabLabel, startupTabSelector); - - contentLayout->addStretch(); - - auto *scrollArea = new QScrollArea(this); - scrollArea->setWidget(content); - scrollArea->setWidgetResizable(true); - scrollArea->setFrameShape(QFrame::NoFrame); - - auto *layout = new QVBoxLayout(this); - layout->addWidget(scrollArea); - - SettingsCache &settings = SettingsCache::instance(); - - connect(doubleClickToPlayCheckBox, &QCheckBox::toggled, &settings.userInterface(), - &InterfaceSettings::setDoubleClickToPlay); - connect(horizontalHandCheckBox, &QCheckBox::toggled, &settings.userInterface(), - &InterfaceSettings::setHorizontalHand); - connect(playToStackCheckBox, &QCheckBox::toggled, &settings.userInterface(), &InterfaceSettings::setPlayToStack); - - connect(notificationsEnabledCheckBox, &QCheckBox::toggled, &settings.userInterface(), - &InterfaceSettings::setNotificationsEnabled); - connect(soundEnabledCheckBox, &QCheckBox::toggled, &settings.sound(), &SoundSettings::setSoundEnabled); - connect(soundEnabledCheckBox, &QCheckBox::toggled, soundEngine, &SoundEngine::testSound); - - connect(startupTabSelector, QOverload::of(&QComboBox::currentIndexChanged), this, [this](int index) { - if (index < 0) { - return; - } - SettingsCache::instance().tabs().setStartupTabIndex(startupTabSelector->itemData(index).toInt()); - }); - - retranslateUi(); -} - -void PreferencesSetupPage::initializePage() -{ - SettingsCache &settings = SettingsCache::instance(); - - doubleClickToPlayCheckBox->setChecked(settings.userInterface().getDoubleClickToPlay()); - horizontalHandCheckBox->setChecked(settings.userInterface().getHorizontalHand()); - playToStackCheckBox->setChecked(settings.userInterface().getPlayToStack()); - - notificationsEnabledCheckBox->setChecked(settings.userInterface().getNotificationsEnabled()); - soundEnabledCheckBox->setChecked(settings.sound().getSoundEnabled()); - - startupTabSelector->setCurrentIndex(startupTabSelector->findData(settings.tabs().getStartupTabIndex())); -} - -bool PreferencesSetupPage::isSkippable() const -{ - return true; -} - -QString PreferencesSetupPage::stepTitle() const -{ - return tr("A Few Preferences"); -} - -QString PreferencesSetupPage::stepSubtitle() const -{ - return tr("Defaults are fine — tweak these now or from Settings anytime."); -} - -void PreferencesSetupPage::retranslateUi() -{ - gameplayGroup->setTitle(tr("Gameplay")); - doubleClickToPlayCheckBox->setText(tr("Double-click cards to play them")); - doubleClickToPlayCheckBox->setToolTip(tr("When disabled, a single click plays the selected card onto the table.")); - horizontalHandCheckBox->setText(tr("Display hand horizontally")); - horizontalHandCheckBox->setToolTip( - tr("Shows your hand as a row along the bottom of the table instead of a column beside it.")); - playToStackCheckBox->setText(tr("Play all nonlands onto the stack by default")); - playToStackCheckBox->setToolTip( - tr("Cards you play appear on the stack so other players can respond to them, as in a tabletop game.")); - - notificationsGroup->setTitle(tr("Notifications && Sound")); - notificationsEnabledCheckBox->setText(tr("Show desktop notifications")); - soundEnabledCheckBox->setText(tr("Play sound effects")); - - startupGroup->setTitle(tr("Startup")); - startupTabLabel->setText(tr("Startup tab:")); - const QList tabs = wizardStartupTabOrder(); - for (int i = 0; i < tabs.size(); ++i) { - QString name; - switch (tabs[i]) { - case StartupTabHome: - name = tr("Home"); - break; - case StartupTabVisualDeckStorage: - name = tr("Visual Deck Storage"); - break; - case StartupTabDeckStorage: - name = tr("Deck Storage"); - break; - case StartupTabReplays: - name = tr("Game Replays"); - break; - case StartupTabDeckEditor: - name = tr("Deck Editor"); - break; - case StartupTabVisualDeckEditor: - name = tr("Visual Deck Editor"); - break; - case StartupTabServer: - name = tr("Server"); - break; - case StartupTabServerRoom: - name = tr("Server Room"); - break; - } - startupTabSelector->setItemText(i, name); - } -} diff --git a/cockatrice/src/interface/widgets/onboarding/pages/preferences_setup_page.h b/cockatrice/src/interface/widgets/onboarding/pages/preferences_setup_page.h deleted file mode 100644 index eed1b3aed..000000000 --- a/cockatrice/src/interface/widgets/onboarding/pages/preferences_setup_page.h +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef PREFERENCES_SETUP_PAGE_H -#define PREFERENCES_SETUP_PAGE_H - -#include "../first_run_wizard_page.h" - -class QCheckBox; -class QComboBox; -class QGroupBox; -class QLabel; - -/** @brief A curated subset of settings for the user to adjust. - **/ -class PreferencesSetupPage : public FirstRunWizardPage -{ - Q_OBJECT - -public: - explicit PreferencesSetupPage(QWidget *parent = nullptr); - - void initializePage() override; - bool isSkippable() const override; - QString stepTitle() const override; - QString stepSubtitle() const override; - void retranslateUi() override; - -private: - QGroupBox *gameplayGroup; - QCheckBox *doubleClickToPlayCheckBox; - QCheckBox *horizontalHandCheckBox; - QCheckBox *playToStackCheckBox; - - QGroupBox *notificationsGroup; - QCheckBox *notificationsEnabledCheckBox; - QCheckBox *soundEnabledCheckBox; - - QGroupBox *startupGroup; - QLabel *startupTabLabel; - QComboBox *startupTabSelector; -}; - -#endif // PREFERENCES_SETUP_PAGE_H diff --git a/cockatrice/src/interface/widgets/onboarding/pages/theme_setup_page.cpp b/cockatrice/src/interface/widgets/onboarding/pages/theme_setup_page.cpp deleted file mode 100644 index 3293b19ac..000000000 --- a/cockatrice/src/interface/widgets/onboarding/pages/theme_setup_page.cpp +++ /dev/null @@ -1,231 +0,0 @@ -#include "theme_setup_page.h" - -#include "../../client/settings/cache_settings.h" -#include "../../interface/palette_editor/palette_generator.h" -#include "../../interface/palette_editor/quick_setup_panel.h" -#include "../../interface/theme_manager.h" -#include "../../interface/widgets/general/background_sources.h" -#include "libcockatrice/settings/appearance_settings.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -ThemeSetupPage::ThemeSetupPage(QWidget *parent) : FirstRunWizardPage(parent) -{ - themeCombo = new QComboBox(this); - schemeCombo = new QComboBox(this); - schemeCombo->addItem(tr("Light"), QStringLiteral("Light")); - schemeCombo->addItem(tr("Dark"), QStringLiteral("Dark")); -#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) - schemeCombo->addItem(tr("Match system"), QStringLiteral("System")); -#endif - - quickSetupPanel = new QuickSetupPanel(this); - - connect(themeCombo, QOverload::of(&QComboBox::currentIndexChanged), this, &ThemeSetupPage::onThemeChanged); - connect(schemeCombo, QOverload::of(&QComboBox::currentIndexChanged), this, &ThemeSetupPage::onSchemeChanged); - connect(quickSetupPanel, &QuickSetupPanel::valueChanged, this, &ThemeSetupPage::onGenerateFromAccent); - - homeTabBackgroundCombo = new QComboBox(this); - for (const auto &entry : BackgroundSources::all()) { - homeTabBackgroundCombo->addItem(QObject::tr(entry.trKey), QVariant::fromValue(entry.type)); - } - connect(homeTabBackgroundCombo, QOverload::of(&QComboBox::currentIndexChanged), this, - &ThemeSetupPage::onHomeTabBackgroundChanged); - - // Keep the scheme combo honest when the *theme* changes underneath it - // (switching theme reloads that theme's own stored colorScheme), and - // opportunistically seed a palette for themes that ship none at all. - // Mirrors AppearanceSettingsPage's identical listener for the combo-sync - // half of this. - connect(themeManager, &ThemeManager::themeChanged, this, [this] { - const QString newDir = themeManager->getAvailableThemes().value(SettingsCache::instance().getThemeName()); - const ThemeConfig cfg = ThemeConfig::fromThemeDir(newDir); - const QString current = cfg.colorScheme; - - schemeCombo->blockSignals(true); - const int idx = schemeCombo->findData(current); - schemeCombo->setCurrentIndex(idx >= 0 ? idx : 0); - schemeCombo->blockSignals(false); - - maybeAutoGeneratePalette(); - }); - - auto *form = new QFormLayout; - form->addRow(tr("Theme:"), themeCombo); - form->addRow(tr("Appearance:"), schemeCombo); - form->addRow(tr("Home screen background:"), homeTabBackgroundCombo); - - accentGroup = new QGroupBox(this); - auto *accentLayout = new QVBoxLayout(accentGroup); - accentLayout->addWidget(quickSetupPanel); - - auto *layout = new QVBoxLayout(this); - layout->addLayout(form); - layout->addWidget(accentGroup); - layout->addStretch(); - - retranslateUi(); -} - -void ThemeSetupPage::initializePage() -{ - themeCombo->blockSignals(true); - themeCombo->clear(); - const QString currentTheme = SettingsCache::instance().getThemeName(); - for (const QString &name : themeManager->getAvailableThemes().keys()) { - themeCombo->addItem(name); - } - const int idx = themeCombo->findText(currentTheme); - themeCombo->setCurrentIndex(idx >= 0 ? idx : 0); - themeCombo->blockSignals(false); - - const QString dirPath = themeManager->getAvailableThemes().value(SettingsCache::instance().getThemeName()); - const ThemeConfig cfg = ThemeConfig::fromThemeDir(dirPath); - schemeCombo->blockSignals(true); - const int schemeIdx = schemeCombo->findData(cfg.colorScheme); - schemeCombo->setCurrentIndex(schemeIdx >= 0 ? schemeIdx : 0); - schemeCombo->blockSignals(false); - - homeTabBackgroundCombo->blockSignals(true); - QString homeTabSource = SettingsCache::instance().appearance().getHomeTabBackgroundSource(); - int homeTabIdx = homeTabBackgroundCombo->findData(BackgroundSources::fromId(homeTabSource)); - homeTabBackgroundCombo->setCurrentIndex(homeTabIdx >= 0 ? homeTabIdx : 0); - homeTabBackgroundCombo->blockSignals(false); - - // Opening the page must not touch the running application's palette: - // previews and auto-generation only happen in response to the user - // actually changing a control, never on mere page visibility. - paletteDirty = false; -} - -QString ThemeSetupPage::currentScheme() const -{ - return schemeCombo->currentData().toString(); -} - -QString ThemeSetupPage::resolvedScheme() const -{ - const QString scheme = currentScheme(); - if (scheme.isEmpty() || scheme == QStringLiteral("System")) { - return themeManager->isDarkMode(themeManager->getCurrentThemePath()) ? "Dark" : "Light"; - } - return scheme; -} - -void ThemeSetupPage::onThemeChanged(int index) -{ - if (index < 0) { - return; - } - paletteDirty = false; - SettingsCache::instance().setThemeName(themeCombo->itemText(index)); - // Scheme-combo sync and auto-generation both happen via the - // ThemeManager::themeChanged listener above, triggered by setThemeName. -} - -void ThemeSetupPage::onSchemeChanged() -{ - themeManager->setColorScheme(currentScheme()); -} - -void ThemeSetupPage::onHomeTabBackgroundChanged(int index) -{ - if (index < 0) { - return; - } - auto type = homeTabBackgroundCombo->currentData().value(); - SettingsCache::instance().appearance().setHomeTabBackgroundSource(BackgroundSources::toId(type)); -} - -void ThemeSetupPage::onGenerateFromAccent(const QColor &accent, int intensity) -{ - PaletteConfig cfg = PaletteGenerator::fromAccent(accent, intensity, resolvedScheme()); - themeManager->previewPalette(cfg, resolvedScheme()); - paletteDirty = true; -} - -void ThemeSetupPage::maybeAutoGeneratePalette() -{ - const QString dirPath = themeManager->getAvailableThemes().value(SettingsCache::instance().getThemeName()); - const QString scheme = resolvedScheme(); - - if (PaletteConfig::fromScheme(dirPath, scheme).hasPalette() || - PaletteConfig::fromDefault(dirPath, scheme).hasPalette()) { - return; // theme already has something real to show -- leave it alone - } - - // The theme+scheme combination has nothing saved and nothing shipped, and - // the user just switched to it. Rather than leaving a flat, unstyled look, - // seed one from whatever accent QuickSetupPanel currently holds and mark - // it dirty so it's written to disk if the user moves on. Only ever reached - // through user interaction (theme/scheme change, accent drag) -- never on - // page open. - PaletteConfig generated = - PaletteGenerator::fromAccent(quickSetupPanel->accentColor(), quickSetupPanel->intensity(), scheme); - themeManager->previewPalette(generated, scheme); - paletteDirty = true; -} - -bool ThemeSetupPage::validatePage() -{ - if (paletteDirty) { - const QString scheme = resolvedScheme(); - PaletteConfig cfg = - PaletteGenerator::fromAccent(quickSetupPanel->accentColor(), quickSetupPanel->intensity(), scheme); - if (!ThemeManager::commitPalette(writableThemeDir(), scheme, cfg)) { - QMessageBox::warning(this, tr("Save failed"), - tr("Could not write the theme palette to:\n%1").arg(writableThemeDir())); - return false; - } - themeManager->reloadCurrentTheme(); - } - return true; -} - -QString ThemeSetupPage::writableThemeDir() const -{ - // Built-in themes resolve to the read-only system themes directory; - // palette edits must go to the user themes directory instead, exactly - // as PaletteEditorDialog does. - const QString dirPath = themeManager->getAvailableThemes().value(SettingsCache::instance().getThemeName()); - if (!dirPath.isEmpty()) { - const QString probe = QDir(dirPath).absoluteFilePath(".cockatrice_write_test"); - QFile f(probe); - if (f.open(QIODevice::WriteOnly)) { - f.close(); - f.remove(); - return dirPath; - } - } - return QDir(SettingsCache::instance().paths().getThemesPath()) - .absoluteFilePath(SettingsCache::instance().getThemeName()); -} - -bool ThemeSetupPage::isSkippable() const -{ - return true; -} - -QString ThemeSetupPage::stepTitle() const -{ - return tr("Pick a Look"); -} - -QString ThemeSetupPage::stepSubtitle() const -{ - return tr("You can fine-tune every colour later from Settings → Appearance."); -} - -void ThemeSetupPage::retranslateUi() -{ - accentGroup->setTitle(tr("Accent colour (optional)")); -} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/onboarding/pages/theme_setup_page.h b/cockatrice/src/interface/widgets/onboarding/pages/theme_setup_page.h deleted file mode 100644 index d1f84c1b9..000000000 --- a/cockatrice/src/interface/widgets/onboarding/pages/theme_setup_page.h +++ /dev/null @@ -1,58 +0,0 @@ -#ifndef THEME_SETUP_PAGE_H -#define THEME_SETUP_PAGE_H - -#include "../first_run_wizard_page.h" - -class QComboBox; -class QGroupBox; -class QuickSetupPanel; - -/** @brief First-run theme step. Reuses the same building blocks as Appearance - * settings and the Palette Editor (ThemeManager, PaletteConfig, - * PaletteGenerator, and the QuickSetupPanel widget itself) rather than - * reimplementing palette generation or preview here. - * - * Behavior specific to this page (deliberately not pushed down into - * ThemeManager, to avoid changing app-wide behaviour for existing installs): - * - Opening the page never changes the running palette; previews and - * auto-generation only happen when the user actually changes a control. - * - If a theme+scheme the user selects has no saved palette and no shipped - * default, one is generated from the QuickSetupPanel's current accent so - * the preview doesn't fall back to a flat, unstyled look. */ -class ThemeSetupPage : public FirstRunWizardPage -{ - Q_OBJECT - -public: - explicit ThemeSetupPage(QWidget *parent = nullptr); - - void initializePage() override; - bool validatePage() override; - bool isSkippable() const override; - QString stepTitle() const override; - QString stepSubtitle() const override; - void retranslateUi() override; - -private slots: - void onThemeChanged(int index); - void onSchemeChanged(); - void onGenerateFromAccent(const QColor &accent, int intensity); - void onHomeTabBackgroundChanged(int index); - -private: - QString currentScheme() const; - QString resolvedScheme() const; // "System" -> actual Light/Dark - void maybeAutoGeneratePalette(); - QString writableThemeDir() const; - - QComboBox *themeCombo; - QComboBox *schemeCombo; - QGroupBox *accentGroup; - QuickSetupPanel *quickSetupPanel; - - QComboBox *homeTabBackgroundCombo; - - bool paletteDirty = false; -}; - -#endif // THEME_SETUP_PAGE_H diff --git a/cockatrice/src/interface/widgets/onboarding/pages/welcome_page.cpp b/cockatrice/src/interface/widgets/onboarding/pages/welcome_page.cpp deleted file mode 100644 index 16e0719d2..000000000 --- a/cockatrice/src/interface/widgets/onboarding/pages/welcome_page.cpp +++ /dev/null @@ -1,79 +0,0 @@ -#include "welcome_page.h" - -#include "../../../../main.h" -#include "../../client/settings/cache_settings.h" -#include "../../settings_page/general_settings_page.h" -#include "libcockatrice/settings/personal_settings.h" - -#include -#include -#include -#include -#include -#include -#include - -WelcomePage::WelcomePage(QWidget *parent) : FirstRunWizardPage(parent) -{ - bodyLabel = new QLabel(this); - bodyLabel->setWordWrap(true); - bodyLabel->setAlignment(Qt::AlignCenter); - - languageLabel = new QLabel(this); - langCombo = new QComboBox(this); - for (const QString &code : GeneralSettingsPage::findQmFiles()) { - langCombo->addItem(GeneralSettingsPage::languageName(code), code); - } - - QString current = SettingsCache::instance().personal().getLang(); - if (current.isEmpty()) { - current = QLocale::system().name(); - } - int index = langCombo->findData(current); - if (index < 0) { - index = langCombo->findData(current.section('_', 0, 0)); - } - if (index >= 0) { - langCombo->setCurrentIndex(index); - } - - connect(langCombo, QOverload::of(&QComboBox::currentIndexChanged), this, &WelcomePage::languageChanged); - - auto *languageRow = new QHBoxLayout; - languageRow->addStretch(); - languageRow->addWidget(languageLabel); - languageRow->addWidget(langCombo); - languageRow->addStretch(); - - auto *layout = new QVBoxLayout(this); - layout->addStretch(); - layout->addWidget(bodyLabel); - layout->addStretch(); - layout->addLayout(languageRow); - - retranslateUi(); -} - -void WelcomePage::languageChanged(int index) -{ - if (index < 0) { - return; - } - SettingsCache::instance().personal().setLang(langCombo->itemData(index).toString()); - qApp->removeTranslator(translator); // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast) - installNewTranslator(); -} - -QString WelcomePage::stepTitle() const -{ - return tr("Welcome!"); -} - -void WelcomePage::retranslateUi() -{ - bodyLabel->setText(tr("Let's get you set up. This will only take a minute — " - "we'll grab the card database, pick a look you like, " - "and get you ready to connect to a server.\n\n" - "You can change any of this later from Settings.")); - languageLabel->setText(tr("Language:")); -} diff --git a/cockatrice/src/interface/widgets/onboarding/pages/welcome_page.h b/cockatrice/src/interface/widgets/onboarding/pages/welcome_page.h deleted file mode 100644 index 93b24d83d..000000000 --- a/cockatrice/src/interface/widgets/onboarding/pages/welcome_page.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef WELCOME_PAGE_H -#define WELCOME_PAGE_H - -#include "../first_run_wizard_page.h" - -class QComboBox; -class QLabel; - -class WelcomePage : public FirstRunWizardPage -{ - Q_OBJECT - -public: - explicit WelcomePage(QWidget *parent = nullptr); - - QString stepTitle() const override; - void retranslateUi() override; - -private slots: - void languageChanged(int index); - -private: - QLabel *bodyLabel; - QLabel *languageLabel; - QComboBox *langCombo; -}; - -#endif // WELCOME_PAGE_H diff --git a/cockatrice/src/interface/widgets/onboarding/qml/BrandBanner.qml b/cockatrice/src/interface/widgets/onboarding/qml/BrandBanner.qml deleted file mode 100644 index f1a385cad..000000000 --- a/cockatrice/src/interface/widgets/onboarding/qml/BrandBanner.qml +++ /dev/null @@ -1,62 +0,0 @@ -import QtQuick - -Item { - id: root - - ShaderEffect { - id: effectA - anchors.fill: parent - opacity: bannerConfig.frontIsA ? 1.0 : 0.0 - Behavior on opacity { NumberAnimation { duration: 450; easing.type: Easing.InOutCubic } } - property real iTime: bannerConfig.time - property real uAspect: bannerConfig.aspect - property real uMode: bannerConfig.modeA - property real uSpeed: bannerConfig.speedA - property real uSeed: bannerConfig.seedA - property vector4d uColorA: Qt.vector4d(bannerConfig.colorA.r, bannerConfig.colorA.g, bannerConfig.colorA.b, 1.0) - property vector4d uColorB: Qt.vector4d(bannerConfig.colorB.r, bannerConfig.colorB.g, bannerConfig.colorB.b, 1.0) - property vector4d uAccent: Qt.vector4d(bannerConfig.accent.r, bannerConfig.accent.g, bannerConfig.accent.b, 1.0) - property real uLogoGlow: bannerConfig.logoGlow - fragmentShader: "qrc:/onboarding/shaders/brand_banner.frag.qsb" - } - - ShaderEffect { - id: effectB - anchors.fill: parent - opacity: bannerConfig.frontIsA ? 0.0 : 1.0 - Behavior on opacity { NumberAnimation { duration: 450; easing.type: Easing.InOutCubic } } - property real iTime: bannerConfig.time - property real uAspect: bannerConfig.aspect - property real uMode: bannerConfig.modeB - property real uSpeed: bannerConfig.speedB - property real uSeed: bannerConfig.seedB - property vector4d uColorA: Qt.vector4d(bannerConfig.colorA.r, bannerConfig.colorA.g, bannerConfig.colorA.b, 1.0) - property vector4d uColorB: Qt.vector4d(bannerConfig.colorB.r, bannerConfig.colorB.g, bannerConfig.colorB.b, 1.0) - property vector4d uAccent: Qt.vector4d(bannerConfig.accent.r, bannerConfig.accent.g, bannerConfig.accent.b, 1.0) - property real uLogoGlow: bannerConfig.logoGlow - fragmentShader: "qrc:/onboarding/shaders/brand_banner.frag.qsb" - } - - // The hero logo itself — breathes cleanly over a 0.5–1.0 opacity range - Image { - id: logo - anchors.centerIn: parent - visible: bannerConfig.logoVisible - source: "qrc:/resources/cockatrice-logo-white.svg" - width: root.height * 0.6 - height: width * (sourceSize.height > 0 ? sourceSize.height / Math.max(sourceSize.width, 1) : 1) - fillMode: Image.PreserveAspectFit - smooth: true - opacity: 0.5 + 0.5 * bannerConfig.logoGlow - sourceSize: Qt.size(256, 256) - - Behavior on opacity { NumberAnimation { duration: 300; easing.type: Easing.InOutSine } } - - transform: Scale { - origin.x: logo.width / 2 - origin.y: logo.height / 2 - xScale: 0.94 + 0.06 * bannerConfig.logoGlow - yScale: 0.94 + 0.06 * bannerConfig.logoGlow - } - } -} diff --git a/cockatrice/src/interface/widgets/onboarding/shader_banner_widget.cpp b/cockatrice/src/interface/widgets/onboarding/shader_banner_widget.cpp deleted file mode 100644 index fd1fb2a98..000000000 --- a/cockatrice/src/interface/widgets/onboarding/shader_banner_widget.cpp +++ /dev/null @@ -1,195 +0,0 @@ -#include "shader_banner_widget.h" - -#include "banner_shader_config.h" - -#include -#include -#include -#include -#include -#include - -namespace -{ -// Near-black base palette -- the background is dark and quiet so the green -// accent stands out. -constexpr QRgb kColorA = 0x1A1A20; -constexpr QRgb kColorB = 0x0E0E12; -constexpr QRgb kAccent = 0x8BDD6B; -} // namespace - -class GradientFallbackWidget : public QWidget -{ -public: - using QWidget::QWidget; - -protected: - void paintEvent(QPaintEvent *) override - { - QPainter painter(this); - QLinearGradient gradient(0, 0, width(), height()); - gradient.setColorAt(0.0, QColor(kColorA)); - gradient.setColorAt(1.0, QColor(kColorB)); - painter.fillRect(rect(), gradient); - } -}; - -BannerHost::BannerHost(QWidget *parent) : QWidget(parent) -{ - setFixedHeight(150); - - stack = new QStackedLayout(this); - stack->setContentsMargins(0, 0, 0, 0); - - fallback = new GradientFallbackWidget(this); - stack->addWidget(fallback); - - quickWidget = new QQuickWidget(this); - quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView); - - config = new BannerShaderConfig(quickWidget->engine()); - quickWidget->rootContext()->setContextProperty("bannerConfig", config); - quickWidget->setSource(QUrl("qrc:/onboarding/qml/BrandBanner.qml")); - - if (quickWidget->status() == QQuickWidget::Error) { - activateFallback(); - } else { - connect(quickWidget, &QQuickWidget::sceneGraphError, this, &BannerHost::onSceneGraphFailed); - stack->addWidget(quickWidget); - stack->setCurrentWidget(quickWidget); - } - - connect(&clock, &QTimer::timeout, this, &BannerHost::tick); - clock.setInterval(16); // ~60fps; the shader itself is cheap, this is just a wall clock - - applyMotifPreset(currentMotif); - updateAspect(); -} - -void BannerHost::activateFallback() -{ - if (usingFallback) { - return; - } - usingFallback = true; - clock.stop(); - stack->setCurrentWidget(fallback); - - if (quickWidget) { - quickWidget->deleteLater(); // takes BannerShaderConfig (parented to its engine) with it - quickWidget = nullptr; - config = nullptr; - } -} - -void BannerHost::onSceneGraphFailed() -{ - activateFallback(); -} - -void BannerHost::setMotif(Motif motif) -{ - currentMotif = motif; - applyMotifPreset(motif); -} - -BannerHost::Preset BannerHost::presetFor(Motif motif) -{ - // speed/seed tuned per motif so e.g. the network "pulse" (Account) reads - // at a deliberately calmer cadence than the data "scan" lines - // (Preferences), even though both come from the same shader. - switch (motif) { - case Motif::Welcome: - return {0.0, 0.6, 0.15}; - case Motif::CardDatabase: - return {1.0, 1.3, 0.42}; - case Motif::Theming: - return {2.0, 1.2, 0.73}; - case Motif::Account: - return {3.0, 0.8, 0.28}; - case Motif::Preferences: - return {4.0, 1.0, 0.61}; - case Motif::Finish: - return {5.0, 1.0, 0.91}; - } - return {0.0, 0.6, 0.15}; -} - -void BannerHost::applyMotifPreset(Motif motif) -{ - if (usingFallback || !config) { - return; - } - - const Preset p = presetFor(motif); - - config->setColorA(QColor(kColorA)); - config->setColorB(QColor(kColorB)); - config->setAccent(QColor(kAccent)); - config->setLogoVisible(motif == Motif::Welcome); - - if (isFirstApply) { - // Nothing on screen yet -- write straight into the front bank, no - // crossfade needed for the very first paint. - config->setModeA(p.mode); - config->setSpeedA(p.speed); - config->setSeedA(p.seed); - config->setFrontIsA(true); - isFirstApply = false; - return; - } - - // Write the new preset into whichever bank is currently hidden, then - // flip which one is front. QML's opacity Behavior does the actual - // crossfade -- BannerHost never animates anything itself. - if (config->frontIsA()) { - config->setModeB(p.mode); - config->setSpeedB(p.speed); - config->setSeedB(p.seed); - config->setFrontIsA(false); - } else { - config->setModeA(p.mode); - config->setSpeedA(p.speed); - config->setSeedA(p.seed); - config->setFrontIsA(true); - } -} - -void BannerHost::updateAspect() -{ - if (config && height() > 0) { - config->setAspect(qreal(width()) / qreal(height())); - } -} - -void BannerHost::resizeEvent(QResizeEvent *event) -{ - QWidget::resizeEvent(event); - updateAspect(); -} - -void BannerHost::showEvent(QShowEvent *event) -{ - QWidget::showEvent(event); - if (!usingFallback) { - elapsed.restart(); - clock.start(); - } -} - -void BannerHost::hideEvent(QHideEvent *event) -{ - QWidget::hideEvent(event); - clock.stop(); -} - -void BannerHost::tick() -{ - if (config) { - qreal t = elapsed.elapsed() / 1000.0; - config->setTime(t); - // Visible breathing for the logo: oscillates between 0.0 and 1.0 - qreal glow = 0.5 + 0.5 * qSin(t * 0.4); - config->setLogoGlow(glow); - } -} diff --git a/cockatrice/src/interface/widgets/onboarding/shader_banner_widget.h b/cockatrice/src/interface/widgets/onboarding/shader_banner_widget.h deleted file mode 100644 index 2e230ad7f..000000000 --- a/cockatrice/src/interface/widgets/onboarding/shader_banner_widget.h +++ /dev/null @@ -1,83 +0,0 @@ -#ifndef SHADER_BANNER_WIDGET_H -#define SHADER_BANNER_WIDGET_H - -#include -#include -#include - -class BannerShaderConfig; -class QQuickWidget; -class GradientFallbackWidget; -class QStackedLayout; - -/** @brief Onboarding banner: a subtle, looping brand-shader animation, one of six - * per-page "motifs" driving the same prebaked fragment shader - * (onboarding/shaders/brand_banner.frag) with different uniform values, so - * every page feels distinct but unmistakably part of the same family. - * - * Motif switches crossfade smoothly (see BrandBanner.qml's two stacked - * ShaderEffect layers + Behavior on opacity) rather than cutting instantly - * -- BannerHost just writes the new preset into whichever layer is - * currently hidden and flips BannerShaderConfig::frontIsA; QML handles the - * actual animation declaratively. - * - * Falls back to a static two-stop gradient (no shader, no QQuickWidget) if - * the platform's Qt Quick scenegraph can't initialize -- e.g. software - * rendering only, or a CI/VM environment with no GPU -- so onboarding - * never blocks or blanks out over a graphics driver problem. The fallback - * is permanent for the lifetime of this widget once triggered. */ -class BannerHost : public QWidget -{ - Q_OBJECT - -public: - enum class Motif - { - Welcome, - CardDatabase, - Theming, - Account, - Preferences, - Finish, - }; - - explicit BannerHost(QWidget *parent = nullptr); - - void setMotif(Motif motif); - -protected: - void showEvent(QShowEvent *event) override; - void hideEvent(QHideEvent *event) override; - void resizeEvent(QResizeEvent *event) override; - -private slots: - void tick(); - void onSceneGraphFailed(); - -private: - struct Preset - { - qreal mode; - qreal speed; - qreal seed; - }; - - static Preset presetFor(Motif motif); - - void applyMotifPreset(Motif motif); - void updateAspect(); - void activateFallback(); - - QStackedLayout *stack; - QQuickWidget *quickWidget = nullptr; - BannerShaderConfig *config = nullptr; - GradientFallbackWidget *fallback = nullptr; - - QTimer clock; - QElapsedTimer elapsed; - Motif currentMotif = Motif::Welcome; - bool usingFallback = false; - bool isFirstApply = true; -}; - -#endif // SHADER_BANNER_WIDGET_H diff --git a/cockatrice/src/interface/widgets/onboarding/shaders/brand_banner.frag b/cockatrice/src/interface/widgets/onboarding/shaders/brand_banner.frag deleted file mode 100644 index 508bd4bc4..000000000 --- a/cockatrice/src/interface/widgets/onboarding/shaders/brand_banner.frag +++ /dev/null @@ -1,461 +0,0 @@ -#version 440 - -// ════════════════════════════════════════════════════════════════════════ -// brand_banner.frag -// -// One shader, six motifs (uMode 0..5). All motifs composite over a shared -// backgroundField() whose colour is flow-noise-modulated blend of uColorA -// and uColorB. SDFs operate in aspect-corrected space (ac.x = uv.x * -// uAspect) to preserve shape proportions on the wide banner. -// -// IMPORTANT: the uniform block below must list custom uniforms in EXACTLY -// the order they're declared as properties on each ShaderEffect instance in -// BrandBanner.qml (after the two Qt-supplied members, qt_Matrix/qt_Opacity). -// ════════════════════════════════════════════════════════════════════════ - -layout(location = 0) in vec2 qt_TexCoord0; -layout(location = 0) out vec4 fragColor; - -layout(std140, binding = 0) uniform buf -{ - mat4 qt_Matrix; - float qt_Opacity; - float iTime; - float uAspect; - float uMode; - float uSpeed; - float uSeed; - vec4 uColorA; - vec4 uColorB; - vec4 uAccent; - float uLogoGlow; -}; - -// ── Primitives ────────────────────────────────────────────────────────── - -float hash21(vec2 p) -{ - p = fract(p * vec2(123.34, 456.21)); - p += dot(p, p + 45.32); - return fract(p.x * p.y); -} - -float valueNoise(vec2 p) -{ - vec2 i = floor(p); - vec2 f = fract(p); - float a = hash21(i); - float b = hash21(i + vec2(1.0, 0.0)); - float c = hash21(i + vec2(0.0, 1.0)); - float d = hash21(i + vec2(1.0, 1.0)); - vec2 u = f * f * (3.0 - 2.0 * f); - return mix(mix(a, b, u.x), mix(c, d, u.x), u.y); -} - -float fbm(vec2 p) -{ - float v = 0.0; - float amp = 0.5; - for (int i = 0; i < 3; i++) { - v += amp * valueNoise(p); - p *= 2.03; - amp *= 0.5; - } - return v; -} - -float flowNoise(vec2 p, float t) -{ - vec2 warp1 = vec2(fbm(p + vec2(0.0, 0.0)), fbm(p + vec2(5.2, 1.3))); - vec2 warp2 = vec2(fbm(p + 4.0 * warp1 + vec2(1.7, 9.2) + t * 0.6), - fbm(p + 4.0 * warp1 + vec2(8.3, 2.8) - t * 0.5)); - return fbm(p + 4.0 * warp2 + t * 0.15); -} - -float bloom(float d, float coreRadius, float haloRadius) -{ - float core = exp(-(d * d) / (coreRadius * coreRadius)); - float halo = exp(-d / haloRadius) * 0.35; - return core + halo; -} - -float roundedBoxSDF(vec2 p, vec2 halfSize, float radius) -{ - vec2 d = abs(p) - halfSize + radius; - return length(max(d, 0.0)) - radius + min(max(d.x, d.y), 0.0); -} - -// Rotated box SDF -- applies 2D rotation to p before evaluating roundedBoxSDF. -float rotatedBoxSDF(vec2 p, vec2 halfSize, float radius, float angle) -{ - float c = cos(angle); - float s = sin(angle); - vec2 rp = vec2(p.x * c - p.y * s, p.x * s + p.y * c); - return roundedBoxSDF(rp, halfSize, radius); -} - -float vignette(vec2 uv) -{ - vec2 c = uv - 0.5; - c.x *= max(uAspect, 0.0001); - return smoothstep(1.0, 0.25, length(c)); -} - -// ── Shared background ─────────────────────────────────────────────────── - -vec3 backgroundField(vec2 uv, float time) -{ - // Diagonal luminance gradient from (0,0) to (1,1) used as blend factor - // between uColorA and uColorB; modulated by flowNoise. - float baseD = smoothstep(0.0, 1.0, uv.y * 0.5 + uv.x * 0.2); - float painted = flowNoise(uv * 1.5, time * 0.04) - 0.5; - baseD = clamp(baseD + painted * 0.12, 0.0, 1.0); - - vec3 col = mix(uColorA.rgb, uColorB.rgb, baseD); - - // Low-frequency fBM noise pushes local colour toward uColorB for depth - float deep = fbm(uv * 1.0 + vec2(37.1, 12.4) + time * 0.015); - col = mix(col, uColorB.rgb, (deep - 0.5) * 0.08); - - // Accent-coloured fog layer: flowNoise peaks above 0.6 contribute accent - float fog = flowNoise(uv * 0.8 + vec2(100.0, 50.0), time * 0.02); - col += uAccent.rgb * max(fog - 0.6, 0.0) * 0.10; - - return col; -} - -// ── Motifs ────────────────────────────────────────────────────────────── - -// Centre bloom, flow-noise shimmer gated to centre, and 48 orbiting ember -// particles that deflect into a tight ring near the centre. -vec3 motifWelcome(vec2 uv, vec3 bg, float t) -{ - vec3 col = bg; - float asp = max(uAspect, 0.001); - vec2 ac = vec2(uv.x * asp, uv.y); - vec2 center = vec2(asp * 0.5, 0.5); - float cDist = length(ac - center); - - // Centre bloom at logo position; intensity scales with uLogoGlow - float centreLight = bloom(cDist, 0.08 * asp, 0.40 * asp); - col += centreLight * 0.20 * uLogoGlow; - - // Flow-noise shimmer gated by Gaussian mask at centre - float shimmer = flowNoise(ac * 0.8 + vec2(55.0, 33.0), t * 0.05) * 0.5 + 0.5; - float shimmerMask = exp(-(cDist * cDist) / (0.18 * asp * 0.18 * asp)); - col += shimmer * shimmerMask * 0.04 * uLogoGlow; - - // 48 ember particles: hash-seeded position, speed, size, brightness. - // Embers within a distance threshold of centre are deflected into an - // orbital ring via tangent displacement perpendicular to the centre vector. - const int EMBERS = 48; - for (int i = 0; i < EMBERS; i++) { - float fi = float(i); - - float baseX = hash21(vec2(fi * 7.31 + uSeed, fi * 3.17)); - float baseY = hash21(vec2(fi * 11.9 + uSeed * 1.4, fi * 5.53)); - - float riseSpeed = 0.025 + hash21(vec2(fi * 1.7, uSeed * 2.1)) * 0.035; - float driftAmp = 0.04 + hash21(vec2(fi * 9.3, uSeed)) * 0.06; - float driftFreq = 0.3 + hash21(vec2(fi * 4.1, uSeed * 3.3)) * 0.5; - - float pX = baseX * asp + sin(t * driftFreq + fi * 1.7) * driftAmp * asp; - float pY = fract(baseY + t * riseSpeed); - - float size = 0.006 + hash21(vec2(fi * 2.9, uSeed * 4.7)) * 0.012; - float bright = 0.15 + hash21(vec2(fi * 6.1, uSeed * 0.9)) * 0.30; - - // Fade out near top/bottom edges - float edgeFade = smoothstep(0.0, 0.12, pY) * smoothstep(1.0, 0.88, pY); - float twinkle = 0.6 + 0.4 * sin(t * (1.2 + fi * 0.37) + fi * 2.9); - - vec2 ePos = vec2(pX, pY); - - // Embers near centre: deflect into orbital ring via tangent displacement - vec2 toCenter = ePos - center; - float distToCenter = length(toCenter); - float ringWeight = smoothstep(0.38 * asp, 0.06 * asp, distToCenter); - - float orbitPhase = t * (0.15 + fi * 0.020) + fi * 2.3; - float orbitAmount = 0.020 + hash21(vec2(fi * 12.3, uSeed * 2.7)) * 0.020; - vec2 tangent = vec2(-toCenter.y, toCenter.x); - vec2 deflected = ePos + tangent * ringWeight * orbitAmount * asp * sin(orbitPhase); - - float pushOut = ringWeight * (0.008 + hash21(vec2(fi * 6.7, uSeed * 1.1)) * 0.012) * asp; - deflected += normalize(toCenter + 0.001) * pushOut; - - float dist = length(ac - deflected); - float intensity = bright * edgeFade * twinkle; - col += uAccent.rgb * bloom(dist, size, size * 4.0) * intensity; - } - - return col; -} - -// 25 card-shaped box SDFs at parallax depths drifting horizontally across -// the banner; each card has a semi-transparent fill, accent outline, and -// card-back diamond pattern. -vec3 motifCardDatabase(vec2 uv, vec3 bg, float t) -{ - vec3 col = bg; - float asp = max(uAspect, 0.001); - vec2 ac = vec2(uv.x * asp, uv.y); - - const int CARDS = 25; - for (int i = 0; i < CARDS; i++) { - float fi = float(i); - - // Parallax depth via hash; used to scale size, speed, brightness - float depth = hash21(vec2(fi * 1.37 + uSeed, fi * 0.91)); - - // Card dimensions in corrected space (portrait: height > width) - float cardH = mix(0.055, 0.15, depth); - cardH *= 0.85 + 0.30 * hash21(vec2(fi * 3.14, uSeed * 2.71)); - float cardW = cardH * 0.71; // 5:7 ratio - - // Horizontal drift; nearer cards (higher depth) move faster - float speed = mix(0.06, 0.18, depth); - float xPhase = hash21(vec2(fi * 7.13, uSeed * 4.37)); - xPhase = fract(xPhase + t * speed); - float x = mix(-1.5, asp + 1.5, xPhase); - - // Vertical position: hash distribution with sinusoidal oscillation - float yBase = hash21(vec2(fi * 2.91, uSeed * 1.63)); - float y = yBase + sin(t * 0.6 + fi * 1.9) * 0.035; - y = clamp(y, cardH + 0.02, 1.0 - cardH - 0.02); - - // Random rotation angle ±4 degrees - float tilt = (hash21(vec2(fi * 5.71, uSeed * 8.29)) - 0.5) * 0.14; - - vec2 p = ac - vec2(x, y); - float d = rotatedBoxSDF(p, vec2(cardW, cardH), cardW * 0.14, tilt); - - // Semi-transparent dark fill - float fill = smoothstep(0.015, -0.005, d); - col = mix(col, uColorB.rgb * 0.55, fill * 0.50); - - // Accent outline - float edge = smoothstep(0.035, 0.0, abs(d)); - col += uAccent.rgb * edge * mix(0.18, 0.50, 1.0 - depth); - - // Card-back diamond: smaller rotated box inset from card edges - float innerD = rotatedBoxSDF(p, vec2(cardW * 0.45, cardH * 0.55), cardW * 0.08, tilt); - float innerEdge = smoothstep(0.012, 0.0, abs(innerD)); - col += uAccent.rgb * innerEdge * fill * 0.12 * (1.0 - depth); - - // Centre dot - float dotDist = length(p); - col += uAccent.rgb * bloom(dotDist, 0.008, 0.02) * fill * 0.15 * (1.0 - depth); - } - return col; -} - -// 4 horizontal bands with multi-frequency sinusoidal warp and pulsing width. -vec3 motifTheming(vec2 uv, vec3 bg, float t) -{ - vec3 col = bg; - - const int BANDS = 4; - for (int i = 0; i < BANDS; i++) { - float fi = float(i); - float yCenter = 0.18 + fi * 0.22; - - // Three summed sinusoids for horizontal undulation - float wave = sin(uv.x * 3.2 + t * 0.5 + fi * 2.1) * 0.08; - wave += sin(uv.x * 7.0 - t * 0.3 + fi * 1.3) * 0.035; - wave += sin(uv.x * 1.6 + t * 0.18 + fi * 3.7) * 0.05; - - float bandDist = abs(uv.y - yCenter - wave); - float bandWidth = 0.04 + sin(t * 0.2 + fi * 0.8) * 0.012; - float band = smoothstep(bandWidth, 0.0, bandDist); - - // Upper bands have higher intensity - float intensity = mix(0.15, 0.38, 1.0 - fi / float(BANDS)); - col += uAccent.rgb * band * intensity; - } - - return col; -} - -// 14 nodes at pseudo-random positions with sinusoidal pulse; edges drawn -// between nodes within a threshold distance; central glow + periodic ring. -vec3 motifAccount(vec2 uv, vec3 bg, float t) -{ - vec3 col = bg; - float asp = max(uAspect, 0.001); - vec2 ac = vec2(uv.x * asp, uv.y); - vec2 center = vec2(asp * 0.5, 0.5); - - const int NODES = 14; - vec2 nodePos[14]; - float nodePulse[14]; - - for (int i = 0; i < NODES; i++) { - float fi = float(i); - - // Hash-seeded position with gentle sinusoidal drift - float nx = hash21(vec2(fi * 3.17 + uSeed, fi * 1.93)) * asp; - float ny = hash21(vec2(fi * 5.41 + uSeed * 1.7, fi * 2.79)); - - float dx = sin(t * 0.12 + fi * 1.7) * 0.08; - float dy = cos(t * 0.09 + fi * 2.3) * 0.04; - vec2 pos = vec2(nx + dx, ny + dy); - nodePos[i] = pos; - - // Per-node pulse phase, normalised to [0, 1] - float pulsePhase = hash21(vec2(fi * 4.31, uSeed * 6.17)); - float pulse = sin(t * 0.8 + pulsePhase * 6.283) * 0.5 + 0.5; - nodePulse[i] = pulse; - - // Node glow via bloom; intensity modulated by pulse - float dist = length(ac - pos); - col += uAccent.rgb * bloom(dist, 0.018, 0.08) * mix(0.20, 0.45, pulse); - } - - // Edges: connect nodes within a radius threshold - float connectDist = asp * 0.22; - for (int i = 0; i < NODES; i++) { - for (int j = i + 1; j < NODES; j++) { - float pairDist = length(nodePos[i] - nodePos[j]); - if (pairDist < connectDist) { - float strength = 1.0 - pairDist / connectDist; - vec2 pa = ac - nodePos[i]; - vec2 ba = nodePos[j] - nodePos[i]; - float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0); - float lineDist = length(pa - ba * h); - col += uAccent.rgb * smoothstep(0.010, 0.0, lineDist) * strength * 0.10; - } - } - } - - // Central bloom at banner centre - float cDist = length(ac - center); - col += uAccent.rgb * bloom(cDist, 0.04, 0.25) * 0.12; - - // Periodic expanding ring from centre - float ripplePhase = t * 0.4; - float rippleDist = abs(cDist - fract(ripplePhase) * asp * 0.7); - col += uAccent.rgb * smoothstep(0.02, 0.0, rippleDist) * 0.10; - - return col; -} - -// 18x5 toggle-grid of rounded boxes with hash-driven on/off per cell; -// a scanning highlight sweeps L-to-R, brightening cells near the scan line. -vec3 motifPreferences(vec2 uv, vec3 bg, float t) -{ - vec3 col = bg; - - float cols = 18.0; - float rows = 5.0; - vec2 gridUV = uv * vec2(cols, rows); - vec2 cell = fract(gridUV) - 0.5; - vec2 cellId = floor(gridUV); - - // On/off state per cell, hash-seeded for pseudo-randomness - float on = step(0.55, hash21(cellId + uSeed * 10.0)); - - float d = roundedBoxSDF(cell, vec2(0.28, 0.32), 0.06); - - // Filled "on" cells - float cellFill = smoothstep(0.04, -0.02, d); - col += uAccent.rgb * cellFill * on * 0.18; - - // Cell borders (drawn on all cells) - float border = smoothstep(0.025, 0.0, abs(d)); - col += uAccent.rgb * border * 0.06; - - // Scanning highlight: thin line + soft glow sweeping L-to-R - float scanX = fract(t * 0.15); - float scanDist = abs(uv.x - scanX); - float scanLine = smoothstep(0.015, 0.0, scanDist); - col += uAccent.rgb * scanLine * 0.40; - - float scanGlow = smoothstep(0.08, 0.0, scanDist); - col += uAccent.rgb * scanGlow * 0.08; - - // "On" cells near the scan line get extra brightness - float scanProximity = smoothstep(0.12, 0.0, scanDist); - col += uAccent.rgb * cellFill * on * scanProximity * 0.15; - - return col; -} - -// Centre radial bloom with sinusoidal pulse, 4 expanding ring halos with -// outer glow falloff, and 35 rising particles. -vec3 motifFinish(vec2 uv, vec3 bg, float t) -{ - vec3 col = bg; - float asp = max(uAspect, 0.001); - vec2 ac = vec2(uv.x * asp, uv.y); - vec2 center = vec2(asp * 0.5, 0.5); - float cDist = length(ac - center); - - // Centre bloom with sinusoidal pulse modulation - float pulse = 0.65 + 0.35 * sin(t * 0.4); - col += uAccent.rgb * bloom(cDist, 0.12, 0.55) * 0.10 * pulse; - - // 4 expanding rings: radius increases via phase; ring width grows with - // expansion; combined with exponential outer glow falloff - for (int i = 0; i < 4; i++) { - float fi = float(i); - float phase = fract(t * 0.06 + fi * 0.25); - float ringRadius = phase * asp * 0.7; - float ringDist = abs(cDist - ringRadius); - float ringWidth = 0.025 + phase * 0.025; - float ring = smoothstep(ringWidth, 0.0, ringDist); - float outerGlow = exp(-ringDist / (0.03 + phase * 0.02)) * 0.3; - float combined = ring + outerGlow; - float fade = 1.0 - phase * 0.5; - col += uAccent.rgb * combined * fade * 0.15; - } - - // 35 particles rising vertically with sinusoidal horizontal drift; - // each particle uses bloom with edge fade and twinkle animation - const int PARTICLES = 35; - for (int i = 0; i < PARTICLES; i++) { - float fi = float(i); - float baseX = hash21(vec2(fi * 13.7 + uSeed, fi * 7.31)); - float baseY = hash21(vec2(fi * 23.1 + uSeed * 1.9, fi * 11.3)); - - float riseSpeed = 0.04 + hash21(vec2(fi * 3.1, uSeed * 2.7)) * 0.06; - float driftAmp = 0.03 + hash21(vec2(fi * 8.9, uSeed)) * 0.05; - float driftFreq = 0.4 + hash21(vec2(fi * 5.3, uSeed * 4.1)) * 0.6; - - float pX = baseX * asp + sin(t * driftFreq + fi * 2.3) * driftAmp * asp; - float pY = fract(baseY + t * riseSpeed); - - float size = 0.005 + hash21(vec2(fi * 4.7, uSeed * 3.9)) * 0.010; - float bright = 0.12 + hash21(vec2(fi * 7.1, uSeed * 1.3)) * 0.25; - - float edgeFade = smoothstep(0.0, 0.1, pY) * smoothstep(1.0, 0.9, pY); - float twinkle = 0.5 + 0.5 * sin(t * (1.8 + fi * 0.43) + fi * 3.1); - - vec2 pPos = vec2(pX, pY); - float dist = length(ac - pPos); - col += uAccent.rgb * bloom(dist, size, size * 3.5) * bright * edgeFade * twinkle; - } - - return col; -} - -// ── Main ──────────────────────────────────────────────────────────────── - -void main() -{ - vec2 uv = qt_TexCoord0; - float t = iTime * uSpeed; - - vec3 bg = backgroundField(uv, iTime); - - vec3 col; - if (uMode < 0.5) col = motifWelcome(uv, bg, t); - else if (uMode < 1.5) col = motifCardDatabase(uv, bg, t); - else if (uMode < 2.5) col = motifTheming(uv, bg, t); - else if (uMode < 3.5) col = motifAccount(uv, bg, t); - else if (uMode < 4.5) col = motifPreferences(uv, bg, t); - else col = motifFinish(uv, bg, t); - - col *= mix(0.62, 1.0, vignette(uv)); - fragColor = vec4(col, 1.0) * qt_Opacity; -} diff --git a/cockatrice/src/interface/widgets/onboarding/step_indicator_widget.cpp b/cockatrice/src/interface/widgets/onboarding/step_indicator_widget.cpp deleted file mode 100644 index d25e8544b..000000000 --- a/cockatrice/src/interface/widgets/onboarding/step_indicator_widget.cpp +++ /dev/null @@ -1,84 +0,0 @@ -#include "step_indicator_widget.h" - -#include -#include - -StepIndicatorWidget::StepIndicatorWidget(QWidget *parent) : QWidget(parent) -{ - setFixedHeight(kDotDiameter + 2 * kVerticalMargin); -} - -void StepIndicatorWidget::setStepCount(int count) -{ - stepCount = qMax(0, count); - currentStep = qBound(0, currentStep, qMax(0, stepCount - 1)); - updateGeometry(); - update(); -} - -void StepIndicatorWidget::setCurrentStep(int index) -{ - if (stepCount == 0) { - return; - } - currentStep = qBound(0, index, stepCount - 1); - update(); -} - -QSize StepIndicatorWidget::sizeHint() const -{ - return minimumSizeHint(); -} - -QSize StepIndicatorWidget::minimumSizeHint() const -{ - if (stepCount == 0) { - return QSize(0, height()); - } - int width = kActiveDotWidth + (stepCount - 1) * kDotDiameter + (stepCount - 1) * kDotSpacing; - return QSize(width, height()); -} - -void StepIndicatorWidget::paintEvent(QPaintEvent * /*event*/) -{ - if (stepCount == 0) { - return; - } - - QPainter painter(this); - painter.setRenderHint(QPainter::Antialiasing); - - const QColor activeColor = palette().color(QPalette::Highlight); - - // QPalette::Mid alpha-blended against a dark Window background reads as - // near-invisible (Mid is itself a dark grey in dark palettes -- see - // PaletteGenerator's satShadeLo/Dark roles). WindowText is guaranteed to - // contrast against Window in any theme by definition, so alpha-blending - // *that* instead keeps the dots visibly dim-but-present in both light and - // dark schemes. Same trick PaletteGenerator uses for placeholder text. - QColor inactiveColor = palette().color(QPalette::WindowText); - inactiveColor.setAlpha(100); - - int totalWidth = 0; - for (int i = 0; i < stepCount; ++i) { - totalWidth += (i == currentStep) ? kActiveDotWidth : kDotDiameter; - if (i > 0) { - totalWidth += kDotSpacing; - } - } - - int x = (width() - totalWidth) / 2; - const int y = height() / 2; - - for (int i = 0; i < stepCount; ++i) { - const bool active = (i == currentStep); - const int dotWidth = active ? kActiveDotWidth : kDotDiameter; - - QPainterPath path; - QRectF rect(x, y - kDotDiameter / 2.0, dotWidth, kDotDiameter); - path.addRoundedRect(rect, kDotDiameter / 2.0, kDotDiameter / 2.0); - painter.fillPath(path, active ? activeColor : inactiveColor); - - x += dotWidth + kDotSpacing; - } -} diff --git a/cockatrice/src/interface/widgets/onboarding/step_indicator_widget.h b/cockatrice/src/interface/widgets/onboarding/step_indicator_widget.h deleted file mode 100644 index 1b85be04f..000000000 --- a/cockatrice/src/interface/widgets/onboarding/step_indicator_widget.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef STEP_INDICATOR_WIDGET_H -#define STEP_INDICATOR_WIDGET_H - -#include - -/** @brief Row of dots showing progress through a fixed-length sequence of steps, - * in the style of a mobile/OS setup flow. Purely presentational. */ -class StepIndicatorWidget : public QWidget -{ - Q_OBJECT - -public: - explicit StepIndicatorWidget(QWidget *parent = nullptr); - - void setStepCount(int count); - void setCurrentStep(int index); - - QSize sizeHint() const override; - QSize minimumSizeHint() const override; - -protected: - void paintEvent(QPaintEvent *event) override; - -private: - int stepCount = 0; - int currentStep = 0; - - static constexpr int kDotDiameter = 8; - static constexpr int kActiveDotWidth = 22; - static constexpr int kDotSpacing = 10; - static constexpr int kVerticalMargin = 6; -}; - -#endif // STEP_INDICATOR_WIDGET_H diff --git a/cockatrice/src/interface/widgets/settings_page/general_settings_page.h b/cockatrice/src/interface/widgets/settings_page/general_settings_page.h index 8dd7e8798..fbe70a5a4 100644 --- a/cockatrice/src/interface/widgets/settings_page/general_settings_page.h +++ b/cockatrice/src/interface/widgets/settings_page/general_settings_page.h @@ -20,9 +20,6 @@ public: GeneralSettingsPage(); void retranslateUi() override; - static QStringList findQmFiles(); - static QString languageName(const QString &lang); - private slots: void deckPathButtonClicked(); void filtersPathButtonClicked(); @@ -36,6 +33,9 @@ private slots: void updateStartupServerControlsVisibility(); private: + QStringList findQmFiles(); + QString languageName(const QString &lang); + QGroupBox *languageGroupBox; QGroupBox *versionGroupBox; QGroupBox *cardDatabaseGroupBox; diff --git a/cockatrice/src/interface/window_main.cpp b/cockatrice/src/interface/window_main.cpp index 4567991c8..13c37473e 100644 --- a/cockatrice/src/interface/window_main.cpp +++ b/cockatrice/src/interface/window_main.cpp @@ -32,7 +32,6 @@ #include "../interface/widgets/dialogs/dlg_tip_of_the_day.h" #include "../interface/widgets/dialogs/dlg_update.h" #include "../interface/widgets/dialogs/dlg_view_log.h" -#include "../interface/widgets/onboarding/first_run_wizard.h" #include "../interface/widgets/tabs/tab_game.h" #include "../interface/widgets/tabs/tab_server.h" #include "../interface/widgets/tabs/tab_supervisor.h" @@ -351,7 +350,6 @@ void MainWindow::retranslateUi() aStatusBar->setText(tr("Show Status Bar")); aViewLog->setText(tr("View &Debug Log")); aOpenSettingsFolder->setText(tr("Open Settings Folder")); - aFirstRunWizard->setText(tr("Re-run Onboarding Wizard...")); aShow->setText(tr("Show/Hide")); @@ -413,8 +411,6 @@ void MainWindow::createActions() connect(aViewLog, &QAction::triggered, this, &MainWindow::actViewLog); aOpenSettingsFolder = new QAction(this); connect(aOpenSettingsFolder, &QAction::triggered, this, &MainWindow::actOpenSettingsFolder); - aFirstRunWizard = new QAction(this); - connect(aFirstRunWizard, &QAction::triggered, this, [this] { runFirstRunWizard(); }); aShow = new QAction(this); connect(aShow, &QAction::triggered, this, &MainWindow::actShow); @@ -493,8 +489,6 @@ void MainWindow::createMenus() helpMenu->addAction(aStatusBar); helpMenu->addAction(aViewLog); helpMenu->addAction(aOpenSettingsFolder); - helpMenu->addSeparator(); - helpMenu->addAction(aFirstRunWizard); } MainWindow::MainWindow(QWidget *parent) @@ -591,10 +585,9 @@ void MainWindow::startupConfigCheck() // no config found, 99% new clean install qCInfo(WindowMainStartupVersionLog) << "Startup: old client version empty, assuming first start after clean install"; + alertForcedOracleRun(VERSION_STRING, false); SettingsCache::instance().downloads().resetToDefaultURLs(); // populate the download urls SettingsCache::instance().network().setClientVersion(VERSION_STRING); - actCheckServerUpdates(); - runFirstRunWizard(); if (QString(VERSION_STRING).contains("custom", Qt::CaseInsensitive)) { SettingsCache::instance().updates().setCheckUpdatesOnStartup(false); @@ -674,21 +667,6 @@ void MainWindow::startupConfigCheck() } } -void MainWindow::runFirstRunWizard() -{ - auto *wizard = new FirstRunWizard(this); - wizard->setAttribute(Qt::WA_DeleteOnClose); - - connect(wizard, &FirstRunWizard::cardDatabaseUpdateRequested, this, &MainWindow::actCheckCardUpdatesBackground); - connect(wizard, &FirstRunWizard::manualCardDatabaseSetupRequested, this, &MainWindow::actCheckCardUpdates); - connect(this, &MainWindow::cardDatabaseUpdateFinished, wizard, &FirstRunWizard::onCardDatabaseUpdateFinished); - connect(wizard, &FirstRunWizard::registerRequested, connectionController, &ConnectionController::registerToServer); - connect(wizard, &FirstRunWizard::connectRequested, connectionController, &ConnectionController::connectToServer); - - wizard->setModal(true); - wizard->show(); -} - /** * Drives the server-based startup destinations (Server lobby, Server Room) through the intent * system: fetch saved credentials, connect to the configured server, then land on the Lobby or @@ -1050,7 +1028,6 @@ void MainWindow::createCardUpdateProcess(bool background) QMessageBox::warning(this, tr("Error"), tr("Unable to run the card database updater: ") + dir.absoluteFilePath(binaryName)); exitCardDatabaseUpdate(); - emit cardDatabaseUpdateFinished(false); return; } @@ -1064,9 +1041,6 @@ void MainWindow::createCardUpdateProcess(bool background) void MainWindow::exitCardDatabaseUpdate() { - if (!cardUpdateProcess) { - return; - } cardUpdateProcess->deleteLater(); cardUpdateProcess = nullptr; statusBar()->clearMessage(); @@ -1104,17 +1078,14 @@ void MainWindow::cardUpdateError(QProcess::ProcessError err) exitCardDatabaseUpdate(); QMessageBox::warning(this, tr("Error"), tr("The card database updater exited with an error:\n%1").arg(error)); - emit cardDatabaseUpdateFinished(false); } -void MainWindow::cardUpdateFinished(int exitCode, QProcess::ExitStatus exitStatus) +void MainWindow::cardUpdateFinished(int, QProcess::ExitStatus exitStatus) { - const bool success = (exitStatus == QProcess::NormalExit) && (exitCode == 0); if (exitStatus == QProcess::NormalExit) { SettingsCache::instance().updates().setLastCardUpdateCheck(QDateTime::currentDateTime().date()); } exitCardDatabaseUpdate(); - emit cardDatabaseUpdateFinished(success); } void MainWindow::actCheckServerUpdates() diff --git a/cockatrice/src/interface/window_main.h b/cockatrice/src/interface/window_main.h index 920145552..baacd3096 100644 --- a/cockatrice/src/interface/window_main.h +++ b/cockatrice/src/interface/window_main.h @@ -64,10 +64,6 @@ class IntentUrlParser; class MainWindow : public QMainWindow { Q_OBJECT -signals: - /** @brief Emitted after the background card-database update subprocess exits. */ - void cardDatabaseUpdateFinished(bool success); - public slots: void actCheckCardUpdates(); void actCheckCardUpdatesBackground(); @@ -129,9 +125,6 @@ private: void createTrayIcon(); int getNextCustomSetPrefix(QDir dataDir); - - void runFirstRunWizard(); - inline QString getCardUpdaterBinaryName() { return "oracle"; @@ -147,8 +140,8 @@ private: QAction *aConnect, *aDisconnect, *aRegister, *aForgotPassword, *aSinglePlayer, *aWatchReplay, *aFullScreen; QAction *aManageSets, *aEditTokens, *aOpenCustomFolder, *aOpenCustomsetsFolder, *aAddCustomSet, *aReloadCardDatabase; - QAction *aTips, *aUpdate, *aCheckCardUpdates, *aCheckCardUpdatesBackground, *aFirstRunWizard, *aStatusBar, - *aViewLog, *aOpenSettingsFolder; + QAction *aTips, *aUpdate, *aCheckCardUpdates, *aCheckCardUpdatesBackground, *aStatusBar, *aViewLog, + *aOpenSettingsFolder; TabSupervisor *tabSupervisor; IntentUrlParser *urlParser; diff --git a/doc/doxygen/extra-pages/user_documentation/index.md b/doc/doxygen/extra-pages/user_documentation/index.md index 468a28f8d..d7e9d529d 100644 --- a/doc/doxygen/extra-pages/user_documentation/index.md +++ b/doc/doxygen/extra-pages/user_documentation/index.md @@ -11,11 +11,6 @@ - @subpage beta_release -## Troubleshooting - -- @subpage fixing_card_pictures -- @subpage enabling_debug_logs - ## Syntax Help - @subpage search_syntax_help diff --git a/doc/doxygen/extra-pages/user_documentation/troubleshooting/enabling_debug_logs.md b/doc/doxygen/extra-pages/user_documentation/troubleshooting/enabling_debug_logs.md deleted file mode 100644 index 7e4196a50..000000000 --- a/doc/doxygen/extra-pages/user_documentation/troubleshooting/enabling_debug_logs.md +++ /dev/null @@ -1,140 +0,0 @@ -@page enabling_debug_logs Enabling Debug Logs - -Cockatrice ships with a "diagnostics mode" that prints detailed debug messages about what the client is doing. This is -extremely useful when asking for help, because it shows exactly what happened: which URL the card picture loader tried, -whether it found or missed a file on disk, whether a download succeeded or failed, which redirects were followed, and -much more. Each of these messages belongs to a category, and you can enable or disable categories individually. - -Don't worry, this sounds more technical than it is. You only need to do two things: create one small text file, and tell -Cockatrice where it is. There are no installation steps and you can undo everything later (see [When you are -done](#when-you-are-done)). - -# Step 1: Create the file - -Open a plain text editor (Notepad on Windows, TextEdit on macOS, or any text editor on Linux) and paste the following -content: - -```ini -[Rules] -# The default log level is info -*.debug = false - -# Turn on debug level logs for the card picture loader and all its sub categories -card_picture_loader.* = true -``` - -Save the file with the exact name `qtlogging.ini` in a place you can find again, for example your Documents folder. - -\attention The file name matters: it must be `qtlogging.ini`, not `qtlogging.ini.txt`. If your text editor adds a -`.txt` extension automatically, you need to stop it from doing so (see below). On macOS, TextEdit must be switched to -plain text mode first via 'Format → Make Plain Text'. - -The file contains one rule per line. The `*.debug = false` rule turns off debug messages everywhere by default, and the -`card_picture_loader.* = true` line then re-enables them for the card picture loader. The `.*` at the end means "this -category and all of its sub categories". To enable a different category instead, just replace that line with the -category name of your choice, for example `card_database.loading = true` or `window_main.startup = true`. - -# Step 2: Tell Cockatrice where the file is - -Cockatrice does not know about the file yet. You have to point it there by setting an environment variable called -`QT_LOGGING_CONF` to the full location of your file. How to do this depends on your operating system: - -**Windows** - -1. Press the Windows key, type "environment variables", and open "Edit the system environment variables". -2. Click "Environment Variables...", then under "User variables" click "New...". -3. Set "Variable name" to `QT_LOGGING_CONF` and "Variable value" to the full path of your file, for example - `C:\Users\YourName\Documents\qtlogging.ini`. -4. Confirm all dialogs, then close and reopen Cockatrice. - -Alternatively, if Cockatrice is installed in a folder you can write to, you can simply place the `qtlogging.ini` file -directly next to the Cockatrice executable (in the same folder as `cockatrice.exe`) and skip the environment variable -altogether. Note that this copy may be replaced when you update the client. - -**macOS** - -Open the Terminal app (it is in 'Applications → Utilities') and run the following two commands, replacing the path -with the full location of your file: - -```shell -launchctl setenv QT_LOGGING_CONF /path/to/qtlogging.ini -open -a Cockatrice -``` - -You can also use `export QT_LOGGING_CONF=/path/to/qtlogging.ini` to set the variable for the current terminal session. - -The setting stays active until you log out or restart your Mac. If you have multiple users on the same Mac, be aware -that this setting only applies to your user account. - -**Linux** - -For a quick test, open a terminal and start Cockatrice with the file on the command line, replacing the path with the -full location of your file: - -```shell -QT_LOGGING_CONF=/path/to/qtlogging.ini cockatrice -``` - -If this works and you want it to apply every time you start Cockatrice, add the following line to your `~/.profile` -file and log in again: - -```shell -export QT_LOGGING_CONF="/path/to/qtlogging.ini" -``` - -# Step 3: See the logs - -Now that debug logging is enabled, open Cockatrice and trigger the behavior you are investigating, for example by -opening a deck, reloading the card database, or starting a game. - -The easiest way to see the logs is to use the built-in log viewer inside Cockatrice itself: open 'Help → View Debug -Log'. A window appears that shows the log messages live and keeps the most recent entries. It even has a 'Copy to -clipboard' button so you can paste the output into a bug report or a Discord message. This works the same on every -operating system. - -If you prefer to capture everything to a file instead, start Cockatrice with the `--debug-output` option: - -```shell -cockatrice --debug-output -``` - -Cockatrice then writes the full log to a file called `qdebug.txt` in the folder it was started from. - -# Which categories are available? - -Every message Cockatrice logs belongs to a category. The following table lists the most useful ones for troubleshooting, -grouped by area. Enable a category by adding a line like `category = true` to your `qtlogging.ini` file (or use a `.*` -suffix, e.g. `card_picture_loader.*`, to include all sub categories). - -| What you want to see | Categories | -| ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | -| Card picture loading (URLs, local file hits/misses, downloads, redirects) | `card_picture_loader.*` | -| Card database loading and parsing | `card_database`, `card_database.loading`, `card_database.loading.success_or_failure`, `cockatrice_xml.*` | -| Card, set, and deck information | `card_info`, `card_list`, `deck_loader` | -| Startup sequence and update checks | `window_main.startup.*`, `release_channel`, `spoiler_background_updater` | -| User interface and themes | `theme_manager`, `sound_engine`, `flow_layout`, `flow_widget.*`, `pixel_map_generator`, `card_info_picture_widget` | -| Networking and servers | `local_client`, `remote_client`, `tapped_out_interface`, `servers_settings` | -| In-game logic | `player`, `game_scene.*`, `card_zone.*`, `view_zone`, `game_event_handler` | -| Dialogs and tabs | `dlg_settings`, `dlg_update`, `dlg_tip_of_the_day`, `tab_game`, `tab_message`, `tab_supervisor` | -| Settings and shortcuts | `settings_cache`, `shortcuts_settings` | -| Deck and card filtering | `filter_string`, `deck_filter_string`, `syntax_help` | - -For example, to investigate why a card database update seems to fail, enable the card database categories: - -```ini -[Rules] -*.debug = false - -card_database = true -card_database.loading = true -card_database.loading.success_or_failure = true -cockatrice_xml.* = true -``` - -# When you are done - -To turn the diagnostics back off, just reverse what you did: remove the `QT_LOGGING_CONF` environment variable (or -unset it again via `launchctl unsetenv QT_LOGGING_CONF` on macOS) and/or delete the `qtlogging.ini` file, then restart -Cockatrice. Leaving it on is harmless, but the extra logging can make the client slightly slower. - -For the full details on how Cockatrice logging works, including the complete list of categories, see @ref logging. diff --git a/doc/doxygen/extra-pages/user_documentation/troubleshooting/fixing_card_pictures.md b/doc/doxygen/extra-pages/user_documentation/troubleshooting/fixing_card_pictures.md deleted file mode 100644 index 78ba5586b..000000000 --- a/doc/doxygen/extra-pages/user_documentation/troubleshooting/fixing_card_pictures.md +++ /dev/null @@ -1,109 +0,0 @@ -@page fixing_card_pictures Fixing Card Pictures - -This guide collects the common causes for card pictures not showing up, showing the wrong printing, or showing the -card back instead of the artwork, and how to fix them. - -Work through the steps in order. In most cases the problem is caused by an outdated client, misconfigured download -URLs, stale local images, or a corrupted cache. - -# Update Your Client - -Picture handling bugs are fixed on a regular basis, so the first thing to try is updating your client. - -Use the update check in 'Help → Check for Updates' (or look for the update prompt shown on startup). - -If a fix has not yet made it into the latest stable release, it may already be available in the beta release. The beta -ships very frequently and usually receives a follow-up fix within a day or two if something breaks. - -See @subpage beta_release for instructions on how to switch to the beta channel. - -# Check Your Download URLs - -Card pictures are downloaded from a list of URL templates. Each template is tried in order until one produces a valid -image, so the order matters: URLs at the top of the list are tried first. - -The list can be found in 'Cockatrice → Settings' (or Ctrl + Shift + P by default), on the 'Deck Editor' tab, in the -'URL Download Priority' section. Make sure 'Download card pictures on the fly' is enabled and that the list contains -valid URLs. If you suspect the list has been modified or corrupted, press 'Reset Download URLs' to restore the -defaults. - -For information on how to add your own custom URL templates, see the 'How to add a custom URL' link in the same -settings section. - -# Check Your Local Picture Folder - -Before any network request is made, Cockatrice looks for local image files. If a matching file is found on disk it is -shown instead of anything downloaded, even if it is the wrong image. - -The pictures directory is configured on the 'General' settings tab, under 'Directories' → 'Pictures directory'. -Cockatrice checks the following locations, in order: - -- The custom pictures folder (recursively indexed by file name). -- `//` -- `/downloadedPics//` - -The following import naming schemes are recognized (using both `_` and `-` as separators): - -| Scheme | Pattern | -| --------------------------- | -------------------------- | -| Card Name + Provider ID | `{name}_{providerId}` | -| Card Name + Set + Collector | `{name}_{set}_{collector}` | -| Set + Collector + Card Name | `{set}_{collector}_{name}` | -| Card Name + Set | `{name}_{set}` | -| Card Name | `{name}` | - -If a picture you downloaded or placed manually is wrong, stale, or corrupted, delete the offending file. Pay special -attention to the `downloadedPics` subfolder: this is where the filesystem caching method writes downloaded images, and -after a provider outage it can permanently contain the wrong printing until you delete it manually. - -See @ref loading_card_pictures for details on how local images are loaded. - -# Clear Caches - -Cockatrice caches card pictures in three places. All of them can be managed on the 'Storage' settings tab: - -- **Network cache** — downloaded images stored on disk. Press 'Delete Cached Images' to clear it. -- **Filesystem / image backup** — downloaded images written directly to `downloadedPics`. Press 'Delete Saved Images' - to clear it. -- **In-memory (pixmap) cache** — images currently held in RAM. Press 'Clear In-Memory Images' to clear it. - -If a provider outage caused the wrong pictures to be downloaded and cached, clearing the network cache (and the -'Delete Saved Images' button if you use the filesystem caching method) will force Cockatrice to download the correct -images again. The redirect cache TTL (also on the Storage tab) controls how long previously seen redirects for -download URLs are remembered; lowering it can help if a URL used to redirect somewhere else. - -# Restart the Client - -After updating the client, changing the download URLs, moving or deleting local image files, or clearing caches, it is -recommended to restart Cockatrice so that all changes are fully picked up. - -# Check Logs - -Before changing any settings, check the existing logs first. Rate limit errors and most download errors are already -logged at warn level, so you may find the cause without enabling debug mode. - -Open 'Help → View Debug Log' and look for error or warning messages related to card picture loading. If you need more -detail than the default log level provides, see below. - -# Enable Picture Loader Debug Logs - -If the steps above did not solve the problem, you can turn on a "diagnostics mode" that prints what the picture loader -is actually doing: which URL it is trying, whether it found or missed a file on disk, whether the download succeeded or -failed, and which redirects it followed. This information is extremely useful when asking for help. - -See @subpage enabling_debug_logs for a step-by-step guide on how to enable the logs, including instructions -for Windows, macOS, and Linux. - -# Provider Accuracy - -Cards in Cockatrice are identified by a provider ID, which is the Scryfall UUID of a specific printing. Decks store -this ID, which is why the exact printing a card was added as can be looked up again. - -The Scryfall URL templates built into Cockatrice use this provider ID directly (`!set:uuid!`), so they always download -the exact printing that was requested. - -The Gatherer URL templates, on the other hand, do **not** use the provider ID. They resolve pictures by multiverse ID -(`!set:muid!`) or by card name (`!name!`) only. As a result they may return a different printing than the one the -provider ID refers to, or no picture at all for cards Gatherer does not know. If you need pictures to match the exact -printing of a card, make sure the Scryfall URLs are at the top of your download URL priority list and consider removing -or demoting the Gatherer URLs.