From 445b10d6f04f732e80c2e6ac51115fed173e6da1 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 18 Aug 2025 20:41:44 +0700 Subject: [PATCH] put wallpaper picker in loader and make it use real thumbnails --- .../ii/modules/common/Directories.qml | 1 + .../wallpaperOverview/WallpaperOverview.qml | 658 +++++++++--------- 2 files changed, 334 insertions(+), 325 deletions(-) diff --git a/.config/quickshell/ii/modules/common/Directories.qml b/.config/quickshell/ii/modules/common/Directories.qml index a1748ece..2b235c69 100644 --- a/.config/quickshell/ii/modules/common/Directories.qml +++ b/.config/quickshell/ii/modules/common/Directories.qml @@ -11,6 +11,7 @@ Singleton { readonly property string config: StandardPaths.standardLocations(StandardPaths.ConfigLocation)[0] readonly property string state: StandardPaths.standardLocations(StandardPaths.StateLocation)[0] readonly property string cache: StandardPaths.standardLocations(StandardPaths.CacheLocation)[0] + readonly property string genericCache: StandardPaths.standardLocations(StandardPaths.GenericCacheLocation)[0] readonly property string pictures: StandardPaths.standardLocations(StandardPaths.PicturesLocation)[0] readonly property string downloads: StandardPaths.standardLocations(StandardPaths.DownloadLocation)[0] diff --git a/.config/quickshell/ii/modules/wallpaperOverview/WallpaperOverview.qml b/.config/quickshell/ii/modules/wallpaperOverview/WallpaperOverview.qml index 70d065db..667f2015 100644 --- a/.config/quickshell/ii/modules/wallpaperOverview/WallpaperOverview.qml +++ b/.config/quickshell/ii/modules/wallpaperOverview/WallpaperOverview.qml @@ -14,380 +14,388 @@ import Quickshell.Hyprland Scope { id: scope - PanelWindow { - id: root - readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.screen) - property bool monitorIsFocused: (Hyprland.focusedMonitor?.id == monitor?.id) - visible: GlobalStates.wallpaperOverviewOpen - property var filteredWallpapers: Wallpapers.wallpapers + Loader { + active: GlobalStates.wallpaperOverviewOpen + sourceComponent: PanelWindow { + id: root + readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.screen) + property bool monitorIsFocused: (Hyprland.focusedMonitor?.id == monitor?.id) + property var filteredWallpapers: Wallpapers.wallpapers - WlrLayershell.namespace: "quickshell:wallpaper-overview" - WlrLayershell.layer: WlrLayer.Overlay - WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand - color: "transparent" + WlrLayershell.namespace: "quickshell:wallpaper-overview" + WlrLayershell.layer: WlrLayer.Overlay + WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand + color: "transparent" - anchors { - top: true - bottom: true - left: true - right: true - } + anchors { + top: true + bottom: true + left: true + right: true + } - ColumnLayout { - id: layout - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: parent.top - spacing: 8 + ColumnLayout { + id: layout + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + spacing: 8 - TextField { - id: filterField - Layout.preferredWidth: bg.implicitWidth - Layout.alignment: Qt.AlignHcenter - implicitHeight: 40 - padding: 10 - placeholderText: "Search wallpapers..." - placeholderTextColor: Appearance.colors.colSubtext - color: Appearance.colors.colPrimary - background: Rectangle { - color: Appearance.colors.colLayer0 - border.color: Appearance.colors.colLayer0Border - border.width: 1 - radius: Appearance.rounding.small - } - font.family: Appearance.font.family.main - font.pixelSize: Appearance.font.pixelSize.normal + TextField { + id: filterField + Layout.preferredWidth: bg.implicitWidth + Layout.alignment: Qt.AlignHcenter + implicitHeight: 40 + padding: 10 + placeholderText: "Search wallpapers..." + placeholderTextColor: Appearance.colors.colSubtext + color: Appearance.colors.colPrimary + background: Rectangle { + color: Appearance.colors.colLayer0 + border.color: Appearance.colors.colLayer0Border + border.width: 1 + radius: Appearance.rounding.small + } + font.family: Appearance.font.family.main + font.pixelSize: Appearance.font.pixelSize.normal - onTextChanged: { - let newModel = []; - if (text.length > 0) { - for (let i = 0; i < Wallpapers.wallpapers.length; ++i) { - let wallpaperPath = Wallpapers.wallpapers[i]; - if (wallpaperPath.toLowerCase().includes(text.toLowerCase())) { - newModel.push(wallpaperPath); + onTextChanged: { + let newModel = []; + if (text.length > 0) { + for (let i = 0; i < Wallpapers.wallpapers.length; ++i) { + let wallpaperPath = Wallpapers.wallpapers[i]; + if (wallpaperPath.toLowerCase().includes(text.toLowerCase())) { + newModel.push(wallpaperPath); + } + } + root.filteredWallpapers = newModel; + } else { + root.filteredWallpapers = Wallpapers.wallpapers; + } + } + + Keys.onPressed: event => { + if (text.length === 0) { + if (event.key === Qt.Key_Down || event.key === Qt.Key_Left || event.key === Qt.Key_Right) { + bg.forceActiveFocus(); + if (event.key === Qt.Key_Down) + grid.moveSelection(grid.columns); + else if (event.key === Qt.Key_Left) + grid.moveSelection(-1); + else if (event.key === Qt.Key_Right) + grid.moveSelection(1); + event.accepted = true; + } + } else { + if (event.key === Qt.Key_Down) { + grid.moveSelection(grid.columns); + event.accepted = true; + bg.forceActiveFocus(); } } - root.filteredWallpapers = newModel; - } else { - root.filteredWallpapers = Wallpapers.wallpapers; + if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { + grid.activateCurrent(); + event.accepted = true; + } else if (event.key === Qt.Key_Escape) { + if (filterField.text.length > 0) { + filterField.text = ""; + } else { + GlobalStates.wallpaperOverviewOpen = false; + } + event.accepted = true; + } } } - Keys.onPressed: event => { - if (text.length === 0) { - if (event.key === Qt.Key_Down || event.key === Qt.Key_Left || event.key === Qt.Key_Right) { - bg.forceActiveFocus(); - if (event.key === Qt.Key_Down) - grid.moveSelection(grid.columns); - else if (event.key === Qt.Key_Left) - grid.moveSelection(-1); - else if (event.key === Qt.Key_Right) - grid.moveSelection(1); - event.accepted = true; + Rectangle { + id: bg + focus: true + color: Appearance.colors.colLayer0 + border.width: 1 + border.color: Appearance.colors.colLayer0Border + radius: Appearance.rounding.screenRounding + + property int calculatedRows: Math.ceil(grid.count / grid.columns) + + implicitWidth: { + if (root.filteredWallpapers.length === 0) { + return 300; + } else if (root.filteredWallpapers.length < grid.columns) { + return root.filteredWallpapers.length * grid.cellWidth + 16; + } else { + return Math.min(root.width * 0.7, 900); } - } else { - if (event.key === Qt.Key_Down) { + } + + implicitHeight: { + if (root.filteredWallpapers.length === 0) { + return 100; + } else { + return Math.min(root.height * 0.6, Math.min(calculatedRows, 3) * grid.cellHeight + 16); + } + } + + Behavior on implicitWidth { + animation: Appearance.animation.elementMove.numberAnimation.createObject(this) + } + + Behavior on implicitHeight { + animation: Appearance.animation.elementMove.numberAnimation.createObject(this) + } + + Keys.onPressed: event => { + if (event.key === Qt.Key_Escape) { + GlobalStates.wallpaperOverviewOpen = false; + event.accepted = true; + } else if (event.key === Qt.Key_Left) { + grid.moveSelection(-1); + event.accepted = true; + } else if (event.key === Qt.Key_Right) { + grid.moveSelection(1); + event.accepted = true; + } else if (event.key === Qt.Key_Up) { + if (grid.currentIndex < grid.columns) { + filterField.forceActiveFocus(); + } else { + grid.moveSelection(-grid.columns); + } + event.accepted = true; + } else if (event.key === Qt.Key_Down) { grid.moveSelection(grid.columns); event.accepted = true; - bg.forceActiveFocus(); - } - } - if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { - grid.activateCurrent(); - event.accepted = true; - } else if (event.key === Qt.Key_Escape) { - if (filterField.text.length > 0) { - filterField.text = ""; - } else { - GlobalStates.wallpaperOverviewOpen = false; - } - event.accepted = true; - } - } - } - - Rectangle { - id: bg - focus: true - color: Appearance.colors.colLayer0 - border.width: 1 - border.color: Appearance.colors.colLayer0Border - radius: Appearance.rounding.screenRounding - - property int calculatedRows: Math.ceil(grid.count / grid.columns) - - implicitWidth: { - if (root.filteredWallpapers.length === 0) { - return 300; - } else if (root.filteredWallpapers.length < grid.columns) { - return root.filteredWallpapers.length * grid.cellWidth + 16; - } else { - return Math.min(root.width * 0.7, 900); - } - } - - implicitHeight: { - if (root.filteredWallpapers.length === 0) { - return 100; - } else { - return Math.min(root.height * 0.6, Math.min(calculatedRows, 3) * grid.cellHeight + 16); - } - } - - Behavior on implicitWidth { - animation: Appearance.animation.elementMove.numberAnimation.createObject(this) - } - - Behavior on implicitHeight { - animation: Appearance.animation.elementMove.numberAnimation.createObject(this) - } - - Keys.onPressed: event => { - if (event.key === Qt.Key_Escape) { - GlobalStates.wallpaperOverviewOpen = false; - event.accepted = true; - } else if (event.key === Qt.Key_Left) { - grid.moveSelection(-1); - event.accepted = true; - } else if (event.key === Qt.Key_Right) { - grid.moveSelection(1); - event.accepted = true; - } else if (event.key === Qt.Key_Up) { - if (grid.currentIndex < grid.columns) { + } else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { + grid.activateCurrent(); + event.accepted = true; + } else if (event.key === Qt.Key_Backspace) { + if (filterField.text.length > 0) { + filterField.text = filterField.text.substring(0, filterField.text.length - 1); + } filterField.forceActiveFocus(); + event.accepted = true; } else { - grid.moveSelection(-grid.columns); + filterField.forceActiveFocus(); + if (event.text.length > 0) { + filterField.text += event.text; + filterField.cursorPosition = filterField.text.length; + } + event.accepted = true; } - event.accepted = true; - } else if (event.key === Qt.Key_Down) { - grid.moveSelection(grid.columns); - event.accepted = true; - } else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { - grid.activateCurrent(); - event.accepted = true; - } else if (event.key === Qt.Key_Backspace) { - if (filterField.text.length > 0) { - filterField.text = filterField.text.substring(0, filterField.text.length - 1); - } - filterField.forceActiveFocus(); - event.accepted = true; - } else { - filterField.forceActiveFocus(); - if (event.text.length > 0) { - filterField.text += event.text; - filterField.cursorPosition = filterField.text.length; - } - event.accepted = true; } - } - ColumnLayout { - anchors.fill: parent - anchors.margins: 8 - spacing: 8 + ColumnLayout { + anchors.fill: parent + anchors.margins: 8 + spacing: 8 - GridView { - id: grid - visible: root.filteredWallpapers.length > 0 + GridView { + id: grid + visible: root.filteredWallpapers.length > 0 - readonly property int columns: 4 - property int currentIndex: 0 - readonly property int rows: Math.max(1, Math.ceil(count / columns)) + readonly property int columns: 4 + property int currentIndex: 0 + readonly property int rows: Math.max(1, Math.ceil(count / columns)) - Layout.preferredWidth: columns * cellWidth - Layout.alignment: Qt.AlignHcenter - Layout.fillHeight: true - cellWidth: 220 - cellHeight: 140 - clip: true - interactive: true - keyNavigationWraps: true - boundsBehavior: Flickable.StopAtBounds + Layout.preferredWidth: columns * cellWidth + Layout.alignment: Qt.AlignHCenter + Layout.fillHeight: true + cellWidth: 128 + cellHeight: 72 + clip: true + interactive: true + keyNavigationWraps: true + boundsBehavior: Flickable.StopAtBounds - cacheBuffer: cellHeight * 2 - ScrollBar.horizontal: ScrollBar { - policy: ScrollBar.AsNeeded - visible: false - } - ScrollBar.vertical: ScrollBar { - policy: ScrollBar.AsNeeded - visible: false - } + cacheBuffer: cellHeight * 2 + ScrollBar.horizontal: ScrollBar { + policy: ScrollBar.AsNeeded + visible: false + } + ScrollBar.vertical: ScrollBar { + policy: ScrollBar.AsNeeded + visible: false + } - model: root.filteredWallpapers - onModelChanged: currentIndex = 0 + model: root.filteredWallpapers + onModelChanged: currentIndex = 0 - function moveSelection(delta) { - for (let i = 0; i < count; i++) { - const item = itemAtIndex(i); - if (item) { - item.isHovered = false; + function moveSelection(delta) { + for (let i = 0; i < count; i++) { + const item = itemAtIndex(i); + if (item) { + item.isHovered = false; + } } + currentIndex = Math.max(0, Math.min(count - 1, currentIndex + delta)); + positionViewAtIndex(currentIndex, GridView.Contain); } - currentIndex = Math.max(0, Math.min(count - 1, currentIndex + delta)); - positionViewAtIndex(currentIndex, GridView.Contain); - } - function activateCurrent() { - const path = model[currentIndex]; - if (!path) - return; - GlobalStates.wallpaperOverviewOpen = false; - filterField.text = ""; - Wallpapers.apply(path); - } - - delegate: Item { - width: grid.cellWidth - height: grid.cellHeight - property bool isHovered: false - - Behavior on width { - animation: Appearance.animation.elementMove.numberAnimation.createObject(this) + function activateCurrent() { + const path = model[currentIndex]; + if (!path) + return; + GlobalStates.wallpaperOverviewOpen = false; + filterField.text = ""; + Wallpapers.apply(path); } - Behavior on height { - animation: Appearance.animation.elementMove.numberAnimation.createObject(this) - } + delegate: Item { + width: grid.cellWidth + height: grid.cellHeight + property bool isHovered: false - Rectangle { - anchors.fill: parent - radius: Appearance.rounding.windowRounding - color: Appearance.colors.colLayer1 - border.width: (index === grid.currentIndex || parent.isHovered) ? 3 : 0 - border.color: Appearance.colors.colSecondary - } + Behavior on width { + animation: Appearance.animation.elementMove.numberAnimation.createObject(this) + } - Rectangle { - anchors.fill: parent - anchors.margins: 8 - color: Appearance.colors.colLayer2 - radius: Appearance.rounding.elementRounding + Behavior on height { + animation: Appearance.animation.elementMove.numberAnimation.createObject(this) + } Rectangle { - anchors.centerIn: parent - width: Math.min(parent.width * 0.4, 32) - height: Math.min(parent.height * 0.4, 32) - radius: Appearance.rounding.elementRounding - color: Appearance.colors.colLayer3 - visible: thumbnailImage.status !== Image.Ready - - opacity: 0.3 - SequentialAnimation on opacity { - running: parent.visible - loops: Animation.Infinite - NumberAnimation { - to: 1.0 - duration: 800 - easing.type: Easing.InOutSine - } - NumberAnimation { - to: 0.3 - duration: 800 - easing.type: Easing.InOutSine - } - } - } - - Image { - id: thumbnailImage anchors.fill: parent - source: `file://${modelData}` - fillMode: Image.PreserveAspectCrop - asynchronous: true - cache: false - smooth: true - - sourceSize.width: Math.min(128, grid.cellWidth - 16) - sourceSize.height: Math.min(96, grid.cellHeight - 16) - - mipmap: false - - opacity: status === Image.Ready ? 1 : 0 - Behavior on opacity { - animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) - } + radius: Appearance.rounding.windowRounding + color: Appearance.colors.colLayer1 + border.width: (index === grid.currentIndex || parent.isHovered) ? 3 : 0 + border.color: Appearance.colors.colSecondary } - } - MouseArea { - anchors.fill: parent - hoverEnabled: true - onEntered: { - for (let i = 0; i < grid.count; i++) { - const item = grid.itemAtIndex(i); - if (item && item !== parent) { - item.isHovered = false; + Rectangle { + anchors.fill: parent + anchors.margins: 8 + color: Appearance.colors.colLayer2 + radius: Appearance.rounding.elementRounding + + Rectangle { + anchors.centerIn: parent + width: Math.min(parent.width * 0.4, 32) + height: Math.min(parent.height * 0.4, 32) + radius: Appearance.rounding.elementRounding + color: Appearance.colors.colLayer3 + visible: thumbnailImage.status !== Image.Ready + + opacity: 0.3 + SequentialAnimation on opacity { + running: parent.visible + loops: Animation.Infinite + NumberAnimation { + to: 1.0 + duration: 800 + easing.type: Easing.InOutSine + } + NumberAnimation { + to: 0.3 + duration: 800 + easing.type: Easing.InOutSine + } + } + } + + Image { + id: thumbnailImage + anchors.fill: parent + source: { + const resolvedUrl = Qt.resolvedUrl(modelData); + const md5Hash = Qt.md5(resolvedUrl); + const cacheSize = "normal" + const thumbnailPath = `${Directories.genericCache}/thumbnails/${cacheSize}/${md5Hash}.png`; + return thumbnailPath + } + fillMode: Image.PreserveAspectCrop + asynchronous: true + cache: true + smooth: true + + sourceSize.width: Math.min(128, grid.cellWidth - 16) + sourceSize.height: Math.min(96, grid.cellHeight - 16) + + mipmap: false + + opacity: status === Image.Ready ? 1 : 0 + Behavior on opacity { + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } } - parent.isHovered = true; - grid.currentIndex = index; } - onExited: { - parent.isHovered = false; + + MouseArea { + anchors.fill: parent + hoverEnabled: true + onEntered: { + for (let i = 0; i < grid.count; i++) { + const item = grid.itemAtIndex(i); + if (item && item !== parent) { + item.isHovered = false; + } + } + parent.isHovered = true; + grid.currentIndex = index; + } + onExited: { + parent.isHovered = false; + } + onClicked: { + GlobalStates.wallpaperOverviewOpen = false; + filterField.text = ""; + Wallpapers.apply(modelData); + } } - onClicked: { - GlobalStates.wallpaperOverviewOpen = false; - filterField.text = ""; - Wallpapers.apply(modelData); + } + + add: Transition { + from: "*" + to: "*" + ParallelAnimation { + PropertyAnimation { + property: "x" + from: grid.contentX + (grid.width / 2) - width / 2 + } + PropertyAnimation { + property: "y" + from: grid.contentY + (grid.height / 2) - height / 2 + } + NumberAnimation { + property: "scale" + from: 0.0 + to: 1.0 + duration: animationCurves.expressiveDefaultSpatialDuration + easing.bezierCurve: animationCurves.expressiveDefaultSpatial + } + NumberAnimation { + property: "opacity" + from: 0.0 + to: 1.0 + duration: animationCurves.expressiveDefaultSpatialDuration + easing.bezierCurve: animationCurves.expressiveDefaultSpatial + } } } } + // show when no wallpaper found + ColumnLayout { + id: noWallpapersFoundLayout + visible: root.filteredWallpapers.length === 0 + anchors.centerIn: parent - add: Transition { - from: "*" - to: "*" - ParallelAnimation { - PropertyAnimation { - property: "x" - from: grid.contentX + (grid.width / 2) - width / 2 - } - PropertyAnimation { - property: "y" - from: grid.contentY + (grid.height / 2) - height / 2 - } - NumberAnimation { - property: "scale" - from: 0.0 - to: 1.0 - duration: animationCurves.expressiveDefaultSpatialDuration - easing.bezierCurve: animationCurves.expressiveDefaultSpatial - } - NumberAnimation { - property: "opacity" - from: 0.0 - to: 1.0 - duration: animationCurves.expressiveDefaultSpatialDuration - easing.bezierCurve: animationCurves.expressiveDefaultSpatial - } + implicitHeight: noWallpapersFoundLabel.implicitHeight + implicitWidth: noWallpapersFoundLabel.implicitWidth + + Label { + id: noWallpapersFoundLabel + text: "No wallpapers found" + font.family: Appearance.font.family.main + font.pixelSize: Appearance.font.pixelSize.normal + color: Appearance.colors.colSubtext + Layout.alignment: Qt.AlignHcenter | Qt.AlignVCenter } } } - // show when no wallpaper found - ColumnLayout { - id: noWallpapersFoundLayout - visible: root.filteredWallpapers.length === 0 - anchors.centerIn: parent - - implicitHeight: noWallpapersFoundLabel.implicitHeight - implicitWidth: noWallpapersFoundLabel.implicitWidth - - Label { - id: noWallpapersFoundLabel - text: "No wallpapers found" - font.family: Appearance.font.family.main - font.pixelSize: Appearance.font.pixelSize.normal - color: Appearance.colors.colSubtext - Layout.alignment: Qt.AlignHcenter | Qt.AlignVCenter - } - } } } - } - Connections { - target: GlobalStates - function onWallpaperOverviewOpenChanged() { - if (GlobalStates.wallpaperOverviewOpen && monitorIsFocused) { - filterField.forceActiveFocus(); + Connections { + target: GlobalStates + function onWallpaperOverviewOpenChanged() { + if (GlobalStates.wallpaperOverviewOpen && monitorIsFocused) { + filterField.forceActiveFocus(); + } } } }