feat: add wallpaper & style nexus page

This commit is contained in:
2 * r + 2 * t 2026-06-05 00:26:25 +10:00
parent 69fe7c1251
commit c31cb2a7f6
5 changed files with 345 additions and 2 deletions

View file

@ -9,6 +9,7 @@ MouseArea {
property bool disabled
property bool showHoverBackground: true
property bool manualPressOverride
readonly property alias rect: base
property bool shapeMorph
@ -60,12 +61,17 @@ MouseArea {
onPressed: e => press(e.x, e.y)
onPressedChanged: {
if (!pressed && !rippleAnim.running && circle.opacity > 0)
if (!(pressed || manualPressOverride) && !rippleAnim.running && circle.opacity > 0)
fadeAnim.start();
}
onManualPressOverrideChanged: {
if (!(pressed || manualPressOverride) && circleRadius > endRadiusAtPress * 0.99 && !fadeAnim.running)
fadeAnim.start();
}
onCircleRadiusChanged: {
if (!pressed && circleRadius > endRadiusAtPress * 0.99 && !fadeAnim.running)
if (!(pressed || manualPressOverride) && circleRadius > endRadiusAtPress * 0.99 && !fadeAnim.running)
fadeAnim.start();
}

View file

@ -89,4 +89,15 @@ Item {
nState: nState
width: Math.min(Tokens.sizes.nexus.maxNavWidth, Math.round(root.width / 3))
}
Pages {
anchors.left: navPane.right
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.leftMargin: navPane.anchors.margins + anchors.margins
anchors.margins: Tokens.padding.extraLarge
nState: nState
}
}

111
modules/nexus/Pages.qml Normal file
View file

@ -0,0 +1,111 @@
pragma ComponentBehavior: Bound
import "pages"
import QtQuick
import QtQuick.Layouts
import Caelestia.Config
import qs.components
import qs.services
import qs.modules.nexus
Item {
id: root
required property NexusState nState
readonly property list<Component> pageComps: [
Component {
WallpaperAndStyle {
nState: root.nState
}
}
]
property int lastPageIdx
property int animOff
Loader {
id: loader
anchors.fill: parent
layer.enabled: opacity < 1
Component.onCompleted: sourceComponent = root.pageComps[root.nState.currentPageIdx] ?? placeholderComp
}
Connections {
function onCurrentPageIdxChanged(): void {
switchAnim.complete();
root.animOff = root.Tokens.padding.extraLarge * (root.nState.currentPageIdx > root.lastPageIdx ? -1 : 1);
switchAnim.start();
root.lastPageIdx = root.nState.currentPageIdx;
}
target: root.nState
}
SequentialAnimation {
id: switchAnim
Anim {
target: loader
property: "opacity"
to: 0
type: Anim.DefaultEffects
}
PropertyAction {
target: loader
property: "sourceComponent"
value: root.pageComps[root.nState.currentPageIdx] ?? placeholderComp
}
PropertyAction {
target: loader.anchors
property: "topMargin"
value: root.animOff
}
PropertyAction {
target: loader.anchors
property: "bottomMargin"
value: -root.animOff
}
ParallelAnimation {
Anim {
target: loader
property: "opacity"
from: 0
to: 1
type: Anim.SlowEffects
}
Anim {
target: loader.anchors
properties: "topMargin,bottomMargin"
to: 0
type: Anim.SlowEffects
}
}
}
Component {
id: placeholderComp
Item {
ColumnLayout {
anchors.centerIn: parent
spacing: Tokens.padding.extraSmall
MaterialIcon {
Layout.alignment: Qt.AlignHCenter
text: "handyman"
color: Colours.palette.m3outlineVariant
font: Tokens.font.icon.extraLarge
}
StyledText {
Layout.alignment: Qt.AlignHCenter
text: qsTr("Page under construction")
color: Colours.palette.m3outlineVariant
font: Tokens.font.title.large
}
}
}
}
}

View file

@ -0,0 +1,55 @@
import QtQuick
import Caelestia.Config
import qs.components
import qs.components.controls
import qs.services
StyledSwitch {
id: root
property bool first
property bool last
implicitWidth: implicitContentWidth + implicitIndicatorWidth + Tokens.padding.largeIncreased * 2
implicitHeight: Math.max(implicitContentHeight, implicitIndicatorHeight) + Tokens.padding.medium * 2
cLayer: 2
indicator.anchors.verticalCenter: verticalCenter
indicator.anchors.right: right
indicator.anchors.rightMargin: Tokens.padding.large
onPressed: stateLayer.press(stateLayer.mouseX, stateLayer.mouseY)
background: StyledRect {
color: Colours.tPalette.m3surfaceContainer
topLeftRadius: root.first ? Tokens.rounding.extraLarge : Tokens.rounding.extraSmall
topRightRadius: root.first ? Tokens.rounding.extraLarge : Tokens.rounding.extraSmall
bottomLeftRadius: root.last ? Tokens.rounding.extraLarge : Tokens.rounding.extraSmall
bottomRightRadius: root.last ? Tokens.rounding.extraLarge : Tokens.rounding.extraSmall
StateLayer {
id: stateLayer
manualPressOverride: root.pressed
topLeftRadius: parent.topLeftRadius
topRightRadius: parent.topRightRadius
bottomLeftRadius: parent.bottomLeftRadius
bottomRightRadius: parent.bottomRightRadius
}
}
contentItem: StyledText {
id: label
anchors.left: parent.left
anchors.right: root.indicator.left
anchors.leftMargin: Tokens.padding.large
anchors.rightMargin: Tokens.spacing.medium
text: root.text
font: Tokens.font.body.small
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
}
}

View file

@ -0,0 +1,160 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import Caelestia.Components
import Caelestia.Config
import qs.components
import qs.components.controls
import qs.services
import qs.modules.nexus
import qs.modules.nexus.common
ColumnLayout {
id: root
required property NexusState nState
readonly property int cappedWidth: Math.min(800, width)
spacing: Tokens.spacing.large
StyledText {
text: qsTr("Wallpaper & style")
font: Tokens.font.title.large
}
StyledClippingRect {
id: wallWrapper
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: Tokens.spacing.extraLarge
implicitWidth: {
const screen = root.nState.screen;
return implicitHeight / screen.height * screen.width;
}
implicitHeight: {
const screen = root.nState.screen;
const cWidth = root.cappedWidth;
return Math.min(Math.round(cWidth * 0.4), cWidth / screen.width * screen.height);
}
color: Colours.palette.m3surfaceContainer
radius: Tokens.rounding.large
Loader {
anchors.centerIn: parent
opacity: wallImg.status === Image.Ready ? 0 : 1
active: opacity > 0
sourceComponent: StyledRect {
implicitWidth: wallLoadingIndicator.implicitSize + Tokens.padding.largeIncreased * 2
implicitHeight: wallLoadingIndicator.implicitSize + Tokens.padding.largeIncreased * 2
color: Colours.palette.m3primaryContainer
radius: Tokens.rounding.full
LoadingIndicator {
id: wallLoadingIndicator
anchors.centerIn: parent
containsIcon: true
implicitSize: wallWrapper.implicitHeight * 0.4
}
}
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
}
}
}
Image {
id: wallImg
anchors.fill: parent
source: Wallpapers.current
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
}
}
}
}
ButtonRow {
Layout.alignment: Qt.AlignHCenter
spacing: Tokens.spacing.small
IconTextButton {
icon: "wallpaper"
text: qsTr("Wallpapers")
font: Tokens.font.body.large
isRound: true
shapeMorph: true
type: IconTextButton.Tonal
horizontalPadding: Tokens.padding.extraLarge
verticalPadding: Tokens.padding.medium
onClicked: ; // TODO
}
IconTextButton {
icon: "palette"
text: qsTr("Colours")
font: Tokens.font.body.large
isRound: true
shapeMorph: true
type: IconTextButton.Tonal
horizontalPadding: Tokens.padding.extraLarge
verticalPadding: Tokens.padding.medium
onClicked: ; // TODO
}
}
ToggleRow {
Layout.alignment: Qt.AlignHCenter
implicitWidth: root.cappedWidth
first: true
text: qsTr("Display wallpaper")
checked: Config.background.wallpaperEnabled
onToggled: GlobalConfig.background.wallpaperEnabled = checked
}
ToggleRow {
Layout.topMargin: Tokens.spacing.extraSmall / 2 - parent.spacing
Layout.alignment: Qt.AlignHCenter
implicitWidth: root.cappedWidth
text: qsTr("Dark theme")
checked: !Colours.light
onToggled: Colours.setMode(checked ? "dark" : "light")
}
ToggleRow {
Layout.topMargin: Tokens.spacing.extraSmall / 2 - parent.spacing
Layout.alignment: Qt.AlignHCenter
implicitWidth: root.cappedWidth
last: true
text: qsTr("Transparency")
checked: Colours.transparency.enabled
onToggled: GlobalConfig.appearance.transparency.enabled = checked
}
Item {
Layout.fillHeight: true
}
}