Browse Source

Add load screen with animated progress bar

- Added load_screen.png to assets.qrc
- Created LoadScreen.qml with fake progress animation
- Added is_loading property and signal to GameEngine
- Integrated LoadScreen into Main.qml as overlay
- Progress bar starts fast and slows down exponentially
- Load screen shows when starting skirmish or campaign mission

Co-authored-by: djeada <[email protected]>
copilot-swe-agent[bot] 1 week ago
parent
commit
c0fa53300a
7 changed files with 174 additions and 0 deletions
  1. 1 0
      CMakeLists.txt
  2. 2 0
      app/core/game_engine.cpp
  3. 6 0
      app/core/game_engine.h
  4. 1 0
      assets.qrc
  5. 1 0
      qml_resources.qrc
  6. 146 0
      ui/qml/LoadScreen.qml
  7. 17 0
      ui/qml/Main.qml

+ 1 - 0
CMakeLists.txt

@@ -201,6 +201,7 @@ if(QT_VERSION_MAJOR EQUAL 6)
             ui/qml/BattleSummary.qml
             ui/qml/GameView.qml
             ui/qml/CursorManager.qml
+            ui/qml/LoadScreen.qml
         RESOURCES
             assets/shaders/archer.frag
             assets/shaders/archer.vert

+ 2 - 0
app/core/game_engine.cpp

@@ -1157,6 +1157,7 @@ void GameEngine::start_skirmish(const QString &map_path,
   if (m_world && m_renderer && m_camera) {
 
     m_runtime.loading = true;
+    emit is_loading_changed();
 
     if (m_hoverTracker) {
       m_hoverTracker->update_hover(-1, -1, *m_world, *m_camera, 0, 0);
@@ -1209,6 +1210,7 @@ void GameEngine::start_skirmish(const QString &map_path,
     }
 
     m_runtime.loading = false;
+    emit is_loading_changed();
 
     GameStateRestorer::rebuild_entity_cache(m_world.get(), m_entity_cache,
                                             m_runtime.local_owner_id);

+ 6 - 0
app/core/game_engine.h

@@ -144,6 +144,7 @@ public:
       QImage minimap_image READ minimap_image NOTIFY minimap_image_changed)
   Q_PROPERTY(bool is_spectator_mode READ is_spectator_mode NOTIFY
                  spectator_mode_changed)
+  Q_PROPERTY(bool is_loading READ is_loading NOTIFY is_loading_changed)
 
   Q_INVOKABLE void on_map_clicked(qreal sx, qreal sy);
   Q_INVOKABLE void on_right_click(qreal sx, qreal sy);
@@ -245,6 +246,10 @@ public:
     return m_level.is_spectator_mode;
   }
 
+  [[nodiscard]] bool is_loading() const {
+    return m_runtime.loading;
+  }
+
   QObject *audio_system();
 
   void setWindow(QQuickWindow *w) { m_window = w; }
@@ -368,4 +373,5 @@ signals:
   void save_slots_changed();
   void hold_mode_changed(bool active);
   void spectator_mode_changed();
+  void is_loading_changed();
 };

+ 1 - 0
assets.qrc

@@ -86,6 +86,7 @@
         <file>assets/visuals/unit_visuals.json</file>
         <file>assets/visuals/emblems/rome.png</file>
         <file>assets/visuals/emblems/cartaghe.png</file>
+        <file>assets/visuals/load_screen.png</file>
         <!-- Unit icons -->
         <file>assets/visuals/icons/archer_rome.png</file>
         <file>assets/visuals/icons/archer_cartaghe.png</file>

+ 1 - 0
qml_resources.qrc

@@ -23,5 +23,6 @@
         <file>ui/qml/BattleSummary.qml</file>
         <file>ui/qml/GameView.qml</file>
         <file>ui/qml/CursorManager.qml</file>
+        <file>ui/qml/LoadScreen.qml</file>
     </qresource>
 </RCC>

+ 146 - 0
ui/qml/LoadScreen.qml

@@ -0,0 +1,146 @@
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+
+Rectangle {
+    id: loadScreen
+
+    property real progress: 0.0
+    property bool isLoading: false
+
+    anchors.fill: parent
+    color: "#000000"
+    visible: isLoading
+
+    // Background image
+    Image {
+        id: backgroundImage
+        anchors.fill: parent
+        source: "qrc:/assets/visuals/load_screen.png"
+        fillMode: Image.PreserveAspectCrop
+    }
+
+    // Dark overlay for better text visibility
+    Rectangle {
+        anchors.fill: parent
+        color: "#40000000"
+    }
+
+    Column {
+        anchors.centerIn: parent
+        anchors.verticalCenterOffset: parent.height * 0.3
+        spacing: 20
+        width: parent.width * 0.6
+
+        Text {
+            text: qsTr("Loading...")
+            color: "#ecf0f1"
+            font.pixelSize: 48
+            font.bold: true
+            anchors.horizontalCenter: parent.horizontalCenter
+        }
+
+        // Progress bar container
+        Rectangle {
+            width: parent.width
+            height: 40
+            color: "#2c3e50"
+            border.color: "#34495e"
+            border.width: 2
+            radius: 4
+
+            // Progress fill
+            Rectangle {
+                id: progressFill
+                anchors.left: parent.left
+                anchors.top: parent.top
+                anchors.bottom: parent.bottom
+                anchors.margins: 4
+                width: Math.max(0, Math.min(parent.width - 8, (parent.width - 8) * loadScreen.progress))
+                color: "#f39c12"
+                radius: 2
+
+                // Shine effect
+                Rectangle {
+                    anchors.fill: parent
+                    gradient: Gradient {
+                        GradientStop { position: 0.0; color: "#40ffffff" }
+                        GradientStop { position: 0.5; color: "#00ffffff" }
+                        GradientStop { position: 1.0; color: "#40ffffff" }
+                    }
+                    radius: parent.radius
+                }
+            }
+
+            // Progress text
+            Text {
+                anchors.centerIn: parent
+                text: Math.floor(loadScreen.progress * 100) + "%"
+                color: "#ecf0f1"
+                font.pixelSize: 18
+                font.bold: true
+            }
+        }
+
+        Text {
+            text: qsTr("Preparing battlefield...")
+            color: "#bdc3c7"
+            font.pixelSize: 18
+            anchors.horizontalCenter: parent.horizontalCenter
+        }
+    }
+
+    // Fake progress animation
+    Timer {
+        id: progressTimer
+        interval: 50
+        repeat: true
+        running: loadScreen.isLoading
+
+        property real speed: 0.02
+
+        onTriggered: {
+            if (loadScreen.progress < 1.0) {
+                // Start fast, then slow down exponentially
+                var remaining = 1.0 - loadScreen.progress;
+                var increment = speed * remaining;
+                
+                // Ensure minimum progress increment
+                increment = Math.max(0.001, increment);
+                
+                loadScreen.progress = Math.min(1.0, loadScreen.progress + increment);
+                
+                // Slow down as we get closer to 100%
+                if (loadScreen.progress > 0.9) {
+                    speed = 0.005;
+                } else if (loadScreen.progress > 0.7) {
+                    speed = 0.01;
+                } else if (loadScreen.progress > 0.5) {
+                    speed = 0.015;
+                }
+            }
+        }
+    }
+
+    // Reset progress when loading starts
+    onIsLoadingChanged: {
+        if (isLoading) {
+            progress = 0.0;
+            progressTimer.speed = 0.02;
+        }
+    }
+
+    // Function to complete loading immediately
+    function completeLoading() {
+        loadScreen.progress = 1.0;
+        completeTimer.start();
+    }
+
+    Timer {
+        id: completeTimer
+        interval: 300
+        repeat: false
+        onTriggered: {
+            loadScreen.isLoading = false;
+        }
+    }
+}

+ 17 - 0
ui/qml/Main.qml

@@ -109,6 +109,23 @@ ApplicationWindow {
 
     }
 
+    LoadScreen {
+        id: loadScreen
+
+        anchors.fill: parent
+        z: 15
+        isLoading: (typeof game !== 'undefined') ? game.is_loading : false
+
+        Connections {
+            target: game
+            function onIs_loading_changed() {
+                if (!game.is_loading) {
+                    loadScreen.completeLoading();
+                }
+            }
+        }
+    }
+
     MainMenu {
         id: mainMenu