# HG changeset patch # User unC0Rr # Date 1739806679 -3600 # Node ID 02304ad06381dfa07a95db02d56cd44d13a74b0d # Parent 8da5a118120bab0ee09884b1a471e8bec20175d2 Add TiledImageItem and a scene that represents hedgewars map diff -r 8da5a118120b -r 02304ad06381 qmlfrontend/CMakeLists.txt --- 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 diff -r 8da5a118120b -r 02304ad06381 qmlfrontend/GameScene.qml --- /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) + } +} diff -r 8da5a118120b -r 02304ad06381 qmlfrontend/engine_instance.cpp --- 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" diff -r 8da5a118120b -r 02304ad06381 qmlfrontend/hwengine.cpp --- 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(); } diff -r 8da5a118120b -r 02304ad06381 qmlfrontend/hwengine.h --- 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); diff -r 8da5a118120b -r 02304ad06381 qmlfrontend/main.cpp --- 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(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - - qmlRegisterSingletonType( - "Hedgewars.Engine", 1, 0, "PreviewAcceptor", - previewacceptor_singletontype_provider); - qmlRegisterType("Hedgewars.Engine", 1, 0, "HWEngine"); - qmlRegisterType("Hedgewars.Engine", 1, 0, "GameView"); - qmlRegisterType("Hedgewars.Engine", 1, 0, "NetSession"); - qmlRegisterUncreatableType( - "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; diff -r 8da5a118120b -r 02304ad06381 qmlfrontend/main.qml --- 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 { - } } } diff -r 8da5a118120b -r 02304ad06381 qmlfrontend/net_session.cpp --- 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 ¶meters) { 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); } diff -r 8da5a118120b -r 02304ad06381 qmlfrontend/players_model.cpp --- 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); diff -r 8da5a118120b -r 02304ad06381 qmlfrontend/players_model.h --- 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); diff -r 8da5a118120b -r 02304ad06381 qmlfrontend/qml.qrc --- 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 @@ Page1Form.ui.qml qtquickcontrols2.conf res/iconTime.png + GameScene.qml diff -r 8da5a118120b -r 02304ad06381 qmlfrontend/renderer/tiled_image_item.cpp --- /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 +#include + +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)} {} diff -r 8da5a118120b -r 02304ad06381 qmlfrontend/renderer/tiled_image_item.h --- /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 +#include +#include + +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 node; + std::unique_ptr texture; + }; + + void buildTiles(); + int m_tileSize{512}; + std::vector m_tiles; +}; diff -r 8da5a118120b -r 02304ad06381 qmlfrontend/rooms_model.cpp --- 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)); } diff -r 8da5a118120b -r 02304ad06381 qmlfrontend/rooms_model.h --- 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);