pragma ComponentBehavior: Bound import QtQuick import QtQuick.Layouts import Quickshell import qs.Components import qs.Config Item { id: root property alias imgHeight: imgWrapper.implicitHeight property alias radius: imgWrapper.radius property alias source: img.source signal clicked Layout.fillWidth: true implicitHeight: layout.implicitHeight ColumnLayout { id: layout anchors.fill: parent spacing: Appearance.spacing.small CustomClippingRect { id: imgWrapper Layout.fillWidth: true color: DynamicColors.tPalette.m3surfaceContainer implicitHeight: width radius: Appearance.rounding.large Loader { active: opacity > 0 anchors.centerIn: parent opacity: img.status === Image.Ready ? 0 : 1 Behavior on opacity { Anim { } } sourceComponent: CustomRect { color: DynamicColors.palette.m3primaryContainer implicitHeight: loadingIndicator.implicitSize + Appearance.padding.large * 2 implicitWidth: loadingIndicator.implicitSize + Appearance.padding.large * 2 radius: Appearance.rounding.full LoadingIndicator { id: loadingIndicator anchors.centerIn: parent containsIcon: true implicitSize: Math.min(imgWrapper.width, imgWrapper.height) * 0.3 } } } Image { id: img anchors.fill: parent asynchronous: true fillMode: Image.PreserveAspectCrop opacity: status === Image.Ready ? 1 : 0 retainWhileLoading: true sourceSize: { const dpr = (QsWindow.window as QsWindow)?.devicePixelRatio ?? 1; return Qt.size(width * dpr, height * dpr); } Behavior on opacity { Anim { type: Anim.SlowEffects } } } } } StateLayer { anchors.bottomMargin: layout.implicitHeight - imgWrapper.implicitHeight onClicked: root.clicked() } }