feat: actually impl wall selector w/ categories

This commit is contained in:
2 * r + 2 * t 2026-06-05 21:39:05 +10:00
parent b01137edf1
commit 0bbb1c04b7
6 changed files with 189 additions and 89 deletions

View file

@ -8,6 +8,8 @@ QtObject {
property list<int> subPageIdxStack property list<int> subPageIdxStack
property bool searchOpen property bool searchOpen
property string selectedWallpaperCategory
signal close signal close
signal subPageOpened(idx: int) signal subPageOpened(idx: int)
signal subPageClosed signal subPageClosed

View file

@ -18,6 +18,9 @@ QtObject {
Component { Component {
WallpaperSelect {} WallpaperSelect {}
} }
Component {
WallpaperCategory {}
}
} }
} }
] ]

View file

@ -0,0 +1,105 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import Caelestia.Config
import qs.components
import qs.components.controls
import qs.services
Item {
id: root
property alias source: img.source
property alias text: label.text
property alias radius: imgWrapper.radius
property alias imgHeight: imgWrapper.implicitHeight
property bool fillLabel: true
signal clicked
Layout.fillWidth: true
implicitHeight: layout.implicitHeight
ColumnLayout {
id: layout
anchors.fill: parent
spacing: Tokens.spacing.small
StyledClippingRect {
id: imgWrapper
Layout.fillWidth: true
implicitHeight: width
radius: Tokens.rounding.largeIncreased
color: Colours.tPalette.m3surfaceContainer
Loader {
anchors.centerIn: parent
opacity: img.status === Image.Ready ? 0 : 1
active: opacity > 0
sourceComponent: StyledRect {
implicitWidth: loadingIndicator.implicitSize + Tokens.padding.large * 2
implicitHeight: loadingIndicator.implicitSize + Tokens.padding.large * 2
color: Colours.palette.m3primaryContainer
radius: Tokens.rounding.full
LoadingIndicator {
id: loadingIndicator
anchors.centerIn: parent
containsIcon: true
implicitSize: Math.min(imgWrapper.width, imgWrapper.height) * 0.3
}
}
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
}
}
}
Image {
id: img
anchors.fill: parent
asynchronous: true
fillMode: Image.PreserveAspectCrop
sourceSize: {
const dpr = (QsWindow.window as QsWindow)?.devicePixelRatio ?? 1;
return Qt.size(width * dpr, height * dpr);
}
retainWhileLoading: true
opacity: status === Image.Ready ? 1 : 0
Behavior on opacity {
Anim {
type: Anim.SlowEffects
}
}
}
}
StyledText {
id: label
Layout.bottomMargin: Tokens.padding.small
Layout.fillWidth: true
color: Colours.palette.m3onSurfaceVariant
font: Tokens.font.label.builders.small.weight(Font.Medium).build()
horizontalAlignment: Text.AlignHCenter
elide: Text.ElideRight
}
}
StateLayer {
anchors.bottomMargin: root.fillLabel ? 0 : layout.implicitHeight - imgWrapper.implicitHeight
onClicked: root.clicked()
}
}

View file

@ -0,0 +1,38 @@
import QtQuick
import QtQuick.Layouts
import Caelestia.Config
import Caelestia.Models
import qs.services
import qs.modules.nexus.common
PageBase {
id: root
title: {
const c = nState.selectedWallpaperCategory;
return c.slice(0, 1).toUpperCase() + c.slice(1);
}
isSubPage: true
GridLayout {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
width: root.cappedWidth
columns: Config.nexus.wallpapersPerRow
rowSpacing: Tokens.spacing.medium
columnSpacing: Tokens.spacing.large
Repeater {
model: Wallpapers.list.filter(w => Wallpapers.getCategoryFor(w) === root.nState.selectedWallpaperCategory)
WallItem {
required property FileSystemEntry modelData
source: modelData.path
text: modelData.name
onClicked: Wallpapers.setWallpaper(modelData.path)
}
}
}
}

View file

@ -2,12 +2,11 @@ pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell
import Caelestia.Config import Caelestia.Config
import Caelestia.Models import Caelestia.Models
import qs.components import qs.components
import qs.components.controls
import qs.services import qs.services
import qs.utils
import qs.modules.nexus.common import qs.modules.nexus.common
PageBase { PageBase {
@ -22,17 +21,13 @@ PageBase {
width: root.cappedWidth width: root.cappedWidth
spacing: Tokens.spacing.small spacing: Tokens.spacing.small
Wallpaper { WallItem {
Layout.fillWidth: true imgHeight: Math.round(width * 0.3)
implicitHeight: Math.round(width * 0.3)
radius: Tokens.rounding.extraLarge radius: Tokens.rounding.extraLarge
source: Wallpapers.current source: Wallpapers.current
}
StyledText {
Layout.alignment: Qt.AlignHCenter
text: qsTr("Featured wallpaper") text: qsTr("Featured wallpaper")
font: Tokens.font.label.medium fillLabel: false
onClicked: ; // TODO
} }
StyledText { StyledText {
@ -45,95 +40,45 @@ PageBase {
Layout.fillWidth: true Layout.fillWidth: true
columns: Config.nexus.wallpapersPerRow columns: Config.nexus.wallpapersPerRow
rowSpacing: Tokens.spacing.large rowSpacing: Tokens.spacing.medium
columnSpacing: Tokens.spacing.large columnSpacing: Tokens.spacing.large
Repeater { Repeater {
model: Wallpapers.list model: {
const walls = Wallpapers.list;
ColumnLayout { const baseDir = Paths.wallsdir;
id: wallDelegate const categories = {};
for (const w of walls) {
if (w.parentDir !== baseDir) {
const category = Wallpapers.getCategoryFor(w);
if (category && !(category in categories))
categories[category] = w;
}
}
return Object.values(categories);
}
WallItem {
required property FileSystemEntry modelData required property FileSystemEntry modelData
Layout.fillWidth: true source: modelData.path
Layout.preferredWidth: 0 // Force uniform size text: {
spacing: Tokens.spacing.small if (modelData.parentDir !== Paths.wallsdir) {
const category = Wallpapers.getCategoryFor(modelData);
Wallpaper { return category.slice(0, 1).toUpperCase() + category.slice(1);
Layout.fillWidth: true
implicitHeight: width
radius: Tokens.rounding.largeIncreased
source: wallDelegate.modelData.path
} }
return modelData.name;
StyledText { }
Layout.fillWidth: true onClicked: {
text: wallDelegate.modelData.name if (modelData.parentDir !== Paths.wallsdir) {
color: Colours.palette.m3onSurfaceVariant root.nState.selectedWallpaperCategory = Wallpapers.getCategoryFor(modelData);
font: Tokens.font.label.builders.small.weight(Font.Medium).build() root.nState.openSubPage(2);
horizontalAlignment: Text.AlignHCenter } else {
elide: Text.ElideRight Wallpapers.setWallpaper(modelData.path);
} }
} }
} }
} }
} }
component Wallpaper: StyledClippingRect {
id: wallpaper
property alias source: img.source
color: Colours.tPalette.m3surfaceContainer
Loader {
anchors.centerIn: parent
opacity: img.status === Image.Ready ? 0 : 1
active: opacity > 0
sourceComponent: StyledRect {
implicitWidth: loadingIndicator.implicitSize + Tokens.padding.large * 2
implicitHeight: loadingIndicator.implicitSize + Tokens.padding.large * 2
color: Colours.palette.m3primaryContainer
radius: Tokens.rounding.full
LoadingIndicator {
id: loadingIndicator
anchors.centerIn: parent
containsIcon: true
implicitSize: Math.min(wallpaper.width, wallpaper.height) * 0.3
}
}
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
}
}
}
Image {
id: img
anchors.fill: parent
asynchronous: true
fillMode: Image.PreserveAspectCrop
sourceSize: {
const dpr = (QsWindow.window as QsWindow)?.devicePixelRatio ?? 1;
return Qt.size(width * dpr, height * dpr);
}
retainWhileLoading: true
opacity: status === Image.Ready ? 1 : 0
Behavior on opacity {
Anim {
type: Anim.SlowEffects
}
}
}
} }
} }

View file

@ -20,6 +20,13 @@ Searcher {
property string actualCurrent property string actualCurrent
property bool previewColourLock property bool previewColourLock
function getCategoryFor(w: FileSystemEntry): string {
let category = w.parentDir.slice(Paths.wallsdir.length + 1);
if (category.includes("/"))
category = category.slice(0, category.indexOf("/"));
return category;
}
function setWallpaper(path: string): void { function setWallpaper(path: string): void {
actualCurrent = path; actualCurrent = path;
Quickshell.execDetached(["caelestia", "wallpaper", "-f", path, ...smartArg]); Quickshell.execDetached(["caelestia", "wallpaper", "-f", path, ...smartArg]);