diff --git a/plugins/Marketplace/PackageModel.py b/plugins/Marketplace/PackageModel.py
index e57a402fd6..0e73cac3b7 100644
--- a/plugins/Marketplace/PackageModel.py
+++ b/plugins/Marketplace/PackageModel.py
@@ -4,10 +4,12 @@
from PyQt5.QtCore import pyqtProperty, QObject
from typing import Any, Dict, Optional
-from UM.i18n import i18nCatalog # To translate placeholder names if data is not present.
+from UM.Util import parseBool
+from UM.i18n import i18nCatalog # To translate placeholder names if data is not present.
catalog = i18nCatalog("cura")
+
class PackageModel(QObject):
"""
Represents a package, containing all the relevant information to be displayed about a package.
@@ -25,17 +27,65 @@ class PackageModel(QObject):
"""
super().__init__(parent)
self._package_id = package_data.get("package_id", "UnknownPackageId")
+
+ self._icon_url = package_data.get("icon_url", "")
self._display_name = package_data.get("display_name", catalog.i18nc("@label:property", "Unknown Package"))
+ self._is_verified = "verified" in package_data.get("tags", [])
+ self._package_version = package_data.get("package_version", "") # Display purpose, no need for 'UM.Version'.
+ self._package_info_url = package_data.get("website", "") # Not to be confused with 'download_url'.
+ self._download_count = package_data.get("download_count", 0)
+ self._description = package_data.get("description", "")
+
+ self._download_url = package_data.get("download_url", "") # Not used yet, will be.
+ self._release_notes = package_data.get("release_notes", "") # Not used yet, propose to add to description?
+
+ author_data = package_data.get("author", {})
+ self._author_name = author_data.get("display_name", catalog.i18nc("@label:property", "Unknown Author"))
+ self._author_info_url = author_data.get("website", "")
+
self._section_title = section_title
+ # Note that there's a lot more info in the package_data than just these specified here.
@pyqtProperty(str, constant = True)
def packageId(self) -> str:
return self._package_id
+ @pyqtProperty(str, constant=True)
+ def iconUrl(self):
+ return self._icon_url
+
@pyqtProperty(str, constant = True)
def displayName(self) -> str:
return self._display_name
+ @pyqtProperty(bool, constant=True)
+ def isVerified(self):
+ return self._is_verified
+
+ @pyqtProperty(str, constant=True)
+ def packageVersion(self):
+ return self._package_version
+
+ @pyqtProperty(str, constant=True)
+ def packageInfoUrl(self):
+ return self._package_info_url
+
+ @pyqtProperty(int, constant=True)
+ def downloadCount(self):
+ return self._download_count
+
+ @pyqtProperty(str, constant=True)
+ def description(self):
+ return self._description
+
+ @pyqtProperty(str, constant=True)
+ def authorName(self):
+ return self._author_name
+
+ @pyqtProperty(str, constant=True)
+ def authorInfoUrl(self):
+ return self._author_info_url
+
@pyqtProperty(str, constant = True)
def sectionTitle(self) -> Optional[str]:
return self._section_title
diff --git a/plugins/Marketplace/resources/images/placeholder.svg b/plugins/Marketplace/resources/images/placeholder.svg
new file mode 100644
index 0000000000..cc674a4b38
--- /dev/null
+++ b/plugins/Marketplace/resources/images/placeholder.svg
@@ -0,0 +1,3 @@
+
diff --git a/plugins/Marketplace/resources/qml/PackageCard.qml b/plugins/Marketplace/resources/qml/PackageCard.qml
new file mode 100644
index 0000000000..3a185a3e02
--- /dev/null
+++ b/plugins/Marketplace/resources/qml/PackageCard.qml
@@ -0,0 +1,351 @@
+// Copyright (c) 2021 Ultimaker B.V.
+// Cura is released under the terms of the LGPLv3 or higher.
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+import QtQuick.Layouts 1.1
+
+import UM 1.6 as UM
+import Cura 1.6 as Cura
+
+Rectangle
+{
+ property var packageData
+
+ width: parent ? parent.width - UM.Theme.getSize("default_margin").width : 0
+ height: childrenRect.height
+
+ color: UM.Theme.getColor("main_background")
+ radius: UM.Theme.getSize("default_radius").width
+
+ states:
+ [
+ State
+ {
+ name: "Folded"
+ when: true // TODO
+ PropertyChanges
+ {
+ target: downloadCountRow
+ visible: false
+ }
+ PropertyChanges
+ {
+ target: descriptionArea
+ visible: true
+ }
+ },
+ State
+ {
+ name: "Header"
+ when: false // TODO
+ PropertyChanges
+ {
+ target: downloadCountRow
+ visible: true
+ }
+ PropertyChanges
+ {
+ target: descriptionArea
+ visible: false
+ }
+ }
+ ]
+
+ RowLayout
+ {
+ width: parent.width - UM.Theme.getSize("thin_margin").width * 2
+ height: childrenRect.height + UM.Theme.getSize("thin_margin").height * 2
+ x: UM.Theme.getSize("thin_margin").width
+ y: UM.Theme.getSize("thin_margin").height
+
+ spacing: UM.Theme.getSize("default_margin").width
+
+ Image //Separate column for icon on the left.
+ {
+ Layout.preferredWidth: UM.Theme.getSize("card_icon").width
+ Layout.preferredHeight: UM.Theme.getSize("card_icon").height
+ Layout.alignment: Qt.AlignTop
+
+ source: packageData.iconUrl != "" ? packageData.iconUrl : "../images/placeholder.svg"
+ }
+
+ Column
+ {
+ Layout.fillWidth: true
+ Layout.alignment: Qt.AlignTop
+
+ RowLayout //Title row.
+ {
+ Layout.alignment: Qt.AlignTop
+ width: parent.width
+
+ Label
+ {
+ Layout.alignment: Qt.AlignTop
+
+ text: packageData.displayName
+ font: UM.Theme.getFont("medium_bold")
+ color: UM.Theme.getColor("text")
+ }
+
+ Row //Row inside row, but the non-layout version skips invisible elements.
+ {
+ spacing: parent.spacing
+ Layout.alignment: Qt.AlignTop
+
+ Control
+ {
+ width: UM.Theme.getSize("card_tiny_icon").width
+ height: UM.Theme.getSize("card_tiny_icon").height
+ Layout.alignment: Qt.AlignTop
+
+ enabled: packageData.isVerified
+ visible: packageData.isVerified
+
+ Cura.ToolTip
+ {
+ tooltipText: catalog.i18nc("@info", "Verified")
+ visible: parent.hovered
+ }
+
+ UM.RecolorImage
+ {
+ anchors.fill: parent
+
+ color: UM.Theme.getColor("primary")
+ source: UM.Theme.getIcon("CheckCircle")
+ }
+
+ //NOTE: Can we link to something here? (Probably a static link explaining what verified is):
+ // onClicked: Qt.openUrlExternally( XXXXXX )
+ }
+
+ Control
+ {
+ width: UM.Theme.getSize("card_tiny_icon").width
+ height: UM.Theme.getSize("card_tiny_icon").height
+ Layout.alignment: Qt.AlignTop
+
+ enabled: false // remove!
+ visible: false // replace packageInfo.XXXXXX
+ // TODO: waiting for materials card implementation
+
+ Cura.ToolTip
+ {
+ tooltipText: "" // TODO
+ visible: parent.hovered
+ }
+
+ UM.RecolorImage
+ {
+ anchors.fill: parent
+
+ color: UM.Theme.getColor("primary")
+ source: UM.Theme.getIcon("CheckCircle") // TODO
+ }
+
+ // onClicked: Qt.openUrlExternally( XXXXXX ) // TODO
+ }
+ }
+
+ Label
+ {
+ Layout.fillWidth: true
+ Layout.alignment: Qt.AlignTop
+
+ text: packageData.packageVersion
+ font: UM.Theme.getFont("default")
+ color: UM.Theme.getColor("text")
+ }
+
+ Button
+ {
+ id: externalLinkButton
+
+ Layout.preferredWidth: UM.Theme.getSize("card_tiny_icon").width
+ Layout.preferredHeight: UM.Theme.getSize("card_tiny_icon").height
+ Layout.alignment: Qt.AlignTop
+
+ Rectangle
+ {
+ anchors.fill: parent
+ color: externalLinkButton.hovered ? UM.Theme.getColor("action_button_hovered") : UM.Theme.getColor("detail_background")
+
+ UM.RecolorImage
+ {
+ anchors.fill: parent
+ color: externalLinkButton.hovered ? UM.Theme.getColor("text_link") : UM.Theme.getColor("text")
+ source: UM.Theme.getIcon("LinkExternal")
+ }
+ }
+
+ onClicked: Qt.openUrlExternally(packageData.packageInfoUrl)
+ }
+ }
+
+ RowLayout
+ {
+ id: downloadCountRow
+
+ width: childrenRect.width
+ height: childrenRect.height
+ x: UM.Theme.getSize("thin_margin").width
+ y: UM.Theme.getSize("thin_margin").height
+
+ spacing: UM.Theme.getSize("thin_margin").width
+
+ UM.RecolorImage
+ {
+ id: downloadCountIcon
+ width: UM.Theme.getSize("card_tiny_icon").width
+ height: UM.Theme.getSize("card_tiny_icon").height
+ color: UM.Theme.getColor("icon")
+
+ source: UM.Theme.getIcon("Download")
+ }
+
+ Label
+ {
+ id: downloadCountLabel
+ text: packageData.downloadCount
+ }
+ }
+
+ Item
+ {
+ id: descriptionArea
+ width: parent.width
+ height: descriptionLabel.height
+
+ Label
+ {
+ id: descriptionLabel
+ width: parent.width
+ property real lastLineWidth: 0; //Store the width of the last line, to properly position the elision.
+
+ text: packageData.description
+ font: UM.Theme.getFont("medium")
+ color: UM.Theme.getColor("text")
+ maximumLineCount: 2
+ wrapMode: Text.Wrap
+ elide: Text.ElideRight
+
+ onLineLaidOut:
+ {
+ if(truncated && line.isLast)
+ {
+ let max_line_width = parent.width - readMoreButton.width - fontMetrics.advanceWidth("… ") - UM.Theme.getSize("default_margin").width;
+ if(line.implicitWidth > max_line_width)
+ {
+ line.width = max_line_width;
+ }
+ else
+ {
+ line.width = line.implicitWidth - fontMetrics.advanceWidth("…"); //Truncate the ellipsis. We're adding this ourselves.
+ }
+ descriptionLabel.lastLineWidth = line.implicitWidth;
+ }
+ }
+ }
+
+ Cura.TertiaryButton
+ {
+ id: readMoreButton
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+ height: fontMetrics.height //Height of a single line.
+
+ text: catalog.i18nc("@info", "Read more")
+ iconSource: UM.Theme.getIcon("LinkExternal")
+
+ visible: descriptionLabel.truncated
+ enabled: visible
+ leftPadding: UM.Theme.getSize("default_margin").width
+ rightPadding: UM.Theme.getSize("wide_margin").width
+ textFont: descriptionLabel.font
+ isIconOnRightSide: true
+
+ // NOTE: Is this the right URL for this action?
+ onClicked: Qt.openUrlExternally(packageData.packageInfoUrl)
+ }
+
+ Label
+ {
+ anchors.left: parent.left
+ anchors.leftMargin: descriptionLabel.lastLineWidth
+ anchors.bottom: readMoreButton.bottom
+
+ text: "… "
+ font: descriptionLabel.font
+ color: descriptionLabel.color
+ visible: descriptionLabel.truncated
+ }
+ }
+
+ RowLayout //Author and action buttons.
+ {
+ width: parent.width
+ Layout.alignment: Qt.AlignTop
+ spacing: UM.Theme.getSize("thin_margin").width
+
+ Label
+ {
+ id: authorBy
+ Layout.alignment: Qt.AlignTop
+
+ text: catalog.i18nc("@label", "By")
+ font: UM.Theme.getFont("default")
+ color: UM.Theme.getColor("text")
+ }
+
+ Cura.TertiaryButton
+ {
+ Layout.fillWidth: true
+ Layout.preferredHeight: authorBy.height
+ Layout.alignment: Qt.AlignTop
+
+ text: packageData.authorName
+ textFont: UM.Theme.getFont("default_bold")
+ textColor: UM.Theme.getColor("text") // override normal link color
+ leftPadding: 0
+ rightPadding: 0
+ iconSource: UM.Theme.getIcon("LinkExternal")
+ isIconOnRightSide: true
+
+ onClicked: Qt.openUrlExternally(packageData.authorInfoUrl)
+ }
+
+ Cura.SecondaryButton
+ {
+ id: disableButton
+ Layout.alignment: Qt.AlignTop
+ text: catalog.i18nc("@button", "Disable")
+ visible: false // not functional right now, also only when unfolding and required
+ }
+
+ Cura.SecondaryButton
+ {
+ id: uninstallButton
+ Layout.alignment: Qt.AlignTop
+ text: catalog.i18nc("@button", "Uninstall")
+ visible: false // not functional right now, also only when unfolding and required
+ }
+
+ Cura.PrimaryButton
+ {
+ id: installButton
+ Layout.alignment: Qt.AlignTop
+ text: catalog.i18nc("@button", "Update") // OR Download, if new!
+ visible: false // not functional right now, also only when unfolding and required
+ }
+ }
+ }
+ }
+
+ FontMetrics
+ {
+ id: fontMetrics
+ font: UM.Theme.getFont("medium")
+ }
+}
diff --git a/plugins/Marketplace/resources/qml/Packages.qml b/plugins/Marketplace/resources/qml/Packages.qml
index c9ddf88d16..95064c4469 100644
--- a/plugins/Marketplace/resources/qml/Packages.qml
+++ b/plugins/Marketplace/resources/qml/Packages.qml
@@ -46,24 +46,9 @@ ScrollView
}
}
- delegate: Rectangle
+ delegate: PackageCard
{
- width: packagesListview.width
- height: UM.Theme.getSize("card").height
-
- color: UM.Theme.getColor("main_background")
- radius: UM.Theme.getSize("default_radius").width
-
- Label
- {
- anchors.verticalCenter: parent.verticalCenter
- anchors.left: parent.left
- anchors.leftMargin: Math.round((parent.height - height) / 2)
-
- text: model.package.displayName
- font: UM.Theme.getFont("medium_bold")
- color: UM.Theme.getColor("text")
- }
+ packageData: model.package
}
//Wrapper item to add spacing between content and footer.
diff --git a/resources/qml/ToolTip.qml b/resources/qml/ToolTip.qml
index 3157f81d89..c4edc5a361 100644
--- a/resources/qml/ToolTip.qml
+++ b/resources/qml/ToolTip.qml
@@ -38,7 +38,7 @@ ToolTip
onAboutToHide: hide()
// If the text is not set, just set the height to 0 to prevent it from showing
- height: text != "" ? label.contentHeight + 2 * UM.Theme.getSize("thin_margin").width: 0
+ height: label.contentHeight + 2 * UM.Theme.getSize("thin_margin").width
x:
{
@@ -74,7 +74,7 @@ ToolTip
}
function show() {
- opacity = 1
+ opacity = text != "" ? 1 : 0
}
function hide() {
diff --git a/resources/themes/cura-light/icons/default/Download.svg b/resources/themes/cura-light/icons/default/Download.svg
new file mode 100644
index 0000000000..cbe0da2a99
--- /dev/null
+++ b/resources/themes/cura-light/icons/default/Download.svg
@@ -0,0 +1,3 @@
+
diff --git a/resources/themes/cura-light/theme.json b/resources/themes/cura-light/theme.json
index abd4844f47..a4f3172036 100644
--- a/resources/themes/cura-light/theme.json
+++ b/resources/themes/cura-light/theme.json
@@ -553,7 +553,10 @@
"standard_list_lineheight": [1.5, 1.5],
"standard_arrow": [1.0, 1.0],
+
"card": [25.0, 8.75],
+ "card_icon": [6.0, 6.0],
+ "card_tiny_icon": [1.2, 1.2],
"button": [4, 4],
"button_icon": [2.5, 2.5],