Files
DankMaterialShell/quickshell/Modules/Settings/PluginCard.qml
T

258 lines
8.4 KiB
QML

import QtQuick
import Quickshell.Widgets
import qs.Common
import qs.Services
import qs.Widgets
Rectangle {
id: root
property var plugin: ({})
property bool installed: false
property bool selected: false
property string fallbackIcon: "extension"
property string previewSource: PluginService.previewUrl(plugin)
property var badges: PluginService.badgeModel(plugin)
property bool allowUninstall: false
property real previewHeight: Math.round((width - Theme.spacingS * 2) * 0.52)
readonly property int infoHeight: 100
readonly property bool compatible: PluginService.checkPluginCompatibility(plugin.requires_dms)
signal clicked
signal installRequested
signal uninstallRequested
implicitHeight: previewHeight + infoHeight + Theme.spacingS * 2 + Theme.spacingM
radius: Theme.cornerRadius
color: cardMouseArea.containsMouse ? Theme.withAlpha(Theme.surfaceVariant, 0.5) : Theme.withAlpha(Theme.surfaceVariant, 0.3)
border.color: selected ? Theme.primary : Theme.withAlpha(Theme.outline, 0.15)
border.width: selected ? 2 : 1
scale: cardMouseArea.containsMouse ? 1.012 : 1
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
Behavior on scale {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
MouseArea {
id: cardMouseArea
z: 0
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onPressed: mouse => cardRipple.trigger(mouse.x, mouse.y)
onClicked: root.clicked()
}
DankRipple {
id: cardRipple
cornerRadius: root.radius
rippleColor: Theme.surfaceVariantText
}
Item {
id: previewArea
z: 1
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: Theme.spacingS
height: root.previewHeight
ClippingRectangle {
anchors.fill: parent
radius: Theme.cornerRadius - 2
color: Theme.floatingWindowNestedSurface
CachingImage {
id: cardPreview
anchors.fill: parent
imagePath: root.previewSource
maxCacheSize: 640
fillMode: Image.PreserveAspectCrop
animate: false
visible: status === Image.Ready
}
DankIcon {
anchors.centerIn: parent
name: root.plugin.icon || root.fallbackIcon
size: Theme.iconSize + 12
color: Theme.withAlpha(Theme.outline, 0.6)
visible: cardPreview.status !== Image.Ready
}
DankSpinner {
anchors.centerIn: parent
running: cardPreview.status === Image.Loading
visible: running
}
}
Row {
anchors.top: parent.top
anchors.left: parent.left
anchors.margins: Theme.spacingXS
spacing: Theme.spacingXXS
Repeater {
model: root.badges
PluginBadge {
required property var modelData
label: modelData.label
iconName: modelData.icon
tone: PluginService.badgeTone(modelData.tone)
onImage: true
}
}
}
PluginBadge {
anchors.top: parent.top
anchors.right: parent.right
anchors.margins: Theme.spacingXS
iconName: "thumb_up"
label: root.plugin.upvotes || 0
tone: Theme.primary
onImage: true
visible: !!root.plugin.issueUrl
}
}
Column {
z: 1
anchors.top: previewArea.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: Theme.spacingS
anchors.leftMargin: Theme.spacingM
anchors.rightMargin: Theme.spacingM
spacing: Theme.spacingXXS
Row {
width: parent.width
spacing: Theme.spacingS
DankIcon {
id: cardIcon
name: root.plugin.icon || root.fallbackIcon
size: Theme.iconSize - 4
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
width: parent.width - cardIcon.width - installAction.width - Theme.spacingS * 2
text: root.plugin.name || ""
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
elide: Text.ElideRight
maximumLineCount: 1
anchors.verticalCenter: parent.verticalCenter
}
Rectangle {
id: installAction
property string buttonState: {
if (root.installed)
return "installed";
if (!root.compatible)
return "incompatible";
return "available";
}
width: 28
height: 28
radius: 14
anchors.verticalCenter: parent.verticalCenter
color: {
switch (buttonState) {
case "installed":
return root.allowUninstall && installMouseArea.containsMouse ? Theme.withAlpha(Theme.error, 0.15) : Theme.surfaceVariant;
case "incompatible":
return Theme.withAlpha(Theme.warning, 0.15);
default:
return Theme.primary;
}
}
opacity: buttonState === "available" && installMouseArea.containsMouse ? 0.85 : 1
Behavior on opacity {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
DankIcon {
anchors.centerIn: parent
size: 15
name: {
switch (installAction.buttonState) {
case "installed":
return root.allowUninstall && installMouseArea.containsMouse ? "delete" : "check";
case "incompatible":
return "warning";
default:
return "download";
}
}
color: {
switch (installAction.buttonState) {
case "installed":
return root.allowUninstall && installMouseArea.containsMouse ? Theme.error : Theme.surfaceText;
case "incompatible":
return Theme.warning;
default:
return Theme.surface;
}
}
}
MouseArea {
id: installMouseArea
readonly property bool canUninstall: installAction.buttonState === "installed" && root.allowUninstall
anchors.fill: parent
hoverEnabled: true
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
enabled: installAction.buttonState === "available" || canUninstall
onClicked: canUninstall ? root.uninstallRequested() : root.installRequested()
}
}
}
StyledText {
width: parent.width
text: I18n.tr("by %1", "author attribution").arg(root.plugin.author || I18n.tr("Unknown", "unknown author"))
font.pixelSize: Theme.fontSizeSmall
color: Theme.outline
elide: Text.ElideRight
maximumLineCount: 1
}
StyledText {
width: parent.width
text: root.plugin.description || ""
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
elide: Text.ElideRight
maximumLineCount: 2
}
}
}