diff options
Diffstat (limited to 'appearance/AppearancePanel.qml')
| -rw-r--r-- | appearance/AppearancePanel.qml | 222 |
1 files changed, 168 insertions, 54 deletions
diff --git a/appearance/AppearancePanel.qml b/appearance/AppearancePanel.qml index 9ccd109..0547c6b 100644 --- a/appearance/AppearancePanel.qml +++ b/appearance/AppearancePanel.qml @@ -381,73 +381,171 @@ Scope { } } - GridView { + // A folder is a header row followed, while open, by its + // thumbnails a row at a time, all in one flat ListView, so only + // the rows in view (and cacheBuffer around them) exist and + // decode: a large folder loads as it scrolls. Each row holds a + // group key and the index of its first file, start -1 for the + // header. A toggle inserts or removes just that folder's rows, + // so the rows around it, their decoded thumbnails and the + // scroll position all survive it. + ListView { + id: wallList width: parent.width height: parent.height - y clip: true - cellWidth: Math.floor(width / Math.max(1, Math.floor(width / 300))) - cellHeight: cellWidth * 9 / 16 + 26 - // Several hundred files: only the visible thumbnails decode. - cacheBuffer: cellHeight * 2 - model: Wallpapers.files + readonly property int cols: Math.max(1, Math.floor(width / 300)) + readonly property real cell: Math.floor(width / cols) + readonly property real thumbHeight: cell * 9 / 16 + 26 + // The column is laid out after creation and reads negative + // until then, which the buffer refuses with a warning. + cacheBuffer: Math.max(0, thumbHeight * 2) + model: ListModel { id: rows } + + function group(key) { + return Wallpapers.groups.find(g => g.key === key) ?? { key: key, name: "", files: [] }; + } + function rowsOf(g) { + const out = []; + for (let i = 0; i < g.files.length; i += cols) out.push({ key: g.key, start: i }); + return out; + } + function rebuild() { + rows.clear(); + for (const g of Wallpapers.groups) { + rows.append({ key: g.key, start: -1 }); + if (Wallpapers.expanded[g.key]) rows.append(rowsOf(g)); + } + } + function toggle(index) { + const g = group(rows.get(index).key); + const body = rowsOf(g); + if (Wallpapers.expanded[g.key]) rows.remove(index + 1, body.length); + else body.forEach((r, i) => rows.insert(index + 1 + i, r)); + Wallpapers.toggle(g.key); + } + + // The width settles after creation, and a new column count + // regroups every row, so both rebuild. + Component.onCompleted: rebuild() + onColsChanged: rebuild() + Connections { + target: Wallpapers + function onGroupsChanged() { wallList.rebuild(); } + } delegate: Item { - required property string modelData - width: GridView.view.cellWidth - height: GridView.view.cellHeight + id: row + required property int index + required property string key + required property int start + readonly property var group: wallList.group(key) + readonly property bool open: !!Wallpapers.expanded[key] + width: ListView.view.width + height: start < 0 ? 40 : wallList.thumbHeight Rectangle { - readonly property bool staged: - Wallpapers.pendingH === modelData || Wallpapers.pendingV === modelData - - anchors.fill: parent - anchors.margins: 6 + visible: row.start < 0 + anchors { fill: parent; topMargin: 6 } radius: 8 - color: Qt.alpha(Theme.surface, thumbHover.hovered ? 0.8 : 0.3) - border.width: staged ? 2 : 1 - border.color: staged ? Theme.accent - : thumbHover.hovered ? Qt.alpha(Theme.accent, 0.6) - : "transparent" - clip: true - - HoverHandler { - id: thumbHover - // Hovering previews this image on the mock - // screen it would actually land on. - onHoveredChanged: - root.hovering = hovered ? modelData - : (root.hovering === modelData ? "" : root.hovering) - } + color: Qt.alpha(Theme.surface, headerHover.hovered ? 0.8 : 0.4) + + HoverHandler { id: headerHover } MouseArea { anchors.fill: parent - enabled: !Wallpapers.applying cursorShape: Qt.PointingHandCursor - onClicked: Wallpapers.pick(modelData) + onClicked: wallList.toggle(row.index) } - Column { - anchors.fill: parent - anchors.margins: 6 - spacing: 4 - - Image { - width: parent.width - height: parent.height - 18 - source: "file://" + modelData - asynchronous: true - cache: false - fillMode: Image.PreserveAspectCrop - // Decode at display size: full-resolution - // wallpapers would be megabytes each in memory. - sourceSize.width: 480 - } + 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 + } + } - Text { - width: parent.width - elide: Text.ElideMiddle - text: Wallpapers.basename(modelData) - font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 5 } - color: Theme.subtext + Row { + visible: row.start >= 0 + + Repeater { + model: row.start < 0 ? [] : row.group.files.slice(row.start, row.start + wallList.cols) + + delegate: Item { + required property string modelData + width: wallList.cell + height: wallList.thumbHeight + + Rectangle { + readonly property bool staged: + Wallpapers.pendingH === modelData || Wallpapers.pendingV === modelData + + anchors.fill: parent + anchors.margins: 6 + radius: 8 + color: Qt.alpha(Theme.surface, thumbHover.hovered ? 0.8 : 0.3) + // Staged wins the border; otherwise it + // says how well the image fills the + // screen it would go to. + border.width: 2 + border.color: staged ? Theme.accent + : Wallpapers.qualityColor(modelData, Wallpapers.target) + clip: true + + HoverHandler { + id: thumbHover + // Hovering previews this image on the mock + // screen it would actually land on. + onHoveredChanged: + root.hovering = hovered ? modelData + : (root.hovering === modelData ? "" : root.hovering) + } + MouseArea { + anchors.fill: parent + enabled: !Wallpapers.applying + cursorShape: Qt.PointingHandCursor + onClicked: Wallpapers.pick(modelData) + } + + Column { + anchors.fill: parent + anchors.margins: 6 + spacing: 4 + + Image { + width: parent.width + height: parent.height - 18 + source: "file://" + modelData + asynchronous: true + cache: false + fillMode: Image.PreserveAspectCrop + // Decode at display size: full-resolution + // wallpapers would be megabytes each in memory. + sourceSize.width: wallList.cell + } + + Text { + width: parent.width + elide: Text.ElideMiddle + text: Wallpapers.label(modelData) + font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 5 } + color: Theme.subtext + } + } + } } } } @@ -504,13 +602,29 @@ Scope { width: mockPane.width horizontalAlignment: Text.AlignHCenter wrapMode: Text.Wrap - text: root.hovering !== "" ? Wallpapers.basename(root.hovering) + text: root.hovering !== "" ? Wallpapers.label(root.hovering) : Wallpapers.dirty ? "click Apply to set" : "click a wallpaper to stage it" font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 } color: Theme.overlay } + // Why the image under consideration, hovered or else the + // one staged for the target screen, will look worse than + // its thumbnail. + Text { + readonly property string subject: root.hovering !== "" ? root.hovering + : Wallpapers.target === "V" ? Wallpapers.pendingV : Wallpapers.pendingH + anchors.horizontalCenter: parent.horizontalCenter + width: mockPane.width + visible: text !== "" + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.Wrap + text: subject !== "" ? Wallpapers.qualityNote(subject, Wallpapers.target) : "" + font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 2 } + color: Wallpapers.qualityColor(subject, Wallpapers.target) + } + Row { anchors.horizontalCenter: parent.horizontalCenter spacing: 8 |
