Add TiledImageItem and a scene that represents hedgewars map qmlrenderer
authorunC0Rr
Mon, 17 Feb 2025 16:37:59 +0100
branchqmlrenderer
changeset 16118 02304ad06381
parent 16116 8da5a118120b
Add TiledImageItem and a scene that represents hedgewars map
qmlfrontend/CMakeLists.txt
qmlfrontend/GameScene.qml
qmlfrontend/engine_instance.cpp
qmlfrontend/hwengine.cpp
qmlfrontend/hwengine.h
qmlfrontend/main.cpp
qmlfrontend/main.qml
qmlfrontend/net_session.cpp
qmlfrontend/players_model.cpp
qmlfrontend/players_model.h
qmlfrontend/qml.qrc
qmlfrontend/renderer/tiled_image_item.cpp
qmlfrontend/renderer/tiled_image_item.h
qmlfrontend/rooms_model.cpp
qmlfrontend/rooms_model.h
--- a/qmlfrontend/CMakeLists.txt	Tue Feb 04 17:31:55 2025 +0100
+++ b/qmlfrontend/CMakeLists.txt	Mon Feb 17 16:37:59 2025 +0100
@@ -32,9 +32,15 @@
     "net_session.cpp" "net_session.h"
     "players_model.cpp" "players_model.h"
     "rooms_model.cpp" "rooms_model.h"
+    "renderer/tiled_image_item.h" "renderer/tiled_image_item.cpp"
 )
 
-target_link_libraries(${PROJECT_NAME}
+target_include_directories(${PROJECT_NAME} PRIVATE
+    renderer
+)
+
+
+target_link_libraries(${PROJECT_NAME}    
     PRIVATE
     Qt6::Core
     Qt6::Network
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qmlfrontend/GameScene.qml	Mon Feb 17 16:37:59 2025 +0100
@@ -0,0 +1,112 @@
+import QtQuick 2.15
+import Hedgewars 1.0
+
+Item {
+  id: control
+
+  property string dataPrefix: "share/hedgewars/Data/"
+  property url themePath: "file://%1Themes/Stage/".arg(dataPrefix)
+
+  Flickable {
+    id: backgroundContainer
+
+    anchors.fill: parent
+
+    contentWidth: map.implicitWidth
+    contentHeight: map.implicitHeight
+
+    interactive: false
+    contentX: mapContainer.contentX * 0.7 + (contentWidth - width) * 0.15
+    contentY: mapContainer.contentY * 0.7 + (contentHeight - height) * 0.3
+
+    Rectangle {
+      anchors.fill: parent
+      color: "black"
+    }
+
+    Item {
+      width: parent.width
+      height: parent.height
+      anchors.horizontalCenter: parent.horizontalCenter
+      anchors.bottom: parent.bottom
+
+      Image {
+        id: skyLeft
+        anchors.left: parent.left
+        anchors.right: skyCenter.left
+        anchors.bottom: parent.bottom
+        fillMode: Image.TileHorizontally
+        horizontalAlignment: Image.AlignRight
+        source: control.themePath + "SkyL.png"
+      }
+
+      Image {
+        id: skyCenter
+        anchors.horizontalCenter: parent.horizontalCenter
+        anchors.bottom: parent.bottom
+        fillMode: Image.Pad
+        source: control.themePath + "Sky.png"
+      }
+
+      Image {
+        id: skyRight
+        anchors.left: skyCenter.right
+        anchors.right: parent.right
+        anchors.bottom: parent.bottom
+        fillMode: Image.TileHorizontally
+        horizontalAlignment: Image.AlignLeft
+        source: control.themePath + "SkyL.png"
+      }
+    }
+  }
+
+  Flickable {
+    id: mapContainer
+
+    anchors.fill: parent
+
+    boundsMovement: Flickable.StopAtBounds
+
+    contentWidth: map.implicitWidth
+    contentHeight: map.implicitHeight
+
+    onContentXChanged: map.update()
+    onContentYChanged: map.update()
+    onWidthChanged: map.update()
+    onHeightChanged: map.update()
+
+    pixelAligned: true
+
+    TiledImageItem {
+      id: map
+
+    }
+  }
+
+  MouseArea {
+    anchors.fill: mapContainer
+
+    property double zoom: 1.0
+
+    acceptedButtons: Qt.NoButton
+
+    enabled: false
+
+    onWheel: wheel => {
+               zoom = zoom * Math.exp(wheel.angleDelta.y * 0.001)
+               var zoomPoint = Qt.point(wheel.x + mapContainer.contentX,
+                                    wheel.y + mapContainer.contentY);
+
+               map.scale = zoom
+               mapContainer.resizeContent(map.implicitWidth * zoom, map.implicitHeight * zoom, zoomPoint);
+               mapContainer.returnToBounds();
+               console.log(mapContainer.scale)
+             }
+  }
+
+
+  Component.onCompleted: {
+    map.test(dataPrefix + "Maps/Ropes/map.png")
+    console.log(Screen.devicePixelRatio)
+  }
+}
--- a/qmlfrontend/engine_instance.cpp	Tue Feb 04 17:31:55 2025 +0100
+++ b/qmlfrontend/engine_instance.cpp	Mon Feb 17 16:37:59 2025 +0100
@@ -56,7 +56,7 @@
               advance_simulation && move_camera && simple_event && long_event &&
               positioned_event;
 
-  emit isValidChanged(m_isValid);
+  Q_EMIT isValidChanged(m_isValid);
 
   if (isValid()) {
     qDebug() << "Loaded engine library with protocol version"
--- a/qmlfrontend/hwengine.cpp	Tue Feb 04 17:31:55 2025 +0100
+++ b/qmlfrontend/hwengine.cpp	Mon Feb 17 16:37:59 2025 +0100
@@ -14,7 +14,7 @@
 HWEngine::~HWEngine() {}
 
 void HWEngine::getPreview() {
-  emit previewIsRendering();
+  Q_EMIT previewIsRendering();
 
   m_gameConfig = GameConfig();
   m_gameConfig.cmdSeed(QUuid::createUuid().toByteArray());
@@ -29,7 +29,7 @@
 
   if (m_previewAcceptor) m_previewAcceptor->setImage(previewImage);
 
-  emit previewImageChanged();
+  Q_EMIT previewImageChanged();
   // m_runQueue->queue(m_gameConfig);
 }
 
@@ -60,14 +60,14 @@
   if (m_previewAcceptor == previewAcceptor) return;
 
   m_previewAcceptor = previewAcceptor;
-  emit previewAcceptorChanged(m_previewAcceptor);
+  Q_EMIT previewAcceptorChanged(m_previewAcceptor);
 }
 
 void HWEngine::setEngineLibrary(const QString& engineLibrary) {
   if (m_engineLibrary == engineLibrary) return;
 
   m_engineLibrary = engineLibrary;
-  emit engineLibraryChanged(m_engineLibrary);
+  Q_EMIT engineLibraryChanged(m_engineLibrary);
 }
 
 const QString &HWEngine::dataPath() const
@@ -80,5 +80,5 @@
   if (m_dataPath == newDataPath)
     return;
   m_dataPath = newDataPath;
-  emit dataPathChanged();
+  Q_EMIT dataPathChanged();
 }
--- a/qmlfrontend/hwengine.h	Tue Feb 04 17:31:55 2025 +0100
+++ b/qmlfrontend/hwengine.h	Mon Feb 17 16:37:59 2025 +0100
@@ -38,11 +38,11 @@
   const QString &dataPath() const;
   void setDataPath(const QString &newDataPath);
 
-public slots:
+public Q_SLOTS:
   void setPreviewAcceptor(PreviewAcceptor* previewAcceptor);
   void setEngineLibrary(const QString& engineLibrary);
 
- signals:
+ Q_SIGNALS:
   void previewIsRendering();
   void previewImageChanged();
   void previewHogCountChanged(int count);
--- a/qmlfrontend/main.cpp	Tue Feb 04 17:31:55 2025 +0100
+++ b/qmlfrontend/main.cpp	Mon Feb 17 16:37:59 2025 +0100
@@ -7,26 +7,6 @@
   QGuiApplication app(argc, argv);
 
   QQmlApplicationEngine engine;
-  /*
-    qRegisterMetaType<Engine::SimpleEventType>();
-    qRegisterMetaType<Engine::LongEventType>();
-    qRegisterMetaType<Engine::LongEventState>();
-    qRegisterMetaType<Engine::PositionedEventType>();
-
-    qmlRegisterSingletonType<PreviewAcceptor>(
-        "Hedgewars.Engine", 1, 0, "PreviewAcceptor",
-        previewacceptor_singletontype_provider);
-    qmlRegisterType<HWEngine>("Hedgewars.Engine", 1, 0, "HWEngine");
-    qmlRegisterType<GameView>("Hedgewars.Engine", 1, 0, "GameView");
-    qmlRegisterType<NetSession>("Hedgewars.Engine", 1, 0, "NetSession");
-    qmlRegisterUncreatableType<EngineInstance>(
-        "Hedgewars.Engine", 1, 0, "EngineInstance",
-        QStringLiteral("Create by HWEngine run methods"));
-
-    qmlRegisterUncreatableMetaObject(Engine::staticMetaObject,
-    "Hedgewars.Engine", 1, 0, "Engine", QStringLiteral("Namespace: only
-    enums"));
-  */
   engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
   if (engine.rootObjects().isEmpty()) {
     return -1;
--- a/qmlfrontend/main.qml	Tue Feb 04 17:31:55 2025 +0100
+++ b/qmlfrontend/main.qml	Mon Feb 17 16:37:59 2025 +0100
@@ -8,11 +8,7 @@
   height: 480
   title: qsTr("Hello World")
 
-  SwipeView {
-    id: swipeView
+  GameScene {
     anchors.fill: parent
-
-    Page1 {
-    }
   }
 }
--- a/qmlfrontend/net_session.cpp	Tue Feb 04 17:31:55 2025 +0100
+++ b/qmlfrontend/net_session.cpp	Mon Feb 17 16:37:59 2025 +0100
@@ -48,21 +48,21 @@
   if (m_url == url) return;
 
   m_url = url;
-  emit urlChanged(m_url);
+  Q_EMIT urlChanged(m_url);
 }
 
 void NetSession::setNickname(const QString &nickname) {
   if (m_nickname == nickname) return;
 
   m_nickname = nickname;
-  emit nicknameChanged(m_nickname);
+  Q_EMIT nicknameChanged(m_nickname);
 }
 
 void NetSession::setPasswordHash(const QString &passwordHash) {
   if (m_passwordHash == passwordHash) return;
 
   m_passwordHash = passwordHash;
-  emit passwordHashChanged(m_passwordHash);
+  Q_EMIT passwordHashChanged(m_passwordHash);
 
   if (m_sessionState == Authentication) sendPassword();
 }
@@ -71,7 +71,7 @@
   if (m_room == room) return;
 
   m_room = room;
-  emit roomChanged(m_room);
+  Q_EMIT roomChanged(m_room);
 }
 
 void NetSession::close() {
@@ -155,7 +155,7 @@
 void NetSession::handleConnected(const QStringList &parameters) {
   if (parameters.length() < 2 || parameters[1].toInt() < cMinServerVersion) {
     send("QUIT", "Server too old");
-    emit error(tr("Server too old"));
+    Q_EMIT error(tr("Server too old"));
     close();
   } else {
     setSessionState(Login);
@@ -191,7 +191,7 @@
   m_clientSalt = QUuid::createUuid().toString();
 
   if (m_passwordHash.isEmpty()) {
-    emit passwordAsked();
+    Q_EMIT passwordAsked();
   } else {
     sendPassword();
   }
@@ -227,7 +227,7 @@
       // check if server is authenticated or no authentication was performed at
       // all
       if (!m_serverAuthHash.isEmpty()) {
-        emit error(tr("Server authentication error"));
+        Q_EMIT error(tr("Server authentication error"));
 
         close();
       }
@@ -363,5 +363,5 @@
 
   m_sessionState = sessionState;
 
-  emit sessionStateChanged(sessionState);
+  Q_EMIT sessionStateChanged(sessionState);
 }
--- a/qmlfrontend/players_model.cpp	Tue Feb 04 17:31:55 2025 +0100
+++ b/qmlfrontend/players_model.cpp	Mon Feb 17 16:37:59 2025 +0100
@@ -36,7 +36,7 @@
 
   m_data[index.row()].insert(role, value);
 
-  emit dataChanged(index, index);
+  Q_EMIT dataChanged(index, index);
 
   return true;
 }
@@ -87,15 +87,15 @@
 
   checkFriendIgnore(mi);
 
-  emit nickAddedLobby(nickname, notify);
+  Q_EMIT nickAddedLobby(nickname, notify);
 }
 
 void PlayersListModel::removePlayer(const QString &nickname,
                                     const QString &msg) {
   if (msg.isEmpty())
-    emit nickRemovedLobby(nickname, QString());
+    Q_EMIT nickRemovedLobby(nickname, QString());
   else
-    emit nickRemovedLobby(nickname, msg);
+    Q_EMIT nickRemovedLobby(nickname, msg);
 
   QModelIndex mi = nicknameIndex(nickname);
 
@@ -111,11 +111,11 @@
     updateSortData(mi);
   }
 
-  emit nickAdded(nickname, notify);
+  Q_EMIT nickAdded(nickname, notify);
 }
 
 void PlayersListModel::playerLeftRoom(const QString &nickname) {
-  emit nickRemoved(nickname);
+  Q_EMIT nickRemoved(nickname);
 
   QModelIndex mi = nicknameIndex(nickname);
 
--- a/qmlfrontend/players_model.h	Tue Feb 04 17:31:55 2025 +0100
+++ b/qmlfrontend/players_model.h	Mon Feb 17 16:37:59 2025 +0100
@@ -49,7 +49,7 @@
 
   QModelIndex nicknameIndex(const QString &nickname);
 
- public slots:
+ public Q_SLOTS:
   void addPlayer(const QString &nickname, bool notify);
   void removePlayer(const QString &nickname, const QString &msg = QString());
   void playerJoinedRoom(const QString &nickname, bool notify);
@@ -57,7 +57,7 @@
   void resetRoomFlags();
   void setNickname(const QString &nickname);
 
- signals:
+ Q_SIGNALS:
   void nickAdded(const QString &nick, bool notifyNick);
   void nickRemoved(const QString &nick);
   void nickAddedLobby(const QString &nick, bool notifyNick);
--- a/qmlfrontend/qml.qrc	Tue Feb 04 17:31:55 2025 +0100
+++ b/qmlfrontend/qml.qrc	Mon Feb 17 16:37:59 2025 +0100
@@ -5,5 +5,6 @@
         <file>Page1Form.ui.qml</file>
         <file>qtquickcontrols2.conf</file>
         <file>res/iconTime.png</file>
+        <file>GameScene.qml</file>
     </qresource>
 </RCC>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qmlfrontend/renderer/tiled_image_item.cpp	Mon Feb 17 16:37:59 2025 +0100
@@ -0,0 +1,124 @@
+#include "tiled_image_item.h"
+
+#include <QSGImageNode>
+#include <QSGTexture>
+
+TiledImageItem::TiledImageItem(QQuickItem *parent) : QQuickItem{parent} {
+  setFlag(ItemHasContents, true);
+  setFlag(ItemObservesViewport, true);
+}
+
+TiledImageItem::~TiledImageItem() {}
+
+void TiledImageItem::setImageData(int width, int height, uchar *pixels,
+                                  QImage::Format format) {
+  m_texture = QImage{pixels, width, height, width * 4, format};
+
+  setImplicitWidth(width / window()->effectiveDevicePixelRatio());
+  setImplicitHeight(height / window()->effectiveDevicePixelRatio());
+
+  buildTiles();
+
+  update();
+}
+
+void TiledImageItem::buildTiles() {
+  m_tiles.clear();
+  if (m_texture.isNull()) {
+    return;
+  }
+
+  const auto imageWidth = m_texture.width();
+  const auto imageHeight = m_texture.height();
+
+  for (int y = 0; y < imageHeight; y += m_tileSize) {
+    for (int x = 0; x < imageWidth; x += m_tileSize) {
+      int w = qMin(m_tileSize, imageWidth - x);
+      int h = qMin(m_tileSize, imageHeight - y);
+      QImage tileImage{&m_texture.scanLine(y)[x * m_texture.depth() / 8],
+                       m_tileSize, m_tileSize, m_texture.bytesPerLine(),
+                       m_texture.format()};
+
+      QRectF tileRect = QRect{x, y, w, h}.toRectF();
+      tileRect.setTopLeft(tileRect.topLeft() /
+                          window()->effectiveDevicePixelRatio());
+      tileRect.setBottomRight(tileRect.bottomRight() /
+                              window()->effectiveDevicePixelRatio());
+
+      Tile tile;
+      tile.outRect = tileRect;
+      tile.image = tileImage;
+      tile.dirty = true;
+      m_tiles.emplace_back(std::move(tile));
+    }
+  }
+}
+int TiledImageItem::tileSize() const { return m_tileSize; }
+
+void TiledImageItem::setTileSize(int newTileSize) {
+  if (m_tileSize == newTileSize) {
+    return;
+  }
+
+  m_tileSize = newTileSize;
+
+  m_tiles.clear();
+  m_dirty = true;
+
+  Q_EMIT tileSizeChanged();
+}
+
+void TiledImageItem::test(const QString &fileName) {
+  auto image = new QImage(fileName);
+
+  setImageData(image->width(), image->height(), image->bits(), image->format());
+}
+
+QSGNode *TiledImageItem::updatePaintNode(QSGNode *oldNode,
+                                         UpdatePaintNodeData *) {
+  auto rootNode = oldNode;
+  if (!rootNode) {
+    rootNode = new QSGNode{};
+  }
+
+  if (!window()) {
+    return rootNode;
+  }
+
+  const auto rect = clipRect();
+
+  for (auto &tile : m_tiles) {
+    if (!rect.intersects(tile.outRect)) {
+      if (tile.node) {
+        rootNode->removeChildNode(tile.node.get());
+        tile.node.reset();
+      }
+
+      continue;
+    }
+
+    if (tile.dirty) {
+      tile.texture.reset(window()->createTextureFromImage(tile.image));
+      if (tile.node) {
+        tile.node->setTexture(tile.texture.get());
+      }
+
+      tile.dirty = false;
+    }
+
+    if (!tile.node) {
+      tile.node.reset(window()->createImageNode());
+      tile.node->setRect(tile.outRect);
+      tile.node->setTexture(tile.texture.get());
+      rootNode->appendChildNode(tile.node.get());
+    }
+  }
+
+  return rootNode;
+}
+
+TiledImageItem::Tile::Tile(Tile &&other) noexcept
+    : outRect{other.outRect},
+      image{std::move(other.image)},
+      dirty{other.dirty},
+      texture{std::move(other.texture)} {}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qmlfrontend/renderer/tiled_image_item.h	Mon Feb 17 16:37:59 2025 +0100
@@ -0,0 +1,53 @@
+#pragma once
+
+#include <QImage>
+#include <QQuickItem>
+#include <QQuickWindow>
+
+class TiledImageItem : public QQuickItem {
+  Q_OBJECT
+  QML_ELEMENT
+
+  Q_PROPERTY(
+      int tileSize READ tileSize WRITE setTileSize NOTIFY tileSizeChanged FINAL)
+
+ public:
+  explicit TiledImageItem(QQuickItem *parent = nullptr);
+  ~TiledImageItem();
+
+  void setImageData(int width, int height, uchar *pixels,
+                    QImage::Format format);
+
+  int tileSize() const;
+  void setTileSize(int newTileSize);
+
+  Q_INVOKABLE void test(const QString &fileName);
+
+ protected:
+  QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) override;
+
+ Q_SIGNALS:
+
+  void tileSizeChanged();
+
+ private:
+  Q_DISABLE_COPY_MOVE(TiledImageItem)
+
+  QImage m_texture;
+  bool m_dirty;
+
+  struct Tile {
+    Tile() = default;
+    Tile(Tile &&other) noexcept;
+    ~Tile() = default;
+    QRectF outRect;
+    QImage image;
+    bool dirty = false;
+    std::unique_ptr<QSGImageNode> node;
+    std::unique_ptr<QSGTexture, QScopedPointerDeleteLater> texture;
+  };
+
+  void buildTiles();
+  int m_tileSize{512};
+  std::vector<Tile> m_tiles;
+};
--- a/qmlfrontend/rooms_model.cpp	Tue Feb 04 17:31:55 2025 +0100
+++ b/qmlfrontend/rooms_model.cpp	Mon Feb 17 16:37:59 2025 +0100
@@ -227,5 +227,5 @@
 
   m_data[i] = info;
 
-  emit dataChanged(index(i, 0), index(i, columnCount(QModelIndex()) - 1));
+  Q_EMIT dataChanged(index(i, 0), index(i, columnCount(QModelIndex()) - 1));
 }
--- a/qmlfrontend/rooms_model.h	Tue Feb 04 17:31:55 2025 +0100
+++ b/qmlfrontend/rooms_model.h	Mon Feb 17 16:37:59 2025 +0100
@@ -51,7 +51,7 @@
   int columnCount(const QModelIndex &parent) const override;
   QVariant data(const QModelIndex &index, int role) const override;
 
- public slots:
+ public Q_SLOTS:
   void setRoomsList(const QStringList &rooms);
   void addRoom(const QStringList &info);
   void removeRoom(const QString &name);