refactor: add page base component

Also put page content inside flickable
This commit is contained in:
2 * r + 2 * t 2026-06-05 17:13:31 +10:00
parent 45c97074b7
commit 550eb0bf1a
2 changed files with 212 additions and 159 deletions

View file

@ -0,0 +1,64 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Caelestia.Config
import qs.components
import qs.components.containers
import qs.components.controls
import qs.services
import qs.modules.nexus
ColumnLayout {
id: root
required property string title
required property NexusState nState
property bool isSubPage
readonly property int cappedWidth: Math.min(800, width)
default property Item contentChild
signal close
spacing: Tokens.spacing.extraExtraLarge
RowLayout {
spacing: Tokens.spacing.largeIncreased
Loader {
visible: active
active: root.isSubPage
asynchronous: true
sourceComponent: IconButton {
icon: "arrow_back"
font: Tokens.font.icon.medium
type: IconButton.Tonal
isRound: true
inactiveColour: Colours.tPalette.m3surfaceContainerHigh
inactiveOnColour: Colours.palette.m3onSurface
onClicked: root.close()
}
}
StyledText {
text: root.title
font: Tokens.font.title.large
}
}
VerticalFadeFlickable {
id: flickable
Layout.fillWidth: true
Layout.fillHeight: true
Layout.topMargin: -topMargin
Layout.bottomMargin: -bottomMargin
topMargin: Tokens.padding.large
bottomMargin: Tokens.padding.large
contentHeight: root.contentChild?.implicitHeight ?? 0
contentItem.children: [root.contentChild]
}
}

View file

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