From 4938361f311de638c9b1e62cd2eacda63f6f9a25 Mon Sep 17 00:00:00 2001 From: Evgeny Prikazchikov Date: Mon, 23 Mar 2026 15:52:28 +0300 Subject: [PATCH 1/2] News feed added --- hub/ThunderHub.qbs | 2 +- hub/feed/feedmanager.cpp | 111 ++++++++++++++++++-- hub/feed/feedmanager.h | 37 +++++-- hub/install/downloader.cpp | 2 +- hub/install/extractor.cpp | 18 +++- hub/install/extractor.h | 3 +- hub/install/installmodel.cpp | 20 ++-- hub/res/hub.qrc | 1 + hub/res/qml/Blog.qml | 120 ++-------------------- hub/res/qml/Installs.qml | 38 ++----- hub/res/qml/Learn.qml | 2 +- hub/res/qml/Projects.qml | 21 ++-- hub/res/qml/Settings.qml | 2 +- hub/res/qml/Startup.qml | 109 ++++++-------------- hub/res/qml/Theme.qml | 30 ++++-- hub/res/qml/components/BlogTile.qml | 37 ++++--- hub/res/qml/components/Components.qml | 17 ++- hub/res/qml/components/SideButton.qml | 21 ++++ hub/res/qml/components/StyledItem.qml | 2 +- hub/res/qml/components/StyledPathEdit.qml | 4 +- thunder-hub.qbs | 13 +-- 21 files changed, 301 insertions(+), 309 deletions(-) create mode 100644 hub/res/qml/components/SideButton.qml diff --git a/hub/ThunderHub.qbs b/hub/ThunderHub.qbs index fcf6e1b..340d660 100644 --- a/hub/ThunderHub.qbs +++ b/hub/ThunderHub.qbs @@ -24,7 +24,7 @@ Project { Depends { name: "cpp" } Depends { name: "bundle" } Depends { name: "7zip" } - Depends { name: "Qt"; submodules: ["core", "quickwidgets", "network"]; } + Depends { name: "Qt"; submodules: ["core", "quickwidgets", "network", "xml"]; } property bool isBundle: qbs.targetOS.contains("darwin") && bundle.isBundle bundle.infoPlist: ({ "NSHumanReadableCopyright": "(C) 2007-" + ThunderHub.COPYRIGHT_YEAR + " by " + ThunderHub.COPYRIGHT_AUTHOR diff --git a/hub/feed/feedmanager.cpp b/hub/feed/feedmanager.cpp index 12d7489..65c1e7a 100644 --- a/hub/feed/feedmanager.cpp +++ b/hub/feed/feedmanager.cpp @@ -4,8 +4,10 @@ #include #include +#include + FeedManager::FeedManager(QObject *parent) : - QObject(parent), + QAbstractListModel(parent), m_manager(new QNetworkAccessManager()) { QNetworkRequest req(QUrl("http://thunderengine.org/feed.xml")); @@ -19,14 +21,109 @@ FeedManager::~FeedManager() { delete m_manager; } -QString FeedManager::blogFeed() const { - return m_blogData; -} - void FeedManager::onUpdateCheckFinished() { QNetworkReply *reply = dynamic_cast(sender()); if(reply) { - m_blogData = reply->readAll(); - emit blogFeedChanged(); + m_items.clear(); + + QDomDocument doc; + doc.setContent(reply->readAll()); + QDomElement root = doc.documentElement(); + + QDomNodeList entries = root.elementsByTagName("entry"); + for(int i = 0; i < entries.count(); ++i) { + QDomNode entryNode = entries.at(i); + + RssItem item; + + QDomNodeList titleNodes = entryNode.toElement().elementsByTagName("title"); + if(!titleNodes.isEmpty()) { + item.title = extractTextFromNode(titleNodes.at(0)); + } + + QDomNodeList linkNodes = entryNode.toElement().elementsByTagName("link"); + if(!linkNodes.isEmpty()) { + QDomElement linkElem = linkNodes.at(0).toElement(); + if (linkElem.hasAttribute("href")) { + item.link = linkElem.attribute("href"); + } + } + + QDomNodeList summaryNodes = entryNode.toElement().elementsByTagName("summary"); + if(!summaryNodes.isEmpty()) { + item.description = extractTextFromNode(summaryNodes.at(0)); + } + + QDomNodeList categoryNodes = entryNode.toElement().elementsByTagName("category"); + if(!categoryNodes.isEmpty()) { + QDomElement categoryElem = categoryNodes.at(0).toElement(); + if (categoryElem.hasAttribute("term")) { + item.category = categoryElem.attribute("term"); + } + } + + QDomNodeList thumbnailNodes = entryNode.toElement().elementsByTagName("media:thumbnail"); + if(!thumbnailNodes.isEmpty()) { + QDomElement thumbnailElem = thumbnailNodes.at(0).toElement(); + if (thumbnailElem.hasAttribute("url")) { + item.media = thumbnailElem.attribute("url"); + } + } + + if(!item.title.isEmpty()) { + m_items.push_back(item); + } + } + + emit layoutAboutToBeChanged(); + emit layoutChanged(); + } +} + +QString FeedManager::extractTextFromNode(const QDomNode &node) { + QString result; + QDomNode child = node.firstChild(); + + while (!child.isNull()) { + if (child.isText()) { + result += child.toText().data(); + } else if (child.isCDATASection()) { + result += child.toCDATASection().data(); + } + child = child.nextSibling(); + } + + return result.trimmed(); +} + +int FeedManager::rowCount(const QModelIndex &parent) const { + if(parent.isValid()) { + return 0; } + return m_items.size(); +} + +QVariant FeedManager::data(const QModelIndex &index, int role) const { + if(!index.isValid()) { + return QVariant(); + } + RssItem item( m_items.at(index.row()) ); + switch(role) { + case Title: { return item.title; } + case Brief: { return item.description; } + case Link: { return item.link; } + case Media: { return item.media; }; + default: break; + } + return QVariant(); +} + +QHash FeedManager::roleNames() const { + QHash roles = QAbstractListModel::roleNames(); + roles[Title] = "title"; + roles[Brief] = "brief"; + roles[Link] = "link"; + roles[Media] = "icon"; + + return roles; } diff --git a/hub/feed/feedmanager.h b/hub/feed/feedmanager.h index e08e282..97df706 100644 --- a/hub/feed/feedmanager.h +++ b/hub/feed/feedmanager.h @@ -1,32 +1,49 @@ #ifndef FEEDMANAGER_H #define FEEDMANAGER_H -#include +#include class QNetworkAccessManager; -class QNetworkReply; +class QDomNode; + +struct RssItem { + QString title; + QString description; + QString link; + QString category; + QString media; +}; -class FeedManager : public QObject { +class FeedManager : public QAbstractListModel { Q_OBJECT - Q_PROPERTY(QString blogFeed READ blogFeed NOTIFY blogFeedChanged) + enum Roles { + Title, + Brief, + Link, + Media + }; public: explicit FeedManager(QObject *parent = nullptr); ~FeedManager(); - QString blogFeed() const; - -signals: - void blogFeedChanged(); - private slots: void onUpdateCheckFinished(); +private: + QString extractTextFromNode(const QDomNode &node); + + int rowCount(const QModelIndex &parent) const override; + + QVariant data(const QModelIndex &index, int role) const override; + + QHash roleNames() const override; + private: QNetworkAccessManager *m_manager; - QString m_blogData; + QList m_items; }; diff --git a/hub/install/downloader.cpp b/hub/install/downloader.cpp index 3cd470c..e0f8749 100644 --- a/hub/install/downloader.cpp +++ b/hub/install/downloader.cpp @@ -114,7 +114,7 @@ void Downloader::onDownloadFinished() { void Downloader::onExtractFinished() { if(!m_extractComponents.isEmpty()) { - m_extractor.extract(m_extractComponents.takeFirst(), Settings::instance()->sdkDir()); + m_extractor.extract(m_extractComponents.takeFirst(), Settings::instance()->sdkDir(), m_sdk->version); } else { m_sdk->progress = -1; m_sdk->status.clear(); diff --git a/hub/install/extractor.cpp b/hub/install/extractor.cpp index 2600578..d9bfa03 100644 --- a/hub/install/extractor.cpp +++ b/hub/install/extractor.cpp @@ -25,9 +25,10 @@ Extractor::~Extractor() { m_thread.wait(); } -void Extractor::extract(const QString archiveName, const QString outputPath) { +void Extractor::extract(const QString &archiveName, const QString &outputPath, const QString &version) { m_archiveName = archiveName; m_outputPath = outputPath; + m_version = version; m_thread.start(); } @@ -89,10 +90,17 @@ void Extractor::doWork() { SzArEx_GetFileNameUtf16(&db, i, temp); QString sub(QString::fromUtf16(temp)); - sub.remove("sdk/"); - sub.remove("install-root/"); - sub.remove("release/"); - QString path(m_outputPath + "/" + sub); + QStringList list = sub.split('/', Qt::SkipEmptyParts); + list.removeOne("sdk"); + list.removeOne("install-root"); + list.removeOne("release"); + if(list.isEmpty()) { + continue; + } + + list.front() = m_version; + + QString path(m_outputPath + "/" + list.join('/')); if(isDir) { QDir dir; diff --git a/hub/install/extractor.h b/hub/install/extractor.h index a113e26..a288ec8 100644 --- a/hub/install/extractor.h +++ b/hub/install/extractor.h @@ -10,7 +10,7 @@ class Extractor : public QObject { explicit Extractor(QObject *parent = 0); ~Extractor(); - void extract(const QString archiveName, const QString outputPath); + void extract(const QString &archiveName, const QString &outputPath, const QString &version); signals: void extractProgress(qint64 filesExtracted, qint64 filesTotal); @@ -24,6 +24,7 @@ private slots: QString m_archiveName; QString m_outputPath; + QString m_version; }; diff --git a/hub/install/installmodel.cpp b/hub/install/installmodel.cpp index 15f6c43..6ae1ee9 100644 --- a/hub/install/installmodel.cpp +++ b/hub/install/installmodel.cpp @@ -41,13 +41,13 @@ void Sdk::checkInstalled() { if(path.isEmpty()) { path = Settings::instance()->sdkDir() + "/" + version; #ifdef Q_OS_WINDOWS - path += "/windows/x86_64/bin/WorldEditor.exe"; + path += QString("/windows/x86_64/bin/") + EDITOR_NAME + ".exe"; #endif #ifdef Q_OS_LINUX - path += "/linux/x86_64/bin/WorldEditor"; + path += QString("/linux/x86_64/bin/") + EDITOR_NAME; #endif #ifdef Q_OS_MACOS - path += "/macos/arm64/WorldEditor.app/Contents/MacOS/WorldEditor"; + path += QString("/macos/arm64/") + EDITOR_NAME + ".app/Contents/MacOS/" + EDITOR_NAME"; #endif } @@ -128,7 +128,7 @@ int InstallModel::rowCount(const QModelIndex &parent) const { int count = 0; for(auto &it : m_sdk) { - if(it.installed) { + if(it.installed || it.progress != -1) { count++; } } @@ -140,7 +140,7 @@ QVariant InstallModel::data(const QModelIndex &index, int role) const { if(index.isValid()) { int idx = -1; for(auto &it : m_sdk) { - if(it.installed) { + if(it.installed || it.progress != -1) { idx++; if(idx == index.row()) { QFileInfo info(it.path); @@ -319,7 +319,7 @@ void InstallModel::locateSdk() { QString path = QFileDialog::getOpenFileName(nullptr, tr("Locate the ") + EDITOR_NAME, Settings::instance()->sdkDir(), name); if(!path.isEmpty()) { #ifdef Q_OS_MACOS - path += "/Contents/MacOS/WorldEditor"; + path += QString("/Contents/MacOS/") + EDITOR_NAME; #endif QFileInfo info(path); @@ -433,15 +433,14 @@ void InstallModel::commitInstallRecord() { QStringList values; foreach(auto &it, m_sdk) { - if(it.installed) { + if(it.installed || it.progress != -1) { values << it.path + ";" + it.version; } } values.removeDuplicates(); settings.setValue(gInstalls, values); - emit layoutAboutToBeChanged(); - emit layoutChanged(); + onJobUpdated(); } void InstallModel::onJobFinished() { @@ -451,8 +450,7 @@ void InstallModel::onJobFinished() { job->deleteLater(); } - emit layoutAboutToBeChanged(); - emit layoutChanged(); + onJobUpdated(); } void InstallModel::onJobUpdated() { diff --git a/hub/res/hub.qrc b/hub/res/hub.qrc index 51d53cb..c528fc0 100644 --- a/hub/res/hub.qrc +++ b/hub/res/hub.qrc @@ -18,5 +18,6 @@ qml/components/StyledPathEdit.qml icons/close.png icons/folder.png + qml/components/SideButton.qml diff --git a/hub/res/qml/Blog.qml b/hub/res/qml/Blog.qml index d702718..5b33c91 100644 --- a/hub/res/qml/Blog.qml +++ b/hub/res/qml/Blog.qml @@ -37,125 +37,27 @@ Item { anchors.topMargin: 10 height: 1 - color: theme.blue + color: theme.accentColor } - Item { - id: projectsView + GridView { + model: feedManager + anchors.margins: 20 anchors.left: parent.left anchors.right: parent.right anchors.top: pannel.bottom anchors.bottom: parent.bottom - ColumnLayout { - id: view - anchors.fill: parent - anchors.margins: spacing - spacing: 30 - - Rectangle { - id: tile00 - height: 300 - color: theme.panel - Layout.fillWidth: true - - property variant headerData: undefined - - Row { - spacing: view.spacing - anchors.fill: parent - - Image { - anchors.bottom: parent.bottom - anchors.top: parent.top - width: parent.width / 2 - fillMode: Image.PreserveAspectCrop - clip: true - source: (typeof(tile00.headerData) !== "undefined") ? tile00.headerData.thumbnail : "" - } - Column { - anchors.top: parent.top - anchors.topMargin: 30 - spacing: 20 - width: parent.width / 2 - anchors.margins: spacing - Text { - width: parent.width - parent.spacing * 2 - font.pixelSize: 24 - color: theme.textColor - text: (typeof(tile00.headerData) !== "undefined") ? tile00.headerData.title : "" - wrapMode: Text.WordWrap - } - Text { - width: parent.width - parent.spacing * 2 - font.pixelSize: 16 - color: theme.textColor - text: (typeof(tile00.headerData) !== "undefined") ? tile00.headerData.summary : "" - wrapMode: Text.WordWrap - } - } - } - - MouseArea { - anchors.fill: parent - hoverEnabled: true - onEntered: parent.color = theme.hoverPanel - onExited: parent.color = theme.panel - onClicked: { - if(typeof(headerData) !== "undefined") { - Qt.openUrlExternally(blogData.link) - } - } - } - } - - RowLayout { - Layout.fillWidth: true - spacing: view.spacing - - BlogTile { - id: tile11 - Layout.fillWidth: true - } - BlogTile { - id: tile12 - Layout.fillWidth: true - } - BlogTile { - id: tile13 - Layout.fillWidth: true - } - BlogTile { - id: tile14 - Layout.fillWidth: true - } - } - - Item { - Layout.fillHeight: true - } - } - - XmlListModel { - source: feedManager.blogFeed - query: "/feed/entry" - - XmlListModelRole { name: "title"; elementName: "title/string()" } - XmlListModelRole { name: "summary"; elementName: "fn:replace(summary/string(), '\<a href=.*\/a\>', '')" } - XmlListModelRole { name: "thumbnail"; elementName: "media:thumbnail/@url/string()" } - XmlListModelRole { name: "link"; elementName: "link/@href/string()" } + clip: true - onStatusChanged: { - if(status == XmlListModel.Ready) { - tile00.headerData = get(0) + cellWidth: 310 + cellHeight: 510 - tile11.blogData = get(1) - tile12.blogData = get(2) - tile13.blogData = get(3) - tile14.blogData = get(4) - } - } + delegate: BlogTile { + id: tile + Layout.fillWidth: true + blogData: model } } } diff --git a/hub/res/qml/Installs.qml b/hub/res/qml/Installs.qml index 6abefcd..ee73554 100644 --- a/hub/res/qml/Installs.qml +++ b/hub/res/qml/Installs.qml @@ -44,7 +44,7 @@ Item { background: Rectangle { anchors.fill: parent radius: theme.frameRadius - color: parent.hovered ? theme.blueLight : theme.blue + color: parent.hovered ? theme.accentColorHover : theme.accentColor } contentItem: Text { @@ -65,9 +65,9 @@ Item { width: 150 background: Rectangle { - color: theme.greyDark + color: theme.frameColor radius: theme.frameRadius - border.color: theme.greyLight + border.color: theme.frameBorder } property var versionsList: installModel ? installModel.sdkVersions() : [] @@ -116,7 +116,7 @@ Item { background: Rectangle { anchors.fill: parent radius: theme.frameRadius - color: parent.hovered ? theme.hoverPanel : theme.panel + color: parent.hovered ? theme.frameColorHover : theme.frameColor } contentItem: Text { @@ -138,7 +138,7 @@ Item { anchors.topMargin: 10 height: 1 - color: theme.blue + color: theme.accentColor } ListView { @@ -169,12 +169,11 @@ Item { height: 70 clip: true - color: theme.hoverPanel + color: theme.itemColor radius: theme.frameRadius Row { - anchors.leftMargin: 20 - anchors.topMargin: 5 + anchors.margins: 5 spacing: 20 anchors.fill: parent @@ -213,23 +212,6 @@ Item { } } } -/* - Rectangle { - id: progressBar - - visible: true//progress > -1 - - anchors.top: parent.bottom - anchors.left: logo.right - anchors.leftMargin: 10 - - height: theme.textSize - width: progress - radius: theme.frameRadius / 2 - - color: theme.blue - } -*/ } ToolButton { @@ -244,7 +226,7 @@ Item { background: Rectangle { anchors.fill: parent radius: theme.frameRadius - color: parent.hovered ? theme.hoverPanel : "transparent" + color: parent.hovered ? theme.itemColorHover : theme.itemColor } onClicked: itemContextMenu.open() @@ -257,9 +239,9 @@ Item { width: 150 background: Rectangle { - color: theme.greyDark + color: theme.frameColor radius: theme.frameRadius - border.color: theme.greyLight + border.color: theme.frameBorder } property var versionsList: installModel ? installModel.sdkVersions() : [] diff --git a/hub/res/qml/Learn.qml b/hub/res/qml/Learn.qml index b8b8f40..1d4efc7 100644 --- a/hub/res/qml/Learn.qml +++ b/hub/res/qml/Learn.qml @@ -32,6 +32,6 @@ Item { anchors.topMargin: 10 height: 1 - color: theme.blue + color: theme.accent } } diff --git a/hub/res/qml/Projects.qml b/hub/res/qml/Projects.qml index 42a6db2..52c7046 100644 --- a/hub/res/qml/Projects.qml +++ b/hub/res/qml/Projects.qml @@ -42,7 +42,7 @@ Item { background: Rectangle { anchors.fill: parent radius: theme.frameRadius - color: parent.hovered ? theme.blueLight : theme.blue + color: parent.hovered ? theme.accentColorHover : theme.accentColor } contentItem: Row { @@ -79,7 +79,7 @@ Item { background: Rectangle { anchors.fill: parent radius: theme.frameRadius - color: parent.hovered ? theme.hoverPanel : theme.panel + color: parent.hovered ? theme.frameColorHover : theme.frameColor } contentItem: Text { @@ -101,7 +101,7 @@ Item { anchors.topMargin: 10 height: 1 - color: theme.blue + color: theme.accentColor } ListView { @@ -130,11 +130,10 @@ Item { Layout.fillHeight: true radius: theme.frameRadius - color: "transparent" + color: theme.itemColor Row { - anchors.leftMargin: 20 - anchors.topMargin: 5 + anchors.margins: 5 spacing: 20 anchors.fill: parent @@ -166,8 +165,8 @@ Item { anchors.fill: parent hoverEnabled: true onClicked: projectsManager.openProject(path) - onEntered: parent.color = theme.hoverPanel - onExited: parent.color = "transparent" + onEntered: parent.color = theme.itemColorHover + onExited: parent.color = theme.itemColor } } @@ -183,7 +182,7 @@ Item { background: Rectangle { anchors.fill: parent radius: theme.frameRadius - color: parent.hovered ? theme.hoverPanel : "transparent" + color: parent.hovered ? theme.itemColorHover : theme.itemColor } onClicked: itemContextMenu.open() @@ -196,9 +195,9 @@ Item { width: 150 background: Rectangle { - color: theme.greyDark + color: theme.frameColor radius: theme.frameRadius - border.color: theme.greyLight + border.color: theme.frameBorder } MenuSeparator { diff --git a/hub/res/qml/Settings.qml b/hub/res/qml/Settings.qml index e8b5830..5c10c40 100644 --- a/hub/res/qml/Settings.qml +++ b/hub/res/qml/Settings.qml @@ -30,7 +30,7 @@ Item { anchors.topMargin: 10 height: 1 - color: theme.blue + color: theme.accentColor } } diff --git a/hub/res/qml/Startup.qml b/hub/res/qml/Startup.qml index c7c9f9d..3dfb83f 100644 --- a/hub/res/qml/Startup.qml +++ b/hub/res/qml/Startup.qml @@ -3,10 +3,11 @@ import QtQuick.Controls.Basic import QtQuick.Window import "qrc:/qml/." +import "qrc:/qml/components/." Window { id: rect - color: theme.grey + color: theme.background width: 1280 height: 720 visible: true @@ -18,7 +19,7 @@ Window { Rectangle { id: leftPanel width: 200 - color: theme.panel + color: theme.itemColor anchors.bottom: parent.bottom anchors.top: parent.top anchors.left: parent.left @@ -42,6 +43,21 @@ Window { source: "qrc:/icons/thunderlight.svg" } + Text { + id: label + + anchors.left: image.right + anchors.bottom: image.bottom + anchors.leftMargin: 10 + + text: "Hub" + + font.bold: true + font.pixelSize: theme.h1 + verticalAlignment: Text.AlignBottom + color: theme.textColor + } + MouseArea { anchors.fill: parent cursorShape: Qt.PointingHandCursor @@ -62,95 +78,30 @@ Window { anchors.rightMargin: 10 anchors.leftMargin: 10 - RadioButton { + SideButton { checked: true text: qsTr("Projects") - indicator: Rectangle { - width: 4 - height: parent.height - visible: parent.checked - color: theme.blue - } - contentItem: Text { - text: parent.text - font.pixelSize: theme.h2 - color: parent.checked ? theme.textColor : theme.greyLight - verticalAlignment: Text.AlignVCenter - leftPadding: 10 - } - onClicked: pageLoader.source = "Projects.qml" + source: "Projects.qml" } - RadioButton { + SideButton { text: qsTr("Installs") - indicator: Rectangle { - width: 4 - height: parent.height - visible: parent.checked - color: theme.blue - } - contentItem: Text { - text: parent.text - font.pixelSize: theme.h2 - color: parent.checked ? theme.textColor : theme.greyLight - verticalAlignment: Text.AlignVCenter - leftPadding: 10 - } - onClicked: pageLoader.source = "Installs.qml" + source: "Installs.qml" } /* - RadioButton { + SideButton { text: qsTr("Learn") - indicator: Rectangle { - width: 4 - height: parent.height - visible: parent.checked - color: theme.blue - } - contentItem: Text { - text: parent.text - font.pixelSize: theme.h2 - color: parent.checked ? theme.textColor : theme.greyLight - verticalAlignment: Text.AlignVCenter - leftPadding: 10 - } - onClicked: pageLoader.source = "Learn.qml" + source: "Learn.qml" } - - RadioButton { +*/ + SideButton { text: qsTr("Blog") - indicator: Rectangle { - width: 4 - height: parent.height - visible: parent.checked - color: theme.blue - } - contentItem: Text { - text: parent.text - font.pixelSize: theme.h2 - color: parent.checked ? theme.textColor : theme.greyLight - verticalAlignment: Text.AlignVCenter - leftPadding: 10 - } - onClicked: pageLoader.source = "Blog.qml" + source: "Blog.qml" } -*/ - RadioButton { + + SideButton { text: qsTr("Settings") - indicator: Rectangle { - width: 4 - height: parent.height - visible: parent.checked - color: theme.blue - } - contentItem: Text { - text: parent.text - font.pixelSize: theme.h2 - color: parent.checked ? theme.textColor : theme.greyLight - verticalAlignment: Text.AlignVCenter - leftPadding: 10 - } - onClicked: pageLoader.source = "Settings.qml" + source: "Settings.qml" } } } diff --git a/hub/res/qml/Theme.qml b/hub/res/qml/Theme.qml index c2b3a5d..0ea2fdb 100644 --- a/hub/res/qml/Theme.qml +++ b/hub/res/qml/Theme.qml @@ -2,15 +2,18 @@ import QtQuick import QtQuick.Controls Item { - property string textColor: "#ffffff" + property string panelLight: "#60000000" + property string panelLightHover: "#30000000" - property string panel: "#60000000" - property string hoverPanel: "#30000000" + property string panelDark: "#22ffffff" + property string panelDarkHover: "#32ffffff" - property string grey: "#606060" + property string white: "#ffffff" + + property string grey: "#404040" property string greyLight: "#8d8d8d" - property string greyDark: "#363636" - property string greyHover: "#404040" + property string greyDark: "#222222" + property string greyHover: "#606060" property string blue: "#0277bd" property string blueLight: "#58a5f0" @@ -28,6 +31,21 @@ Item { property string redHover: "#d32f2f" property int frameRadius: 4 + property string frameBorder: greyLight + property string frameColor: grey + property string frameColorHover: greyHover + + property string itemColor: panelDark + property string itemColorHover: panelDarkHover + + property string accentColor: blue + property string accentColorHover: blueLight + + property string textColor: white + property string textColorDiabled: greyLight + + property string background: greyDark + property string modalBackground: panelDark property int textSize: 14 property int h1: 28 diff --git a/hub/res/qml/components/BlogTile.qml b/hub/res/qml/components/BlogTile.qml index eb139e2..ce456f3 100644 --- a/hub/res/qml/components/BlogTile.qml +++ b/hub/res/qml/components/BlogTile.qml @@ -2,8 +2,11 @@ import QtQuick Rectangle { id: rect + width: 300 height: 500 - color: theme.panel + color: theme.itemColor + radius: theme.frameRadius + clip: true property variant blogData: undefined @@ -12,28 +15,34 @@ Rectangle { width: rect.width height: rect.height / 2 fillMode: Image.PreserveAspectCrop - clip: true - source: (typeof(blogData) !== "undefined") ? blogData.thumbnail : "" + source: (typeof(blogData) !== "undefined") ? blogData.icon : "" + } + Rectangle { + id: separator + anchors.top: thumbnail.bottom + anchors.left: parent.left + anchors.right: parent.right + + height: 1 + color: theme.accentColor } Column { anchors.left: parent.left - anchors.top: thumbnail.bottom - anchors.margins: spacing + anchors.top: separator.bottom + padding: spacing spacing: 20 Text { - anchors.leftMargin: 20 - width: rect.width - 20 - font.pixelSize: 24 + width: rect.width - parent.spacing * 2 + font.pixelSize: theme.h2 color: theme.textColor text: (typeof(blogData) !== "undefined") ? blogData.title : "" wrapMode: Text.WordWrap } Text { - anchors.leftMargin: 20 - width: rect.width - 20 - font.pixelSize: 16 + width: rect.width - parent.spacing * 2 + font.pixelSize: theme.textSize color: theme.textColor - text: (typeof(blogData) !== "undefined") ? blogData.summary : "" + text: (typeof(blogData) !== "undefined") ? blogData.brief : "" wrapMode: Text.WordWrap } } @@ -41,8 +50,8 @@ Rectangle { MouseArea { anchors.fill: parent hoverEnabled: true - onEntered: parent.color = theme.hoverPanel - onExited: parent.color = theme.panel + onEntered: parent.color = theme.itemColorHover + onExited: parent.color = theme.itemColor onClicked: { if(typeof(blogData) !== "undefined") { Qt.openUrlExternally(blogData.link) diff --git a/hub/res/qml/components/Components.qml b/hub/res/qml/components/Components.qml index 785cddd..b99afa6 100644 --- a/hub/res/qml/components/Components.qml +++ b/hub/res/qml/components/Components.qml @@ -7,7 +7,7 @@ Rectangle { anchors.fill: parent - color: theme.hoverPanel + color: theme.modalBackground property string version: "" property var modules: [] @@ -23,9 +23,8 @@ Rectangle { anchors.centerIn: parent radius: theme.frameRadius - color: theme.greyDark - border.color: theme.greyLight - + color: theme.frameColor + border.color: theme.frameBorder ColumnLayout { anchors.fill: parent @@ -59,7 +58,7 @@ Rectangle { background: Rectangle { anchors.fill: parent radius: theme.frameRadius - color: parent.hovered ? theme.greyHover : theme.grey + color: parent.hovered ? theme.frameColorHover : theme.frameColor } Shortcut { @@ -74,7 +73,7 @@ Rectangle { Rectangle { Layout.fillWidth: true height: 1 - color: theme.greyLight + color: theme.frameBorder } ListView { @@ -133,7 +132,7 @@ Rectangle { Rectangle { Layout.fillWidth: true height: 1 - color: theme.greyLight + color: theme.frameBorder } RowLayout { @@ -150,7 +149,7 @@ Rectangle { background: Rectangle { anchors.fill: parent radius: theme.frameRadius - color: parent.hovered ? theme.blueHover : theme.blue + color: parent.hovered ? theme.accentColorHover : theme.accentColor } contentItem: Text { @@ -178,7 +177,7 @@ Rectangle { background: Rectangle { anchors.fill: parent radius: theme.frameRadius - color: parent.hovered ? theme.greyHover : theme.grey + color: parent.hovered ? theme.frameColorHover : theme.frameColor } contentItem: Text { diff --git a/hub/res/qml/components/SideButton.qml b/hub/res/qml/components/SideButton.qml new file mode 100644 index 0000000..06b9c58 --- /dev/null +++ b/hub/res/qml/components/SideButton.qml @@ -0,0 +1,21 @@ +import QtQuick +import QtQuick.Controls.Basic + +RadioButton { + property string source: "" + + indicator: Rectangle { + width: 4 + height: parent.height + visible: parent.checked + color: theme.accentColor + } + contentItem: Text { + text: parent.text + font.pixelSize: theme.h2 + color: parent.checked ? theme.textColor : theme.textColorDiabled + verticalAlignment: Text.AlignVCenter + leftPadding: 10 + } + onClicked: pageLoader.source = source +} diff --git a/hub/res/qml/components/StyledItem.qml b/hub/res/qml/components/StyledItem.qml index 52605d5..d4cdcac 100644 --- a/hub/res/qml/components/StyledItem.qml +++ b/hub/res/qml/components/StyledItem.qml @@ -17,6 +17,6 @@ MenuItem { anchors.leftMargin: 1 anchors.rightMargin: 1 - color: parent.highlighted ? theme.greyHover : "transparent" + color: parent.highlighted ? theme.itemColorHover : theme.itemColor } } diff --git a/hub/res/qml/components/StyledPathEdit.qml b/hub/res/qml/components/StyledPathEdit.qml index 163d299..f4d5b6e 100644 --- a/hub/res/qml/components/StyledPathEdit.qml +++ b/hub/res/qml/components/StyledPathEdit.qml @@ -14,9 +14,9 @@ ToolButton { } background: Rectangle { - color: parent.hovered ? theme.hoverPanel : theme.panel + color: parent.hovered ? theme.frameColorHover : theme.frameColor radius: theme.frameRadius - border.color: theme.greyLight + border.color: theme.frameBorder } contentItem: Row { diff --git a/thunder-hub.qbs b/thunder-hub.qbs index 0694c56..9c55fcd 100644 --- a/thunder-hub.qbs +++ b/thunder-hub.qbs @@ -19,18 +19,9 @@ Project { Probe { id: probe - property string REVISION property string YEAR configure: { YEAR = new Date().getFullYear().toString() - REVISION = "develop" - var p = new Process() - p.setWorkingDirectory(thunder.sourceDirectory) - if (p.exec("git", ["rev-parse", "HEAD"]) === 0) { - REVISION = p.readStdOut().trim() - } else { - console.error(p.readStdErr()) - } } } @@ -50,9 +41,7 @@ Project { "PRODUCT_NAME=\"" + PRODUCT_NAME + "\"", "PRODUCT_VERSION=\"" + PRODUCT_VERSION + "\"", "EDITOR_NAME=\"" + EDITOR_NAME + "\"", - "COPYRIGHT_YEAR=" + COPYRIGHT_YEAR, - "REVISION=\"" + probe.REVISION + "\"", - "LEGAL=\"" + probe.LEGAL + "\"" + "COPYRIGHT_YEAR=" + COPYRIGHT_YEAR ]; return result; } From 3cae44c48cf1b29ea10990bee23d18ad3fff9cfc Mon Sep 17 00:00:00 2001 From: Evgeny Prikazchikov Date: Mon, 23 Mar 2026 16:26:42 +0300 Subject: [PATCH 2/2] update --- hub/install/installmodel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hub/install/installmodel.cpp b/hub/install/installmodel.cpp index 6ae1ee9..330de77 100644 --- a/hub/install/installmodel.cpp +++ b/hub/install/installmodel.cpp @@ -47,7 +47,7 @@ void Sdk::checkInstalled() { path += QString("/linux/x86_64/bin/") + EDITOR_NAME; #endif #ifdef Q_OS_MACOS - path += QString("/macos/arm64/") + EDITOR_NAME + ".app/Contents/MacOS/" + EDITOR_NAME"; + path += QString("/macos/arm64/") + EDITOR_NAME + ".app/Contents/MacOS/" + EDITOR_NAME; #endif }