lots of changes

This commit is contained in:
Zacharias-Brohn
2025-11-19 17:01:20 +01:00
parent ddae364005
commit 07e399f6d9
19 changed files with 611 additions and 53 deletions
+2
View File
@@ -14,6 +14,7 @@ Singleton {
property alias wallust: adapter.wallust
property alias workspaceWidget: adapter.workspaceWidget
property alias colors: adapter.colors
property alias gpuType: adapter.gpuType
FileView {
id: root
@@ -37,6 +38,7 @@ Singleton {
property bool wallust: false
property WorkspaceWidget workspaceWidget: WorkspaceWidget {}
property Colors colors: Colors {}
property string gpuType: ""
}
}
}
+52 -1
View File
@@ -5,8 +5,10 @@ import Quickshell
import Quickshell.Io
import Quickshell.Services.Notifications
import QtQuick
import ZShell
import qs.Modules
import qs.Helpers
import qs.Paths
Singleton {
id: root
@@ -81,7 +83,7 @@ Singleton {
FileView {
id: storage
path: NotifPath.notifPath
path: `${Paths.state}/notifs.json`
onLoaded: {
const data = JSON.parse(text());
@@ -90,6 +92,7 @@ Singleton {
root.list.sort((a, b) => b.time - a.time);
root.loaded = true;
}
onLoadFailed: err => {
if (err === FileViewError.FileNotFound) {
root.loaded = true;
@@ -144,6 +147,50 @@ Singleton {
}
}
readonly property LazyLoader dummyImageLoader: LazyLoader {
active: false
PanelWindow {
implicitWidth: 48
implicitHeight: 48
color: "transparent"
mask: Region {}
Image {
anchors.fill: parent
source: Qt.resolvedUrl(notif.image)
fillMode: Image.PreserveAspectCrop
cache: false
asynchronous: true
opacity: 0
onStatusChanged: {
if (status !== Image.Ready)
return;
const cacheKey = notif.appName + notif.summary + notif.id;
let h1 = 0xdeadbeef, h2 = 0x41c6ce57, ch;
for (let i = 0; i < cacheKey.length; i++) {
ch = cacheKey.charCodeAt(i);
h1 = Math.imul(h1 ^ ch, 2654435761);
h2 = Math.imul(h2 ^ ch, 1597334677);
}
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507);
h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);
h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);
const hash = (h2 >>> 0).toString(16).padStart(8, 0) + (h1 >>> 0).toString(16).padStart(8, 0);
const cache = `${Paths.notifimagecache}/${hash}.png`;
ZShellIo.saveItem(this, Qt.resolvedUrl(cache), () => {
notif.image = cache;
notif.dummyImageLoader.active = false;
});
}
}
}
}
readonly property Connections conn: Connections {
target: notif.notification
@@ -169,6 +216,8 @@ Singleton {
function onImageChanged(): void {
notif.image = notif.notification.image;
if (notif.notification?.image)
notif.dummyImageLoader.active = true;
}
function onExpireTimeoutChanged(): void {
@@ -225,6 +274,8 @@ Singleton {
appIcon = notification.appIcon;
appName = notification.appName;
image = notification.image;
if (notification?.image)
dummyImageLoader.active = true;
expireTimeout = notification.expireTimeout;
urgency = notification.urgency;
resident = notification.resident;
+28
View File
@@ -0,0 +1,28 @@
import ZShell.Internal
import Quickshell
import QtQuick
import qs.Paths
Image {
id: root
property alias path: manager.path
asynchronous: true
fillMode: Image.PreserveAspectCrop
Connections {
target: QsWindow.window
function onDevicePixelRatioChanged(): void {
manager.updateSource();
}
}
CachingImageManager {
id: manager
item: root
cacheDir: Qt.resolvedUrl(Paths.imagecache)
}
}
+31
View File
@@ -4,11 +4,33 @@ import Quickshell
import Quickshell.Io
import qs.Config
import qs.Modules
import qs.Helpers
import ZShell.Models
Searcher {
id: root
readonly property string currentNamePath: WallpaperPath.currentWallpaperPath
property bool showPreview: false
readonly property string current: showPreview ? previewPath : actualCurrent
property string previewPath
property string actualCurrent: WallpaperPath.currentWallpaperPath
function setWallpaper(path: string): void {
actualCurrent = path;
WallpaperPath.currentWallpaperPath = path;
}
function preview(path: string): void {
previewPath = path;
showPreview = true;
}
function stopPreview(): void {
showPreview = false;
}
list: wallpapers.entries
key: "relativePath"
useFuzzy: true
@@ -16,6 +38,15 @@ Searcher {
forward: false
})
FileView {
path: root.currentNamePath
watchChanges: true
onFileChanged: reload()
onLoaded: {
root.actualCurrent = this.text;
}
}
FileSystemModel {
id: wallpapers
+6
View File
@@ -0,0 +1,6 @@
import QtQuick
Text {
renderType: Text.NativeRendering
textFormat: Text.PlainText
}
+77
View File
@@ -0,0 +1,77 @@
pragma ComponentBehavior: Bound
import QtQuick
import qs.Helpers
Item {
id: root
property string source: SearchWallpapers.current
property Image current: one
anchors.fill: parent
onSourceChanged: {
if (!source) {
current = null;
} else if (current === one) {
two.update();
} else {
one.update();
}
}
Component.onCompleted: {
console.log(root.source)
if (source)
Qt.callLater(() => one.update());
}
Img {
id: one
}
Img {
id: two
}
component Img: CachingImage {
id: img
function update(): void {
if (path === root.source) {
root.current = this;
} else {
path = root.source;
}
}
anchors.fill: parent
opacity: 0
scale: SearchWallpapers.showPreview ? 1 : 0.8
asynchronous: true
onStatusChanged: {
if (status === Image.Ready) {
root.current = this;
}
}
states: State {
name: "visible"
when: root.current === img
PropertyChanges {
img.opacity: 1
img.scale: 1
}
}
transitions: Transition {
Anim {
target: img
properties: "opacity,scale"
}
}
}
}
+4 -1
View File
@@ -83,13 +83,16 @@ TextField {
Search.launch(appListLoader.item.currentItem.modelData);
launcherWindow.visible = false;
} else if ( wallpaperPickerLoader.active ) {
WallpaperPath.currentWallpaperPath = wallpaperPickerLoader.item.currentItem.modelData.path;
SearchWallpapers.setWallpaper(wallpaperPickerLoader.item.currentItem.modelData.path)
if ( Config.wallust ) {
Wallust.generateColors(WallpaperPath.currentWallpaperPath);
}
closeAnim.start();
}
event.accepted = true;
} else if ( event.key === Qt.Key_Escape ) {
if ( wallpaperPickerLoader.active )
SearchWallpapers.stopPreview();
closeAnim.start();
event.accepted = true;
}
+20 -3
View File
@@ -19,6 +19,7 @@ Repeater {
root.flagChanged();
}
}
Column {
id: groupColumn
required property string modelData
@@ -28,6 +29,7 @@ Repeater {
property bool shouldShow: false
property bool isExpanded: false
property bool collapseAnimRunning: false
function closeAll(): void {
for ( const n of NotifServer.notClosed.filter( n => n.appName === modelData ))
@@ -49,7 +51,7 @@ Repeater {
id: addTrans
SequentialAnimation {
PauseAnimation {
duration: ( addTrans.ViewTransition.index - addTrans.ViewTransition.targetIndexes[ 0 ]) * 50
duration: ( addTrans.ViewTransition.index - addTrans.ViewTransition.targetIndexes[ 0 ]) * 30
}
ParallelAnimation {
NumberAnimation {
@@ -77,6 +79,16 @@ Repeater {
}
}
Timer {
interval: addTrans.ViewTransition.targetIndexes.length * 30 + 100
running: groupColumn.isExpanded
repeat: false
onTriggered: {
groupColumn.shouldShow = true;
console.log("ran timer");
}
}
move: Transition {
id: moveTrans
NumberAnimation {
@@ -84,6 +96,11 @@ Repeater {
duration: 100;
easing.type: Easing.OutCubic
}
NumberAnimation {
properties: "opacity, scale";
to: 1.0;
}
}
RowLayout {
@@ -120,11 +137,11 @@ Repeater {
anchors.fill: parent
hoverEnabled: true
onClicked: {
groupColumn.shouldShow = false;
groupColumn.collapseAnimRunning = true;
}
}
}
}
NotifGroupRepeater { }
NotifGroupRepeater { id: groupRepeater }
}
}
+6
View File
@@ -259,6 +259,12 @@ Scope {
}
Component.onCompleted: currentIndex = SearchWallpapers.list.findIndex( w => w.path === WallpaperPath.currentWallpaperPath )
Component.onDestruction: SearchWallpapers.stopPreview()
onCurrentItemChanged: {
if ( currentItem )
SearchWallpapers.preview( currentItem.modelData.path );
}
cacheItemCount: 5
snapMode: PathView.SnapToItem
+25 -25
View File
@@ -4,21 +4,21 @@ import QtQuick
import QtQuick.Layouts
import qs.Config
import qs.Daemons
import qs.Helpers
Repeater {
id: groupListView
model: ScriptModel {
id: groupModel
values: groupColumn.isExpanded ? groupColumn.notifications : groupColumn.notifications.slice( 0, 1 )
values: groupColumn.isExpanded || groupColumn.shouldShow ? groupColumn.notifications : groupColumn.notifications.slice( 0, 1 )
}
Rectangle {
id: groupHeader
required property int index
required property NotifServer.Notif modelData
property alias notifHeight: groupHeader.height
property bool previewHidden: groupColumn.shouldShow && index > 0
property bool previewHidden: !groupColumn.shouldShow && index > 0
width: parent.width
height: contentColumn.height + 15
@@ -26,10 +26,12 @@ Repeater {
border.color: "#555555"
border.width: 1
radius: 8
opacity: previewHidden ? 0 : 1.0
opacity: previewHidden ? 0 : 1
scale: previewHidden ? 0.7 : 1.0
Component.onCompleted: modelData.lock(this);
Component.onCompleted: {
modelData.lock(this);
}
Component.onDestruction: modelData.unlock(this);
MouseArea {
@@ -40,7 +42,6 @@ Repeater {
groupHeader.modelData.actions[0].invoke();
}
} else {
groupColumn.shouldShow = true;
groupColumn.isExpanded = true;
}
}
@@ -48,7 +49,7 @@ Repeater {
ParallelAnimation {
id: collapseAnim
running: !groupColumn.shouldShow && index > 0
running: groupColumn.collapseAnimRunning
Anim {
target: groupHeader
@@ -66,6 +67,8 @@ Repeater {
}
onFinished: {
groupColumn.isExpanded = false;
groupColumn.shouldShow = false;
groupColumn.collapseAnimRunning = false;
}
}
@@ -107,13 +110,6 @@ Repeater {
}
}
// Behavior on height {
// Anim {
// duration: MaterialEasing.expressiveDefaultSpatialTime
// easing.bezierCurve: MaterialEasing.expressiveDefaultSpatial
// }
// }
Column {
id: contentColumn
anchors.top: parent.top
@@ -122,7 +118,6 @@ Repeater {
anchors.leftMargin: 10
anchors.rightMargin: 10
anchors.topMargin: 5
// width: parent.width - 20
spacing: 10
RowLayout {
id: infoRow
@@ -130,19 +125,19 @@ Repeater {
spacing: 10
IconImage {
source: groupHeader.modelData.image
source: groupHeader.modelData.image === "" ? Qt.resolvedUrl(groupHeader.modelData.appIcon) : Qt.resolvedUrl(groupHeader.modelData.image)
Layout.preferredWidth: 48
Layout.preferredHeight: 48
Layout.alignment: Qt.AlignTop | Qt.AlignLeft
Layout.topMargin: 5
visible: groupHeader.modelData.image !== ""
visible: source !== ""
}
ColumnLayout {
Layout.fillWidth: true
Layout.fillHeight: true
Text {
TextRender {
text: groupHeader.modelData.summary
color: "white"
font.bold: true
@@ -152,21 +147,26 @@ Repeater {
Layout.alignment: Qt.AlignTop
}
Text {
TextRender {
text: groupHeader.modelData.body
font.pointSize: 12
color: "#dddddd"
font.pointSize: 12
elide: Text.ElideRight
lineHeightMode: Text.FixedHeight
lineHeight: 20
textFormat: Text.MarkdownText
wrapMode: Text.WordWrap
maximumLineCount: 5
linkColor: Config.accentColor.accents.primaryAlt
onLinkActivated: link => {
Quickshell.execDetached(["app2unit", "-O", "--", link]);
}
Layout.fillWidth: true
Layout.fillHeight: true
}
}
Text {
TextRender {
text: groupHeader.modelData.timeStr
font.pointSize: 10
color: "#666666"
@@ -191,7 +191,7 @@ Repeater {
required property var modelData
color: buttonArea.containsMouse ? "#15FFFFFF" : "#09FFFFFF"
radius: 4
Text {
TextRender {
anchors.centerIn: parent
text: actionButton.modelData.text
color: "white"
@@ -220,7 +220,7 @@ Repeater {
color: closeArea.containsMouse ? "#FF6077" : "transparent"
radius: 9
Text {
TextRender {
anchors.centerIn: parent
text: "✕"
color: closeArea.containsMouse ? "white" : "#888888"
+1 -1
View File
@@ -71,7 +71,7 @@ PanelWindow {
id: showAnimation
target: backgroundRect
property: "x"
to: root.bar.screen.width - backgroundRect.implicitWidth - 10
to: Math.round(root.bar.screen.width - backgroundRect.implicitWidth - 10)
from: root.bar.screen.width
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
+18 -2
View File
@@ -4,8 +4,10 @@ pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Io
import qs.Config
Singleton {
id: root
property double memoryTotal: 1
property double memoryFree: 1
property double memoryUsed: memoryTotal - memoryFree
@@ -19,6 +21,8 @@ Singleton {
property double gpuUsage: 0
property double gpuMemUsage: 0
property double totalMem: 0
readonly property string gpuType: Config.gpuType.toUpperCase() || autoGpuType
property string autoGpuType: "NONE"
Timer {
interval: 1
@@ -52,7 +56,9 @@ Singleton {
previousCpuStats = { total, idle }
}
processGpu.running = true
if ( root.gpuType === "NVIDIA" ) {
processGpu.running = true
}
interval = 1000
}
@@ -61,10 +67,20 @@ Singleton {
FileView { id: fileMeminfo; path: "/proc/meminfo" }
FileView { id: fileStat; path: "/proc/stat" }
Process {
id: gpuTypeCheck
running: !Config.gpuType
command: ["sh", "-c", "if command -v nvidia-smi &>/dev/null && nvidia-smi -L &>/dev/null; then echo NVIDIA; elif ls /sys/class/drm/card*/device/gpu_busy_percent 2>/dev/null | grep -q .; then echo GENERIC; else echo NONE; fi"]
stdout: StdioCollector {
onStreamFinished: root.autoGpuType = text.trim()
}
}
Process {
id: oneshotMem
command: ["nvidia-smi", "--query-gpu=memory.total", "--format=csv,noheader,nounits"]
running: true
running: root.gpuType === "NVIDIA" && totalMem === 0
stdout: StdioCollector {
onStreamFinished: {
totalMem = Number(this.text.trim())
+8 -2
View File
@@ -146,11 +146,11 @@ PanelWindow {
RowLayout {
spacing: 12
IconImage {
source: rootItem.modelData.image
source: rootItem.modelData.image === "" ? Qt.resolvedUrl(rootItem.modelData.appIcon) : Qt.resolvedUrl(rootItem.modelData.image)
Layout.preferredWidth: 48
Layout.preferredHeight: 48
Layout.alignment: Qt.AlignHCenter | Qt.AlignLeft
visible: rootItem.modelData.image !== ""
// visible: rootItem.modelData.image !== ""
}
ColumnLayout {
@@ -185,10 +185,16 @@ PanelWindow {
text: rootItem.modelData.body
color: "#dddddd"
font.pointSize: 14
textFormat: Text.MarkdownText
elide: Text.ElideRight
wrapMode: Text.WordWrap
maximumLineCount: 4
width: parent.width
linkColor: Config.accentColor.accents.primaryAlt
onLinkActivated: link => {
Quickshell.execDetached(["app2unit", "-O", "--", link]);
}
}
}
+1 -1
View File
@@ -15,7 +15,7 @@ Singleton {
repeat: true
onTriggered: {
updatesProc.running = true
interval = 60000
interval = 5000
}
}
+37
View File
@@ -0,0 +1,37 @@
pragma Singleton
import ZShell
import Quickshell
import qs.Config
Singleton {
id: root
readonly property string home: Quickshell.env("HOME")
readonly property string pictures: Quickshell.env("XDG_PICTURES_DIR") || `${home}/Pictures`
readonly property string videos: Quickshell.env("XDG_VIDEOS_DIR") || `${home}/Videos`
readonly property string data: `${Quickshell.env("XDG_DATA_HOME") || `${home}/.local/share`}/zshell`
readonly property string state: `${Quickshell.env("XDG_STATE_HOME") || `${home}/.local/state`}/zshell`
readonly property string cache: `${Quickshell.env("XDG_CACHE_HOME") || `${home}/.cache`}/zshell`
readonly property string config: `${Quickshell.env("XDG_CONFIG_HOME") || `${home}/.config`}/zshell`
readonly property string imagecache: `${cache}/imagecache`
readonly property string notifimagecache: `${imagecache}/notifs`
readonly property string wallsdir: Quickshell.env("ZSHELL_WALLPAPERS_DIR") || absolutePath(Config.wallpaperPath)
readonly property string recsdir: Quickshell.env("ZSHELL_RECORDINGS_DIR") || `${videos}/Recordings`
readonly property string libdir: Quickshell.env("ZSHELL_LIB_DIR") || "/usr/lib/zshell"
function toLocalFile(path: url): string {
path = Qt.resolvedUrl(path);
return path.toString() ? ZShellIo.toLocalFile(path) : "";
}
function absolutePath(path: string): string {
return toLocalFile(path.replace("~", home));
}
function shortenHome(path: string): string {
return path.replace(home, "~");
}
}
+6
View File
@@ -3,4 +3,10 @@ qml_module(ZShell-internal
SOURCES
hyprextras.hpp hyprextras.cpp
hyprdevices.hpp hyprdevices.cpp
cachingimagemanager.hpp cachingimagemanager.cpp
LIBRARIES
Qt::Gui
Qt::Quick
Qt::Concurrent
Qt::Core
)
@@ -0,0 +1,223 @@
#include "cachingimagemanager.hpp"
#include <QtQuick/qquickwindow.h>
#include <qcryptographichash.h>
#include <qdir.h>
#include <qfileinfo.h>
#include <qfuturewatcher.h>
#include <qimagereader.h>
#include <qpainter.h>
#include <qtconcurrentrun.h>
namespace ZShell::internal {
qreal CachingImageManager::effectiveScale() const {
if (m_item && m_item->window()) {
return m_item->window()->devicePixelRatio();
}
return 1.0;
}
QSize CachingImageManager::effectiveSize() const {
if (!m_item) {
return QSize();
}
const qreal scale = effectiveScale();
const QSize size = QSizeF(m_item->width() * scale, m_item->height() * scale).toSize();
m_item->setProperty("sourceSize", size);
return size;
}
QQuickItem* CachingImageManager::item() const {
return m_item;
}
void CachingImageManager::setItem(QQuickItem* item) {
if (m_item == item) {
return;
}
if (m_widthConn) {
disconnect(m_widthConn);
}
if (m_heightConn) {
disconnect(m_heightConn);
}
m_item = item;
emit itemChanged();
if (item) {
m_widthConn = connect(item, &QQuickItem::widthChanged, this, [this]() {
updateSource();
});
m_heightConn = connect(item, &QQuickItem::heightChanged, this, [this]() {
updateSource();
});
updateSource();
}
}
QUrl CachingImageManager::cacheDir() const {
return m_cacheDir;
}
void CachingImageManager::setCacheDir(const QUrl& cacheDir) {
if (m_cacheDir == cacheDir) {
return;
}
m_cacheDir = cacheDir;
if (!m_cacheDir.path().endsWith("/")) {
m_cacheDir.setPath(m_cacheDir.path() + "/");
}
emit cacheDirChanged();
}
QString CachingImageManager::path() const {
return m_path;
}
void CachingImageManager::setPath(const QString& path) {
if (m_path == path) {
return;
}
m_path = path;
emit pathChanged();
if (!path.isEmpty()) {
updateSource(path);
}
}
void CachingImageManager::updateSource() {
updateSource(m_path);
}
void CachingImageManager::updateSource(const QString& path) {
if (path.isEmpty() || path == m_shaPath) {
// Path is empty or already calculating sha for path
return;
}
m_shaPath = path;
const auto future = QtConcurrent::run(&CachingImageManager::sha256sum, path);
const auto watcher = new QFutureWatcher<QString>(this);
connect(watcher, &QFutureWatcher<QString>::finished, this, [watcher, path, this]() {
if (m_path != path) {
// Object is destroyed or path has changed, ignore
watcher->deleteLater();
return;
}
const QSize size = effectiveSize();
if (!m_item || !size.width() || !size.height()) {
watcher->deleteLater();
return;
}
const QString fillMode = m_item->property("fillMode").toString();
// clang-format off
const QString filename = QString("%1@%2x%3-%4.png")
.arg(watcher->result()).arg(size.width()).arg(size.height())
.arg(fillMode == "PreserveAspectCrop" ? "crop" : fillMode == "PreserveAspectFit" ? "fit" : "stretch");
// clang-format on
const QUrl cache = m_cacheDir.resolved(QUrl(filename));
if (m_cachePath == cache) {
watcher->deleteLater();
return;
}
m_cachePath = cache;
emit cachePathChanged();
if (!cache.isLocalFile()) {
qWarning() << "CachingImageManager::updateSource: cachePath" << cache << "is not a local file";
watcher->deleteLater();
return;
}
const QImageReader reader(cache.toLocalFile());
if (reader.canRead()) {
m_item->setProperty("source", cache);
} else {
m_item->setProperty("source", QUrl::fromLocalFile(path));
createCache(path, cache.toLocalFile(), fillMode, size);
}
// Clear current running sha if same
if (m_shaPath == path) {
m_shaPath = QString();
}
watcher->deleteLater();
});
watcher->setFuture(future);
}
QUrl CachingImageManager::cachePath() const {
return m_cachePath;
}
void CachingImageManager::createCache(
const QString& path, const QString& cache, const QString& fillMode, const QSize& size) const {
QThreadPool::globalInstance()->start([path, cache, fillMode, size] {
QImage image(path);
if (image.isNull()) {
qWarning() << "CachingImageManager::createCache: failed to read" << path;
return;
}
image.convertTo(QImage::Format_ARGB32);
if (fillMode == "PreserveAspectCrop") {
image = image.scaled(size, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
} else if (fillMode == "PreserveAspectFit") {
image = image.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
} else {
image = image.scaled(size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
}
if (fillMode == "PreserveAspectCrop" || fillMode == "PreserveAspectFit") {
QImage canvas(size, QImage::Format_ARGB32);
canvas.fill(Qt::transparent);
QPainter painter(&canvas);
painter.drawImage((size.width() - image.width()) / 2, (size.height() - image.height()) / 2, image);
painter.end();
image = canvas;
}
const QString parent = QFileInfo(cache).absolutePath();
if (!QDir().mkpath(parent) || !image.save(cache)) {
qWarning() << "CachingImageManager::createCache: failed to save to" << cache;
}
});
}
QString CachingImageManager::sha256sum(const QString& path) {
QFile file(path);
if (!file.open(QIODevice::ReadOnly)) {
qWarning() << "CachingImageManager::sha256sum: failed to open" << path;
return "";
}
QCryptographicHash hash(QCryptographicHash::Sha256);
hash.addData(&file);
file.close();
return hash.result().toHex();
}
} // namespace ZShell::internal
@@ -0,0 +1,65 @@
#pragma once
#include <QtQuick/qquickitem.h>
#include <qobject.h>
#include <qqmlintegration.h>
namespace ZShell::internal {
class CachingImageManager : public QObject {
Q_OBJECT
QML_ELEMENT
Q_PROPERTY(QQuickItem* item READ item WRITE setItem NOTIFY itemChanged REQUIRED)
Q_PROPERTY(QUrl cacheDir READ cacheDir WRITE setCacheDir NOTIFY cacheDirChanged REQUIRED)
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged)
Q_PROPERTY(QUrl cachePath READ cachePath NOTIFY cachePathChanged)
public:
explicit CachingImageManager(QObject* parent = nullptr)
: QObject(parent)
, m_item(nullptr) {}
[[nodiscard]] QQuickItem* item() const;
void setItem(QQuickItem* item);
[[nodiscard]] QUrl cacheDir() const;
void setCacheDir(const QUrl& cacheDir);
[[nodiscard]] QString path() const;
void setPath(const QString& path);
[[nodiscard]] QUrl cachePath() const;
Q_INVOKABLE void updateSource();
Q_INVOKABLE void updateSource(const QString& path);
signals:
void itemChanged();
void cacheDirChanged();
void pathChanged();
void cachePathChanged();
void usingCacheChanged();
private:
QString m_shaPath;
QQuickItem* m_item;
QUrl m_cacheDir;
QString m_path;
QUrl m_cachePath;
QMetaObject::Connection m_widthConn;
QMetaObject::Connection m_heightConn;
[[nodiscard]] qreal effectiveScale() const;
[[nodiscard]] QSize effectiveSize() const;
void createCache(const QString& path, const QString& cache, const QString& fillMode, const QSize& size) const;
[[nodiscard]] static QString sha256sum(const QString& path);
};
} // namespace ZShell::internal
+1 -17
View File
@@ -21,23 +21,7 @@ Scope {
right: true
bottom: true
}
Image {
id: wallpaperImage
anchors.fill: parent
source: WallpaperPath.currentWallpaperPath
fillMode: Image.PreserveAspectCrop
smooth: true
asynchronous: true
retainWhileLoading: true
// Behavior on source {
// Anim {
// properties: "opacity"
// duration: 500
// }
// }
}
Background {}
}
}
}