From 61d17788ff132fcd02d6831934cfe9beba1d49ce Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Sun, 18 Oct 2026 22:14:33 +0200 Subject: wallpaper: row-per-notch wheel and a sticky folder header The thumbnail list scrolled a fraction of a thumbnail per wheel notch, which is ListView's default step and slow with rows this tall. A MouseArea over the list (acceptedButtons: Qt.NoButton, so clicks and hovers still reach the thumbnails) now moves one row per notch, and keeps a touchpad's pixel delta as it is. An open folder with many images scrolled its header out of view, so folding it meant scrolling back to the top. A copy of the header is now pinned over the list while the topmost row belongs to a folder whose own header has scrolled off; clicking it folds that folder and positions the view at its header. The header row became an inline component so the list and the pinned copy draw the same thing. Loads clean; not yet confirmed by eye. Co-Authored-By: Claude Opus 5.5 --- desktop/modules/appearance/WallpaperTab.qml | 116 +++++++++++++++++++++------- 1 file changed, 86 insertions(+), 30 deletions(-) diff --git a/desktop/modules/appearance/WallpaperTab.qml b/desktop/modules/appearance/WallpaperTab.qml index af3c23b..3931fcb 100644 --- a/desktop/modules/appearance/WallpaperTab.qml +++ b/desktop/modules/appearance/WallpaperTab.qml @@ -28,6 +28,45 @@ Column { // wallhaven-dl opens. signal dismiss + // A folder's header row: chevron, name and count. The list draws one per + // folder, and the sticky copy over its top draws the folder in view. + component FolderHeader: Rectangle { + id: hdr + property string name + property int count + property bool open + signal clicked + radius: 8 + color: Qt.alpha(Theme.surface, headerHover.hovered ? 0.8 : 0.4) + + HoverHandler { id: headerHover } + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: hdr.clicked() + } + + Text { + id: chevron + anchors { left: parent.left; leftMargin: 12; verticalCenter: parent.verticalCenter } + text: hdr.open ? "\uf077" : "\uf078" + font { family: Theme.iconFamily; pixelSize: Theme.fontSize - 4 } + color: Theme.subtext + } + Text { + anchors { left: chevron.right; leftMargin: 10; verticalCenter: parent.verticalCenter } + text: hdr.name + font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 2; bold: true } + color: Theme.text + } + Text { + anchors { right: parent.right; rightMargin: 12; verticalCenter: parent.verticalCenter } + text: hdr.count + font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 } + color: Theme.overlay + } + } + Connections { target: Wallpapers function onApplied(ok, message) { @@ -234,38 +273,13 @@ Column { width: ListView.view.width height: start < 0 ? 40 : wallList.thumbHeight - Rectangle { + FolderHeader { visible: row.start < 0 anchors { fill: parent; topMargin: 6 } - radius: 8 - color: Qt.alpha(Theme.surface, headerHover.hovered ? 0.8 : 0.4) - - HoverHandler { id: headerHover } - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - onClicked: wallList.toggle(row.index) - } - - Text { - id: chevron - anchors { left: parent.left; leftMargin: 12; verticalCenter: parent.verticalCenter } - text: row.open ? "\uf077" : "\uf078" - font { family: Theme.iconFamily; pixelSize: Theme.fontSize - 4 } - color: Theme.subtext - } - Text { - anchors { left: chevron.right; leftMargin: 10; verticalCenter: parent.verticalCenter } - text: row.group.name - font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 2; bold: true } - color: Theme.text - } - Text { - anchors { right: parent.right; rightMargin: 12; verticalCenter: parent.verticalCenter } - text: row.group.files.length - font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 } - color: Theme.overlay - } + name: row.group.name + count: row.group.files.length + open: row.open + onClicked: wallList.toggle(row.index) } Row { @@ -340,6 +354,48 @@ Column { } } } + + // The folder of the topmost row, once its own header has + // scrolled off: a copy pinned over the list, so an open folder + // folds from anywhere in it. Folding returns to its header. + readonly property int topIndex: count, indexAt(0, contentY + 1) + readonly property int topStart: topIndex < 0 ? -1 : rows.get(topIndex).start + Rectangle { + parent: wallList + visible: wallList.topStart >= 0 + width: wallList.width + height: 40 + color: Theme.base + + FolderHeader { + readonly property var group: wallList.topStart < 0 ? null + : wallList.group(rows.get(wallList.topIndex).key) + anchors { fill: parent; topMargin: 6 } + name: group?.name ?? "" + count: group?.files.length ?? 0 + open: true + onClicked: { + let h = wallList.topIndex; + while (h > 0 && rows.get(h).start >= 0) h--; + wallList.toggle(h); + wallList.positionViewAtIndex(h, ListView.Beginning); + } + } + } + + // The default wheel step moves a fraction of a thumbnail; a + // notch here moves one row. A touchpad's pixel delta is kept. + MouseArea { + parent: wallList + anchors.fill: parent + acceptedButtons: Qt.NoButton + onWheel: wheel => { + const d = wheel.pixelDelta.y !== 0 ? wheel.pixelDelta.y + : wheel.angleDelta.y / 120 * wallList.thumbHeight; + wallList.contentY = Math.max(wallList.originY, Math.min( + wallList.originY + wallList.contentHeight - wallList.height, wallList.contentY - d)); + } + } } } } -- cgit v1.2.3