1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-04-04 12:52:06 -04:00
Files
DankMaterialShell/quickshell/Widgets/DankCircularImage.qml
purian23 788c1fa9e6 feat: Implement Material Design 3 elevation and shadow effects
- Added global toggle for enabling M3 elevation & shadow effects
- Refactored various components to sync updated specs
2026-02-26 01:09:21 -05:00

172 lines
5.0 KiB
QML

import QtQuick
import QtQuick.Window
import QtQuick.Effects
import qs.Common
import qs.Widgets
Rectangle {
id: root
property string imageSource: ""
property string fallbackIcon: "notifications"
property string fallbackText: ""
property bool hasImage: imageSource !== ""
readonly property bool shouldProbe: imageSource !== "" && !imageSource.startsWith("image://")
// Probe with AnimatedImage first; once loaded, check frameCount to decide.
readonly property bool isAnimated: shouldProbe && probe.status === Image.Ready && probe.frameCount > 1
readonly property var activeImage: isAnimated ? probe : staticImage
property int imageStatus: activeImage.status
signal imageSaved(string filePath)
property string _pendingSavePath: ""
property var _attachedWindow: root.Window.window
on_AttachedWindowChanged: {
if (_attachedWindow && _pendingSavePath !== "") {
Qt.callLater(function () {
if (root._pendingSavePath !== "") {
let path = root._pendingSavePath;
root._pendingSavePath = "";
root.saveImageToFile(path);
}
});
}
}
function saveImageToFile(filePath) {
if (activeImage.status !== Image.Ready)
return false;
if (!activeImage.Window.window) {
_pendingSavePath = filePath;
return true;
}
activeImage.grabToImage(function (result) {
if (result && result.saveToFile(filePath)) {
root.imageSaved(filePath);
}
});
return true;
}
radius: width / 2
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1)
border.color: "transparent"
border.width: 0
// Probe: loads as AnimatedImage to detect frame count.
AnimatedImage {
id: probe
anchors.fill: parent
anchors.margins: 2
asynchronous: true
fillMode: Image.PreserveAspectCrop
smooth: true
mipmap: true
cache: true
visible: false
source: root.shouldProbe ? root.imageSource : ""
}
// Static fallback: used once probe confirms the image is not animated.
Image {
id: staticImage
anchors.fill: parent
anchors.margins: 2
asynchronous: true
fillMode: Image.PreserveAspectCrop
smooth: true
mipmap: true
cache: true
visible: false
sourceSize.width: Math.max(width * 2, 128)
sourceSize.height: Math.max(height * 2, 128)
source: !root.shouldProbe ? root.imageSource : ""
}
// Once the probe loads, if not animated, hand off to Image and unload probe.
Connections {
target: probe
function onStatusChanged() {
if (!root.shouldProbe)
return;
switch (probe.status) {
case Image.Ready:
if (probe.frameCount <= 1) {
staticImage.source = root.imageSource;
probe.source = "";
}
break;
case Image.Error:
staticImage.source = root.imageSource;
probe.source = "";
break;
}
}
}
// If imageSource changes, reset: re-probe with AnimatedImage.
onImageSourceChanged: {
if (root.shouldProbe) {
staticImage.source = "";
probe.source = root.imageSource;
} else {
probe.source = "";
staticImage.source = root.imageSource;
}
}
MultiEffect {
anchors.fill: parent
anchors.margins: 2
source: root.activeImage
maskEnabled: true
maskSource: circularMask
visible: root.activeImage.status === Image.Ready && root.imageSource !== ""
maskThresholdMin: 0.5
maskSpreadAtMin: 1
}
Item {
id: circularMask
anchors.centerIn: parent
width: parent.width - 4
height: parent.height - 4
layer.enabled: true
layer.smooth: true
visible: false
Rectangle {
anchors.fill: parent
radius: width / 2
color: "black"
antialiasing: true
}
}
AppIconRenderer {
anchors.centerIn: parent
width: Math.round(parent.width * 0.75)
height: width
visible: (root.activeImage.status !== Image.Ready || root.imageSource === "") && root.fallbackIcon !== ""
iconValue: root.fallbackIcon
iconSize: width
iconColor: Theme.surfaceVariantText
materialIconSizeAdjustment: 0
fallbackText: root.fallbackText
fallbackBackgroundColor: "transparent"
fallbackTextColor: Theme.surfaceVariantText
}
StyledText {
anchors.centerIn: parent
visible: root.imageSource === "" && root.fallbackIcon === "" && root.fallbackText !== ""
text: root.fallbackText
font.pixelSize: Math.max(12, parent.width * 0.5)
font.weight: Font.Bold
color: Theme.surfaceVariantText
}
}