diff --git a/GWF_PowerupInjector.pro b/GWF_PowerupInjector.pro index 3dc3aab..594f635 100644 --- a/GWF_PowerupInjector.pro +++ b/GWF_PowerupInjector.pro @@ -1,17 +1,21 @@ -QT += core gui widgets +QT += core widgets gui CONFIG += c++17 +RC_ICONS = app.ico + # You can make your code fail to compile if it uses deprecated APIs. # In order to do so, uncomment the following line. #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 SOURCES += \ main.cpp \ - mainwindow.cpp + mainwindow.cpp \ + splashscreen.cpp HEADERS += \ - mainwindow.h + mainwindow.h \ + splashscreen.h FORMS += \ mainwindow.ui diff --git a/app.ico b/app.ico new file mode 100644 index 0000000..22f2c6a Binary files /dev/null and b/app.ico differ diff --git a/main.cpp b/main.cpp index fd3e533..8cf72cf 100644 --- a/main.cpp +++ b/main.cpp @@ -1,11 +1,42 @@ #include "mainwindow.h" +#include "splashscreen.h" #include +#include int main(int argc, char *argv[]) { QApplication a(argc, argv); + + QCoreApplication::setOrganizationDomain("redline.llc"); + QCoreApplication::setOrganizationName("RedLine Solutions, LLC."); + QCoreApplication::setApplicationName("GWF PowerUp Injector"); + QCoreApplication::setApplicationVersion("v1.1.0"); + + // Show splash screen + SplashScreen splash; + splash.show(); + splash.startAnimation(); + a.processEvents(); + + // Create main window (doesn't load maps yet) MainWindow w; - w.show(); + + // Connect progress signals to splash screen + QObject::connect(&w, &MainWindow::loadingProgress, + [&splash](int percent, const QString &status) { + splash.setProgress(percent); + splash.setStatus(status); + }); + + QObject::connect(&w, &MainWindow::loadingComplete, [&]() { + // Small delay to show 100% progress + QThread::msleep(300); + splash.finish(&w); + }); + + // Start async initialization (loads maps with progress reporting) + w.initializeAsync(); + return a.exec(); } diff --git a/mainwindow.cpp b/mainwindow.cpp index ab6f5c2..a4099fe 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -1,6 +1,7 @@ #include "mainwindow.h" #include "ui_mainwindow.h" +#include #include MainWindow::MainWindow(QWidget *parent) @@ -9,22 +10,45 @@ MainWindow::MainWindow(QWidget *parent) , mSteamPath() { ui->setupUi(this); + // Don't load maps here - call initializeAsync() after showing splash screen +} + +void MainWindow::initializeAsync() +{ + emit loadingProgress(5, "Finding Steam installation..."); + QCoreApplication::processEvents(); findSteamPath(); + emit loadingProgress(10, "Scanning workshop directory..."); + QCoreApplication::processEvents(); + QDir gwfWorkshopDir(mSteamPath + "/steamapps/workshop/content/431240"); - if (!gwfWorkshopDir.exists()) { return; } + if (!gwfWorkshopDir.exists()) + { + emit loadingProgress(100, "Ready (no maps found)"); + emit loadingComplete(); + return; + } qDebug() << "Found Golf With Friends Workshop Path:" << gwfWorkshopDir.path(); - foreach (const QString entry, gwfWorkshopDir.entryList({}, QDir::NoFilter, QDir::Time)) + QStringList entries = gwfWorkshopDir.entryList({}, QDir::NoDotAndDotDot | QDir::Dirs, QDir::Time); + int totalEntries = entries.size(); + int processed = 0; + + foreach (const QString entry, entries) { - if (entry == "." || entry == "..") { continue; } + // Calculate and emit progress (10-90%) + int progress = 10 + (processed * 80 / qMax(totalEntries, 1)); + emit loadingProgress(progress, QString("Loading map: %1").arg(entry)); + QCoreApplication::processEvents(); QFile newEntry(gwfWorkshopDir.path() + "/" + entry + "/Map"); if (!newEntry.open(QIODevice::ReadWrite)) { qDebug() << "Failed to open workshop map:" << entry; + processed++; continue; } const QByteArray rawMapData = newEntry.readAll(); @@ -52,28 +76,65 @@ MainWindow::MainWindow(QWidget *parent) newInfo.mMapData = loadObj; mLevels.push_back(newInfo); + processed++; } + emit loadingProgress(95, "Updating level list..."); + QCoreApplication::processEvents(); + updateLevels(); + + emit loadingProgress(100, "Ready!"); + emit loadingComplete(); } void MainWindow::findSteamPath() { - QDir testDir("C:/Program Files (x86)/Steam"); - if (testDir.exists()) + // Check multiple common Steam installation locations + QStringList possiblePaths = { + "C:/Program Files (x86)/Steam", + "C:/Program Files/Steam", + "D:/Steam", + "D:/Games/Steam", + "E:/Steam", + "E:/Games/Steam" + }; + + for (const QString &path : possiblePaths) { - mSteamPath = testDir.path(); + QDir testDir(path); + if (testDir.exists()) + { + mSteamPath = testDir.path(); + break; + } } - else + + // If not found in common locations, ask user + if (mSteamPath.isEmpty()) { - mSteamPath = QFileDialog::getExistingDirectory(this, "Select Steam Directory", "C:/Program Files (x86)/", QFileDialog::ShowDirsOnly); + mSteamPath = QFileDialog::getExistingDirectory(this, "Select Steam Directory", + "C:/Program Files (x86)/", QFileDialog::ShowDirsOnly); } if (!mSteamPath.isEmpty()) { - ui->lineEdit_SteamPath->setText(QString("FOUND: %1").arg(mSteamPath)); + // Verify the GWF workshop folder exists + QDir gwfDir(mSteamPath + "/steamapps/workshop/content/431240"); + if (!gwfDir.exists()) + { + ui->lineEdit_SteamPath->setText(QString("FOUND: %1 (No GWF maps)").arg(mSteamPath)); + } + else + { + ui->lineEdit_SteamPath->setText(QString("FOUND: %1").arg(mSteamPath)); + } ui->lineEdit_SteamPath->setEnabled(false); } + else + { + ui->lineEdit_SteamPath->setText("NOT FOUND - Please install Steam or GWF workshop maps"); + } } void MainWindow::updateLevels() @@ -162,7 +223,14 @@ MainWindow::~MainWindow() void MainWindow::on_listWidget_MapSelect_currentRowChanged(int currentRow) { - LevelInfo selectedLevel = mLevels[currentRow]; + // Bounds checking to prevent crashes + if (currentRow < 0 || currentRow >= mLevels.size()) + { + clearMapDetails(); + return; + } + + const LevelInfo &selectedLevel = mLevels[currentRow]; ui->lineEdit_LevelName->setText(selectedLevel.mLevelName); ui->lineEdit_Description->setText(selectedLevel.mDescription); ui->lineEdit_ID->setText(selectedLevel.mPublishedID); @@ -173,6 +241,9 @@ void MainWindow::on_listWidget_MapSelect_currentRowChanged(int currentRow) QPixmap scaledPreview = selectedLevel.mPreview.scaled(ui->label_MapPreview->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); ui->label_MapPreview->setPixmap(scaledPreview); + // Clear the powerup list before adding new items (fixes accumulation bug) + ui->listWidget_powerups->clear(); + for (int i = 0; i < selectedLevel.mPowerUps.size(); i++) { ui->listWidget_powerups->addItem(QString("Powerup %1").arg(i)); @@ -182,8 +253,25 @@ void MainWindow::on_listWidget_MapSelect_currentRowChanged(int currentRow) void MainWindow::on_listWidget_powerups_currentRowChanged(int currentRow) { - LevelInfo selectedLevel = mLevels[ui->listWidget_MapSelect->currentRow()]; - PowerUpInfo selectedPowerUp = selectedLevel.mPowerUps[currentRow]; + int mapRow = ui->listWidget_MapSelect->currentRow(); + + // Bounds checking for map selection + if (mapRow < 0 || mapRow >= mLevels.size()) + { + clearPowerupDetails(); + return; + } + + const LevelInfo &selectedLevel = mLevels[mapRow]; + + // Bounds checking for powerup selection + if (currentRow < 0 || currentRow >= selectedLevel.mPowerUps.size()) + { + clearPowerupDetails(); + return; + } + + const PowerUpInfo &selectedPowerUp = selectedLevel.mPowerUps[currentRow]; ui->doubleSpinBox_PosX->setValue(selectedPowerUp.mPosition.x()); ui->doubleSpinBox_PosY->setValue(selectedPowerUp.mPosition.y()); @@ -204,20 +292,26 @@ void MainWindow::saveMap(LevelInfo aLevelInfo, bool aBackup) const QString mapPath = aLevelInfo.mMapPath; if (!QFile::exists(mapPath)) { - qDebug() << "File doesn't exist: " << mapPath; + QMessageBox::critical(this, "Save Error", + QString("Map file not found:\n%1").arg(mapPath)); return; } const QString backupMapPath = aLevelInfo.mMapPath + ".old"; if (!QFile::exists(backupMapPath) && aBackup) { - QFile::copy(mapPath, backupMapPath); + if (!QFile::copy(mapPath, backupMapPath)) + { + QMessageBox::warning(this, "Backup Warning", + "Could not create backup file. Proceeding without backup."); + } } QFile backupMapFile(backupMapPath); if (!backupMapFile.open(QIODevice::ReadOnly)) { - qDebug() << "Failed to open backup file: " << mapPath; + QMessageBox::critical(this, "Save Error", + QString("Failed to open backup file for reading:\n%1").arg(backupMapPath)); return; } @@ -227,16 +321,16 @@ void MainWindow::saveMap(LevelInfo aLevelInfo, bool aBackup) QFile mapFile(mapPath); if (!mapFile.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { - qDebug() << "Failed to open file: " << mapPath; + QMessageBox::critical(this, "Save Error", + QString("Failed to open map file for writing:\n%1").arg(mapPath)); return; } - QByteArray powerUpsData = ","; for (int i = 0; i < aLevelInfo.mPowerUps.size(); i++) { - PowerUpInfo powerUp = aLevelInfo.mPowerUps[i]; + const PowerUpInfo &powerUp = aLevelInfo.mPowerUps[i]; const QString powerUpData = QString("{\"sType\":%1,\"pX\":%2,\"pY\":%3,\"pZ\":%4,\"rW\":%5,\"rX\":%6,\"rY\":%7,\"rZ\":%8,\"sX\":%9,\"sY\":%10,\"sZ\":%11,\"obName\":\"Powerup Spawner\",\"photonData\":{\"photonViewID\":[%12]}},") .arg(0) .arg(powerUp.mPosition.x(), 0, 'f', 1) @@ -261,19 +355,69 @@ void MainWindow::saveMap(LevelInfo aLevelInfo, bool aBackup) mapData.append(powerUpsData); mapData.append(backupData.mid(injectionPoint)); - mapFile.write(mapData); + if (mapFile.write(mapData) == -1) + { + QMessageBox::critical(this, "Save Error", + QString("Failed to write map data:\n%1").arg(mapPath)); + mapFile.close(); + return; + } + mapFile.close(); + qDebug() << "Map saved successfully:" << mapPath; } void MainWindow::on_pushButton_Generate_clicked() { int currentListRow = ui->listWidget_MapSelect->currentRow(); + + // Bounds check + if (currentListRow < 0 || currentListRow >= mLevels.size()) + { + QMessageBox::warning(this, "No Map Selected", + "Please select a map before generating powerups."); + return; + } + LevelInfo selectedLevel = mLevels[currentListRow]; + // Check if powerups already exist and ask user what to do + if (!selectedLevel.mPowerUps.isEmpty()) + { + QMessageBox::StandardButton reply = QMessageBox::question(this, + "Existing Powerups Found", + QString("This map already has %1 powerup(s).\n\n" + "Do you want to replace them with new generated powerups?\n\n" + "Click 'Yes' to replace, 'No' to add to existing, or 'Cancel' to abort.") + .arg(selectedLevel.mPowerUps.size()), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, + QMessageBox::Cancel); + + if (reply == QMessageBox::Cancel) + { + return; + } + if (reply == QMessageBox::Yes) + { + selectedLevel.mPowerUps.clear(); + } + // If No, keep existing and add new ones + } + QVector shuffledPositions = selectedLevel.mObjectPositions; + + if (shuffledPositions.isEmpty()) + { + QMessageBox::warning(this, "No Base Objects", + "This map has no base objects to use as spawn positions.\n" + "Powerups are generated near objects with 'base' in their name."); + return; + } + auto rng = QRandomGenerator::global(); std::shuffle(shuffledPositions.begin(), shuffledPositions.end(), *rng); + int generatedCount = 0; for (int i = 0; i < shuffledPositions.size(); i++) { if (i == MAX_POWERUPS) @@ -295,14 +439,246 @@ void MainWindow::on_pushButton_Generate_clicked() PowerUpInfo newPowerUp; newPowerUp.mPosition = QVector3D(pos.x(), pos.y() + 0.25, pos.z()); + newPowerUp.mRotation = QVector4D(0, 0, 0, 1); // Identity quaternion newPowerUp.mScale = QVector3D(1, 1, 1); selectedLevel.mPowerUps.push_back(newPowerUp); + generatedCount++; } } + saveMap(selectedLevel, true); - mLevels[ui->listWidget_MapSelect->currentRow()] = selectedLevel; + mLevels[currentListRow] = selectedLevel; - ui->listWidget_MapSelect->setCurrentRow(currentListRow); + // Refresh the UI to show the new powerups + ui->listWidget_MapSelect->setCurrentRow(-1); // Deselect first + ui->listWidget_MapSelect->setCurrentRow(currentListRow); // Reselect to refresh + + ui->statusBar->showMessage(QString("Generated %1 powerups successfully!").arg(generatedCount), 5000); +} + +void MainWindow::clearMapDetails() +{ + ui->lineEdit_LevelName->clear(); + ui->lineEdit_Description->clear(); + ui->lineEdit_ID->clear(); + ui->spinBox_Music->setValue(0); + ui->spinBox_Skybox->setValue(0); + ui->spinBox_PowerupCount->setValue(0); + ui->label_MapPreview->clear(); + ui->listWidget_powerups->clear(); + clearPowerupDetails(); +} + +void MainWindow::clearPowerupDetails() +{ + ui->doubleSpinBox_PosX->setValue(0); + ui->doubleSpinBox_PosY->setValue(0); + ui->doubleSpinBox_PosZ->setValue(0); + ui->doubleSpinBox_RotW->setValue(0); + ui->doubleSpinBox_RotX->setValue(0); + ui->doubleSpinBox_RotY->setValue(0); + ui->doubleSpinBox_RotZ->setValue(0); + ui->doubleSpinBox_ScaleX->setValue(0); + ui->doubleSpinBox_ScaleY->setValue(0); + ui->doubleSpinBox_ScaleZ->setValue(0); +} + +void MainWindow::refreshPowerupList(int mapRow) +{ + if (mapRow < 0 || mapRow >= mLevels.size()) + { + ui->listWidget_powerups->clear(); + return; + } + + ui->listWidget_powerups->clear(); + const LevelInfo &level = mLevels[mapRow]; + for (int i = 0; i < level.mPowerUps.size(); i++) + { + ui->listWidget_powerups->addItem(QString("Powerup %1").arg(i)); + } + ui->spinBox_PowerupCount->setValue(level.mPowerUps.size()); +} + +void MainWindow::on_pushButton_SavePowerUp_clicked() +{ + int mapRow = ui->listWidget_MapSelect->currentRow(); + int powerupRow = ui->listWidget_powerups->currentRow(); + + if (mapRow < 0 || mapRow >= mLevels.size()) + { + QMessageBox::warning(this, "No Map Selected", + "Please select a map first."); + return; + } + + if (powerupRow < 0 || powerupRow >= mLevels[mapRow].mPowerUps.size()) + { + QMessageBox::warning(this, "No Powerup Selected", + "Please select a powerup to save."); + return; + } + + // Read values from UI and update the powerup + PowerUpInfo &powerup = mLevels[mapRow].mPowerUps[powerupRow]; + powerup.mPosition.setX(ui->doubleSpinBox_PosX->value()); + powerup.mPosition.setY(ui->doubleSpinBox_PosY->value()); + powerup.mPosition.setZ(ui->doubleSpinBox_PosZ->value()); + powerup.mRotation.setW(ui->doubleSpinBox_RotW->value()); + powerup.mRotation.setX(ui->doubleSpinBox_RotX->value()); + powerup.mRotation.setY(ui->doubleSpinBox_RotY->value()); + powerup.mRotation.setZ(ui->doubleSpinBox_RotZ->value()); + powerup.mScale.setX(ui->doubleSpinBox_ScaleX->value()); + powerup.mScale.setY(ui->doubleSpinBox_ScaleY->value()); + powerup.mScale.setZ(ui->doubleSpinBox_ScaleZ->value()); + + // Save to disk + saveMap(mLevels[mapRow], true); + + ui->statusBar->showMessage("Powerup saved successfully!", 3000); +} + +void MainWindow::on_pushButton_NewPowerUp_clicked() +{ + int mapRow = ui->listWidget_MapSelect->currentRow(); + + if (mapRow < 0 || mapRow >= mLevels.size()) + { + QMessageBox::warning(this, "No Map Selected", + "Please select a map before adding a powerup."); + return; + } + + // Create new powerup at origin with default values + PowerUpInfo newPowerup; + newPowerup.mPosition = QVector3D(0, 0.25, 0); + newPowerup.mRotation = QVector4D(0, 0, 0, 1); // Identity quaternion + newPowerup.mScale = QVector3D(1, 1, 1); + + mLevels[mapRow].mPowerUps.append(newPowerup); + + // Refresh the powerup list + refreshPowerupList(mapRow); + + // Select the new powerup + ui->listWidget_powerups->setCurrentRow(mLevels[mapRow].mPowerUps.size() - 1); + + ui->statusBar->showMessage("New powerup added. Edit values and click Save.", 3000); +} + +void MainWindow::on_pushButton_DeletePowerUp_clicked() +{ + int mapRow = ui->listWidget_MapSelect->currentRow(); + int powerupRow = ui->listWidget_powerups->currentRow(); + + if (mapRow < 0 || mapRow >= mLevels.size()) + { + QMessageBox::warning(this, "No Map Selected", + "Please select a map first."); + return; + } + + if (powerupRow < 0 || powerupRow >= mLevels[mapRow].mPowerUps.size()) + { + QMessageBox::warning(this, "No Powerup Selected", + "Please select a powerup to delete."); + return; + } + + QMessageBox::StandardButton reply = QMessageBox::question(this, + "Confirm Delete", + QString("Are you sure you want to delete Powerup %1?").arg(powerupRow), + QMessageBox::Yes | QMessageBox::No, + QMessageBox::No); + + if (reply == QMessageBox::Yes) + { + mLevels[mapRow].mPowerUps.remove(powerupRow); + + // Save to disk + saveMap(mLevels[mapRow], true); + + // Refresh powerup list + refreshPowerupList(mapRow); + clearPowerupDetails(); + + ui->statusBar->showMessage("Powerup deleted successfully!", 3000); + } +} + +void MainWindow::on_pushButton_Restore_clicked() +{ + int mapRow = ui->listWidget_MapSelect->currentRow(); + + if (mapRow < 0 || mapRow >= mLevels.size()) + { + QMessageBox::warning(this, "No Map Selected", + "Please select a map to restore."); + return; + } + + QString backupPath = mLevels[mapRow].mMapPath + ".old"; + if (!QFile::exists(backupPath)) + { + QMessageBox::information(this, "No Backup", + "No backup file exists for this map.\n" + "Backups are created automatically when you generate or modify powerups."); + return; + } + + QMessageBox::StandardButton reply = QMessageBox::question(this, + "Restore Backup", + "This will restore the map to its original state before any modifications.\n\n" + "All powerup changes will be lost. Are you sure you want to continue?", + QMessageBox::Yes | QMessageBox::No, + QMessageBox::No); + + if (reply == QMessageBox::Yes) + { + QString mapPath = mLevels[mapRow].mMapPath; + + // Remove current map file + if (!QFile::remove(mapPath)) + { + QMessageBox::critical(this, "Restore Error", + "Failed to remove current map file."); + return; + } + + // Copy backup to map file + if (!QFile::copy(backupPath, mapPath)) + { + QMessageBox::critical(this, "Restore Error", + "Failed to restore backup file."); + return; + } + + // Reload the map data + QFile mapFile(mapPath); + if (mapFile.open(QIODevice::ReadOnly)) + { + const QByteArray rawMapData = mapFile.readAll(); + size_t headerSize = rawMapData.indexOf('{'); + size_t footerSize = rawMapData.size() - rawMapData.lastIndexOf('}') - 1; + size_t payloadSize = rawMapData.size() - headerSize - footerSize; + const QByteArray mapData = rawMapData.mid(headerSize, payloadSize); + + QJsonDocument loadDoc = QJsonDocument::fromJson(mapData); + QJsonObject loadObj = loadDoc.object(); + + mLevels[mapRow].mPowerUps = findPowerUps(loadObj); + mLevels[mapRow].mMaxViewID = getMaxViewID(loadObj); + mLevels[mapRow].mMapData = loadObj; + + mapFile.close(); + } + + // Refresh UI + refreshPowerupList(mapRow); + clearPowerupDetails(); + + ui->statusBar->showMessage("Map restored from backup successfully!", 3000); + } } diff --git a/mainwindow.h b/mainwindow.h index fec65a7..02625e5 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -8,12 +8,16 @@ #include #include #include +#include +#include #include #include #include #include +#include + struct PowerUpInfo { QVector3D mPosition; @@ -53,22 +57,34 @@ public: MainWindow(QWidget *parent = nullptr); ~MainWindow(); + void initializeAsync(); // Async initialization for splash screen + void findSteamPath(); void updateLevels(); QVector findPowerUps(QJsonObject aMapData); QVector getObjectPositions(QJsonObject aObj); void saveMap(LevelInfo aLevelInfo, bool aBackup = true); - QByteArray serializePowerUpCompact(const PowerUpInfo &p, int photonId); int getMaxViewID(QJsonObject aMapData); + +signals: + void loadingProgress(int percent, const QString &status); + void loadingComplete(); + private slots: void on_listWidget_MapSelect_currentRowChanged(int currentRow); - void on_listWidget_powerups_currentRowChanged(int currentRow); - void on_pushButton_Generate_clicked(); + void on_pushButton_SavePowerUp_clicked(); + void on_pushButton_NewPowerUp_clicked(); + void on_pushButton_DeletePowerUp_clicked(); + void on_pushButton_Restore_clicked(); private: + void clearMapDetails(); + void clearPowerupDetails(); + void refreshPowerupList(int mapRow); + Ui::MainWindow *ui; QString mSteamPath; diff --git a/mainwindow.ui b/mainwindow.ui index a66b0bf..7a9e0a7 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -17,7 +17,7 @@ - MainWindow + Golf With Your Friends PowerUp Injector @@ -252,9 +252,6 @@ - - false - -10000.000000000000000 @@ -275,9 +272,6 @@ - - false - -10000.000000000000000 @@ -298,9 +292,6 @@ - - false - -10000.000000000000000 @@ -321,9 +312,6 @@ - - false - -10000.000000000000000 @@ -344,9 +332,6 @@ - - false - -10000.000000000000000 @@ -367,9 +352,6 @@ - - false - -10000.000000000000000 @@ -390,9 +372,6 @@ - - false - -10000.000000000000000 @@ -434,9 +413,6 @@ - - false - -10000.000000000000000 @@ -457,9 +433,6 @@ - - false - -10000.000000000000000 @@ -480,9 +453,6 @@ - - false - -10000.000000000000000 @@ -513,9 +483,6 @@ - - false - Save Powerup @@ -523,14 +490,18 @@ - - false - New Powerup + + + + Delete + + + @@ -547,6 +518,13 @@ + + + + Restore Backup + + + @@ -588,17 +566,7 @@ - - - - 0 - 0 - 1330 - 21 - - - - + diff --git a/splashscreen.cpp b/splashscreen.cpp new file mode 100644 index 0000000..11c4142 --- /dev/null +++ b/splashscreen.cpp @@ -0,0 +1,338 @@ +#include "splashscreen.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +SplashScreen::SplashScreen(QWidget *parent) + : QSplashScreen() + , mAnimationTimer(new QTimer(this)) +{ + Q_UNUSED(parent); + + // Create transparent pixmap + QPixmap pixmap(WIDTH, HEIGHT); + pixmap.fill(Qt::transparent); + setPixmap(pixmap); + + setWindowFlags(Qt::SplashScreen | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint); + setAttribute(Qt::WA_TranslucentBackground); + + // Center on screen + if (QScreen *screen = QApplication::primaryScreen()) { + QRect screenGeometry = screen->geometry(); + int x = (screenGeometry.width() - WIDTH) / 2; + int y = (screenGeometry.height() - HEIGHT) / 2; + move(x, y); + } + + // Connect animation timer + connect(mAnimationTimer, &QTimer::timeout, this, &SplashScreen::onAnimationTick); +} + +SplashScreen::~SplashScreen() +{ + stopAnimation(); +} + +void SplashScreen::setStatus(const QString &message) +{ + mStatus = message; + repaint(); + QApplication::processEvents(); +} + +void SplashScreen::setProgress(int value, int max) +{ + mProgress = value; + mProgressMax = max; + repaint(); + QApplication::processEvents(); +} + +void SplashScreen::startAnimation() +{ + mAnimationTimer->start(16); // ~60 FPS +} + +void SplashScreen::stopAnimation() +{ + mAnimationTimer->stop(); +} + +void SplashScreen::finish(QWidget *mainWindow) +{ + stopAnimation(); + if (mainWindow) { + mainWindow->show(); + } + close(); +} + +void SplashScreen::onAnimationTick() +{ + mAnimationPhase += 0.08f; + if (mAnimationPhase > 2 * M_PI) { + mAnimationPhase -= 2 * M_PI; + } + repaint(); +} + +void SplashScreen::mousePressEvent(QMouseEvent *event) +{ + Q_UNUSED(event); + // Allow clicking to dismiss during loading if desired +} + +void SplashScreen::keyPressEvent(QKeyEvent *event) +{ + Q_UNUSED(event); + // Allow key press to dismiss during loading if desired +} + +void SplashScreen::drawContents(QPainter *painter) +{ + painter->setRenderHint(QPainter::Antialiasing, true); + painter->setRenderHint(QPainter::TextAntialiasing, true); + + drawBackground(painter); + drawPowerupIcons(painter); + drawTitle(painter); + + // Draw bouncing golf ball + int ballX = 400; + int ballY = 85 + static_cast(qSin(mAnimationPhase) * 15); + drawGolfBall(painter, ballX, ballY, 25); + + drawProgressBar(painter); + drawStatusText(painter); +} + +void SplashScreen::drawBackground(QPainter *painter) +{ + // Create clipping path for rounded corners + QPainterPath clipPath; + clipPath.addRoundedRect(0, 0, WIDTH, HEIGHT, 20, 20); + painter->setClipPath(clipPath); + + // Draw gradient background (golf course green) + QLinearGradient gradient(0, 0, 0, HEIGHT); + gradient.setColorAt(0, mGreenLight); + gradient.setColorAt(1, mGreenDark); + painter->setBrush(gradient); + painter->setPen(Qt::NoPen); + painter->drawRect(0, 0, WIDTH, HEIGHT); + + // Draw subtle grass texture lines + painter->setPen(QPen(mGreenDark.darker(110), 1)); + for (int y = 20; y < HEIGHT - 60; y += 12) { + int offset = static_cast(qSin(y * 0.1f + mAnimationPhase * 0.5f) * 3); + painter->drawLine(10 + offset, y, WIDTH - 10 + offset, y); + } + + // Draw decorative border + painter->setClipping(false); + painter->setPen(QPen(mGreenDark.darker(130), 3)); + painter->setBrush(Qt::NoBrush); + painter->drawRoundedRect(1, 1, WIDTH - 2, HEIGHT - 2, 20, 20); +} + +void SplashScreen::drawGolfBall(QPainter *painter, int x, int y, int radius) +{ + // Draw shadow + painter->setBrush(QColor(0, 0, 0, 50)); + painter->setPen(Qt::NoPen); + painter->drawEllipse(x - radius + 5, y - radius + 8, radius * 2, radius * 2 - 5); + + // Draw golf ball with gradient + QRadialGradient ballGradient(x - radius/3, y - radius/3, radius * 1.5); + ballGradient.setColorAt(0, QColor(255, 255, 255)); + ballGradient.setColorAt(0.7, QColor(240, 240, 240)); + ballGradient.setColorAt(1, QColor(200, 200, 200)); + painter->setBrush(ballGradient); + painter->setPen(QPen(QColor(180, 180, 180), 1)); + painter->drawEllipse(x - radius, y - radius, radius * 2, radius * 2); + + // Draw dimples + painter->setPen(Qt::NoPen); + painter->setBrush(QColor(180, 180, 180, 100)); + int dimpleSize = 4; + // Top row + painter->drawEllipse(x - 8, y - 12, dimpleSize, dimpleSize); + painter->drawEllipse(x + 4, y - 12, dimpleSize, dimpleSize); + // Middle row + painter->drawEllipse(x - 12, y - 4, dimpleSize, dimpleSize); + painter->drawEllipse(x, y - 4, dimpleSize, dimpleSize); + painter->drawEllipse(x + 8, y - 4, dimpleSize, dimpleSize); + // Bottom row + painter->drawEllipse(x - 8, y + 4, dimpleSize, dimpleSize); + painter->drawEllipse(x + 4, y + 4, dimpleSize, dimpleSize); +} + +void SplashScreen::drawPowerupIcons(QPainter *painter) +{ + painter->setClipRect(0, 0, WIDTH, HEIGHT); + + // Draw scattered powerup icons around the edges + // Lightning bolt - top left + painter->setPen(Qt::NoPen); + painter->setBrush(QColor("#FFEB3B")); // Yellow + QPainterPath lightning; + lightning.moveTo(60, 30); + lightning.lineTo(75, 30); + lightning.lineTo(65, 50); + lightning.lineTo(80, 50); + lightning.lineTo(55, 80); + lightning.lineTo(65, 55); + lightning.lineTo(50, 55); + lightning.closeSubpath(); + painter->drawPath(lightning); + + // Star - bottom left + painter->setBrush(QColor("#FF9800")); // Orange + QPainterPath star; + int starX = 50, starY = 230; + for (int i = 0; i < 5; i++) { + double angle = i * 72 * M_PI / 180 - M_PI / 2; + double innerAngle = angle + 36 * M_PI / 180; + if (i == 0) { + star.moveTo(starX + 15 * qCos(angle), starY + 15 * qSin(angle)); + } else { + star.lineTo(starX + 15 * qCos(angle), starY + 15 * qSin(angle)); + } + star.lineTo(starX + 7 * qCos(innerAngle), starY + 7 * qSin(innerAngle)); + } + star.closeSubpath(); + painter->drawPath(star); + + // Speed lines - right side + painter->setPen(QPen(QColor("#03A9F4"), 3, Qt::SolidLine, Qt::RoundCap)); // Blue + painter->drawLine(420, 180, 460, 180); + painter->drawLine(425, 190, 470, 190); + painter->drawLine(420, 200, 460, 200); + + // Circle powerup - top right area + QRadialGradient circleGrad(440, 35, 12); + circleGrad.setColorAt(0, QColor("#E91E63")); // Pink + circleGrad.setColorAt(1, QColor("#C2185B")); + painter->setBrush(circleGrad); + painter->setPen(Qt::NoPen); + painter->drawEllipse(428, 23, 24, 24); + + // Small decorative dots + painter->setBrush(QColor(255, 255, 255, 100)); + painter->drawEllipse(90, 140, 6, 6); + painter->drawEllipse(380, 250, 8, 8); + painter->drawEllipse(120, 200, 5, 5); +} + +void SplashScreen::drawTitle(QPainter *painter) +{ + painter->setClipRect(0, 0, WIDTH, HEIGHT); + + // Draw drop shadow for title + QFont titleFont("Segoe UI", 32, QFont::Bold); + painter->setFont(titleFont); + + QString gwf = "GWF"; + QString rest = " PowerUp Injector"; + + QFontMetrics fm(titleFont); + int gwfWidth = fm.horizontalAdvance(gwf); + int restWidth = fm.horizontalAdvance(rest); + int totalWidth = gwfWidth + restWidth; + int titleX = (WIDTH - totalWidth) / 2; + int titleY = 100; + + // Draw shadow + painter->setPen(QColor(0, 0, 0, 80)); + painter->drawText(titleX + 2, titleY + 2, gwf); + painter->drawText(titleX + gwfWidth + 2, titleY + 2, rest); + + // Draw "GWF" in gold/yellow accent + painter->setPen(mGold); + painter->drawText(titleX, titleY, gwf); + + // Draw rest in white + painter->setPen(mWhite); + painter->drawText(titleX + gwfWidth, titleY, rest); + + // Tagline + QFont taglineFont("Segoe UI", 11); + painter->setFont(taglineFont); + painter->setPen(mLightGray); + QString tagline = "Add powerups to your workshop maps!"; + QFontMetrics taglineFm(taglineFont); + int taglineWidth = taglineFm.horizontalAdvance(tagline); + painter->drawText((WIDTH - taglineWidth) / 2, titleY + 28, tagline); + + // Version info + QFont versionFont("Segoe UI", 9); + painter->setFont(versionFont); + painter->setPen(QColor(255, 255, 255, 150)); + QString version = QString("Version %1").arg(QCoreApplication::applicationVersion()); + QFontMetrics versionFm(versionFont); + int versionWidth = versionFm.horizontalAdvance(version); + painter->drawText((WIDTH - versionWidth) / 2, titleY + 48, version); + + // Copyright + QString copyright = QString("%1 %2").arg(QCoreApplication::organizationName()) + .arg(QDate::currentDate().year()); + int copyrightWidth = versionFm.horizontalAdvance(copyright); + painter->drawText((WIDTH - copyrightWidth) / 2, HEIGHT - 15, copyright); +} + +void SplashScreen::drawProgressBar(QPainter *painter) +{ + int progressX = 40; + int progressY = HEIGHT - 60; + int progressWidth = WIDTH - 80; + int progressHeight = 12; + + // Progress bar background + painter->setPen(Qt::NoPen); + painter->setBrush(QColor(0, 0, 0, 80)); + painter->drawRoundedRect(progressX, progressY, progressWidth, progressHeight, 6, 6); + + // Progress bar fill + if (mProgressMax > 0 && mProgress > 0) { + int fillWidth = (progressWidth * mProgress) / mProgressMax; + if (fillWidth > 0) { + // Gradient fill for progress + QLinearGradient gradient(progressX, progressY, progressX + fillWidth, progressY); + gradient.setColorAt(0, mGold); + gradient.setColorAt(0.5, mGold.lighter(110)); + gradient.setColorAt(1, mGold); + painter->setBrush(gradient); + painter->drawRoundedRect(progressX, progressY, fillWidth, progressHeight, 6, 6); + + // Add shine effect + painter->setBrush(QColor(255, 255, 255, 60)); + painter->drawRoundedRect(progressX, progressY, fillWidth, progressHeight / 2, 6, 6); + } + } +} + +void SplashScreen::drawStatusText(QPainter *painter) +{ + if (!mStatus.isEmpty()) { + QFont statusFont("Segoe UI", 9); + painter->setFont(statusFont); + painter->setPen(mWhite); + + int progressX = 40; + int progressY = HEIGHT - 60; + int progressWidth = WIDTH - 80; + + QFontMetrics statusFm(statusFont); + QString elidedStatus = statusFm.elidedText(mStatus, Qt::ElideMiddle, progressWidth); + painter->drawText(progressX, progressY - 8, elidedStatus); + } +} diff --git a/splashscreen.h b/splashscreen.h new file mode 100644 index 0000000..b39faa8 --- /dev/null +++ b/splashscreen.h @@ -0,0 +1,58 @@ +#ifndef SPLASHSCREEN_H +#define SPLASHSCREEN_H + +#include +#include +#include +#include +#include + +class SplashScreen : public QSplashScreen +{ + Q_OBJECT + +public: + explicit SplashScreen(QWidget *parent = nullptr); + ~SplashScreen(); + + void setStatus(const QString &message); + void setProgress(int value, int max = 100); + void startAnimation(); + void stopAnimation(); + void finish(QWidget *mainWindow); + +protected: + void drawContents(QPainter *painter) override; + void mousePressEvent(QMouseEvent *event) override; + void keyPressEvent(QKeyEvent *event) override; + +private slots: + void onAnimationTick(); + +private: + void drawBackground(QPainter *painter); + void drawGolfBall(QPainter *painter, int x, int y, int radius); + void drawPowerupIcons(QPainter *painter); + void drawTitle(QPainter *painter); + void drawProgressBar(QPainter *painter); + void drawStatusText(QPainter *painter); + + QString mStatus; + int mProgress = 0; + int mProgressMax = 100; + + QTimer *mAnimationTimer; + float mAnimationPhase = 0.0f; // For bouncing golf ball animation + + // Theme colors - golf course green theme + QColor mGreenLight = QColor("#4CAF50"); // Light green + QColor mGreenDark = QColor("#2E7D32"); // Dark green + QColor mGold = QColor("#FFD700"); // Golden yellow for progress + QColor mWhite = QColor("#FFFFFF"); // White text + QColor mLightGray = QColor("#E0E0E0"); // Light gray for muted text + + static constexpr int WIDTH = 480; + static constexpr int HEIGHT = 300; +}; + +#endif // SPLASHSCREEN_H