commit
dd3402eb14
83 changed files with 5746 additions and 93 deletions
|
|
@ -245,7 +245,7 @@ For example, to disable the bar on DP-1:
|
|||
> - `utilities` (`toasts`, `vpn`)
|
||||
> - `services` (`weatherLocation`, `useFahrenheit`, `useFahrenheitPerformance`, `useTwelveHourClock`,
|
||||
> `gpuType`, `visualiserBars`, `audioIncrement`, `brightnessIncrement`, `maxVolume`, `smartScheme`,
|
||||
> `defaultPlayer`, `playerAliases`, `showLyrics`, `lyricsBackend`)
|
||||
> `defaultPlayer`, `playerAliases`, `lyricsBackend`)
|
||||
> - `paths` (`wallpaperDir`, `lyricsDir`)
|
||||
>
|
||||
> </details>
|
||||
|
|
|
|||
BIN
assets/wallpaper.webp
Normal file
BIN
assets/wallpaper.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.3 MiB |
42
components/AnimLoader.qml
Normal file
42
components/AnimLoader.qml
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import QtQuick
|
||||
|
||||
Loader {
|
||||
id: root
|
||||
|
||||
property Component sourceComp
|
||||
property bool isComplete
|
||||
property int outAnimType: Anim.FastEffects
|
||||
property int inAnimType: Anim.DefaultEffects
|
||||
|
||||
asynchronous: true
|
||||
Component.onCompleted: {
|
||||
isComplete = true;
|
||||
sourceComponent = sourceComp;
|
||||
}
|
||||
onSourceCompChanged: {
|
||||
if (isComplete)
|
||||
anim.restart();
|
||||
}
|
||||
|
||||
SequentialAnimation {
|
||||
id: anim
|
||||
|
||||
running: false
|
||||
|
||||
Anim {
|
||||
target: root
|
||||
property: "opacity"
|
||||
to: 0
|
||||
type: root.outAnimType
|
||||
}
|
||||
ScriptAction {
|
||||
script: root.sourceComponent = root.sourceComp
|
||||
}
|
||||
Anim {
|
||||
target: root
|
||||
property: "opacity"
|
||||
to: 1
|
||||
type: root.inAnimType
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import QtQuick
|
||||
import QtQuick.Shapes
|
||||
import Caelestia
|
||||
import Caelestia.Config
|
||||
import qs.services
|
||||
|
||||
|
|
@ -8,10 +9,11 @@ MouseArea {
|
|||
|
||||
property bool disabled
|
||||
property bool showHoverBackground: true
|
||||
property bool manualPressOverride
|
||||
readonly property alias rect: base
|
||||
|
||||
property bool shapeMorph
|
||||
property real stateOpacity: pressed ? 0.1 : containsMouse ? 0.08 : 0
|
||||
property real stateOpacity: containsMouse ? 0.08 : 0
|
||||
|
||||
property real pressX: width / 2
|
||||
property real pressY: height / 2
|
||||
|
|
@ -29,7 +31,7 @@ MouseArea {
|
|||
const d2 = distSq(width, 0);
|
||||
const d3 = distSq(0, height);
|
||||
const d4 = distSq(width, height);
|
||||
return Math.sqrt(Math.max(d1, d2, d3, d4)) + (shapeMorph ? 24 : 0);
|
||||
return (Math.sqrt(Math.max(d1, d2, d3, d4)) + (shapeMorph ? 24 : 0)) * 1.3;
|
||||
}
|
||||
property real endRadiusAtPress
|
||||
|
||||
|
|
@ -59,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();
|
||||
}
|
||||
|
||||
|
|
@ -75,7 +82,7 @@ MouseArea {
|
|||
target: root
|
||||
property: "circleRadius"
|
||||
to: root.endRadius
|
||||
easing: Tokens.anim.expressiveSlowEffects
|
||||
easing: Tokens.anim.standard
|
||||
duration: Tokens.anim.durations.expressiveSlowEffects * 2
|
||||
}
|
||||
|
||||
|
|
@ -94,8 +101,14 @@ MouseArea {
|
|||
anchors.fill: parent
|
||||
opacity: root.stateOpacity
|
||||
color: Colours.palette.m3onSurface
|
||||
// Pick up radius from parent if it has one (parent can be anything with a radius property)
|
||||
radius: root.parent?.radius ?? 0 // qmllint disable missing-property
|
||||
// Pick up radius from parent if it has one (parent can be anything with radius props)
|
||||
// qmllint disable missing-property
|
||||
radius: root.parent?.radius ?? 0
|
||||
topLeftRadius: root.parent?.topLeftRadius ?? radius ?? 0
|
||||
topRightRadius: root.parent?.topRightRadius ?? radius ?? 0
|
||||
bottomLeftRadius: root.parent?.bottomLeftRadius ?? radius ?? 0
|
||||
bottomRightRadius: root.parent?.bottomRightRadius ?? radius ?? 0
|
||||
// qmllint enable missing-property
|
||||
}
|
||||
|
||||
Shape {
|
||||
|
|
@ -121,12 +134,12 @@ MouseArea {
|
|||
color: Qt.alpha(base.color, 1)
|
||||
}
|
||||
GradientStop {
|
||||
position: 0.99
|
||||
position: CUtils.clamp(1 - 0.2 * root.endRadius / root.circleRadius, 0.01, 0.99)
|
||||
color: Qt.alpha(base.color, 1)
|
||||
}
|
||||
GradientStop {
|
||||
position: 1
|
||||
color: Qt.alpha(base.color, 0)
|
||||
color: Qt.alpha(base.color, CUtils.clamp((root.circleRadius / root.endRadius - 0.9) / 0.1, 0, 1))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,11 +4,28 @@ import qs.components
|
|||
Flickable {
|
||||
id: root
|
||||
|
||||
property bool doneFakeFlick
|
||||
|
||||
maximumFlickVelocity: 3000
|
||||
|
||||
rebound: Transition {
|
||||
onRunningChanged: {
|
||||
if (!running && !root.doneFakeFlick) {
|
||||
root.doneFakeFlick = true;
|
||||
root.flick(1, 1);
|
||||
root.flick(-1, -1);
|
||||
Qt.callLater(() => root.cancelFlick());
|
||||
}
|
||||
}
|
||||
|
||||
Anim {
|
||||
properties: "x,y"
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
running: root.doneFakeFlick
|
||||
interval: 10
|
||||
onTriggered: root.doneFakeFlick = false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,11 +4,28 @@ import qs.components
|
|||
ListView {
|
||||
id: root
|
||||
|
||||
property bool doneFakeFlick
|
||||
|
||||
maximumFlickVelocity: 3000
|
||||
|
||||
rebound: Transition {
|
||||
onRunningChanged: {
|
||||
if (!running && !root.doneFakeFlick) {
|
||||
root.doneFakeFlick = true;
|
||||
root.flick(1, 1);
|
||||
root.flick(-1, -1);
|
||||
Qt.callLater(() => root.cancelFlick());
|
||||
}
|
||||
}
|
||||
|
||||
Anim {
|
||||
properties: "x,y"
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
running: root.doneFakeFlick
|
||||
interval: 10
|
||||
onTriggered: root.doneFakeFlick = false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
72
components/containers/VerticalFadeFlickable.qml
Normal file
72
components/containers/VerticalFadeFlickable.qml
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import qs.components
|
||||
import qs.components.effects
|
||||
|
||||
StyledFlickable {
|
||||
id: root
|
||||
|
||||
property real fadeAmount: 0.2
|
||||
|
||||
property real topFadeOpacity: fadeShouldBeActive(true) ? 0 : 1
|
||||
property real bottomFadeOpacity: fadeShouldBeActive(false) ? 0 : 1
|
||||
|
||||
function fadeShouldBeActive(isStart: bool): bool {
|
||||
// When content is smaller than flickable size, hide fade when rebound starts
|
||||
if (contentHeight + topMargin + bottomMargin < height && rebound.running && ((isStart ? verticalOvershoot > 0 : verticalOvershoot < 0)))
|
||||
return false;
|
||||
|
||||
if (isStart)
|
||||
return visibleArea.yPosition > 0;
|
||||
return visibleArea.yPosition + visibleArea.heightRatio < 1;
|
||||
}
|
||||
|
||||
flickableDirection: Flickable.VerticalFlick
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: Mask {
|
||||
maskSource: mask
|
||||
|
||||
Rectangle {
|
||||
id: mask
|
||||
|
||||
anchors.fill: parent
|
||||
visible: false
|
||||
layer.enabled: true
|
||||
|
||||
gradient: Gradient {
|
||||
orientation: Gradient.Vertical
|
||||
|
||||
GradientStop {
|
||||
position: 0
|
||||
color: Qt.rgba(0, 0, 0, root.topFadeOpacity)
|
||||
}
|
||||
GradientStop {
|
||||
position: root.fadeAmount
|
||||
color: Qt.rgba(0, 0, 0, 1)
|
||||
}
|
||||
GradientStop {
|
||||
position: 1 - root.fadeAmount
|
||||
color: Qt.rgba(0, 0, 0, 1)
|
||||
}
|
||||
GradientStop {
|
||||
position: 1
|
||||
color: Qt.rgba(0, 0, 0, root.bottomFadeOpacity)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on topFadeOpacity {
|
||||
Anim {
|
||||
type: Anim.SlowEffects
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on bottomFadeOpacity {
|
||||
Anim {
|
||||
type: Anim.SlowEffects
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,3 @@
|
|||
//@ pragma Internal
|
||||
|
||||
import QtQuick
|
||||
import Caelestia.Config
|
||||
import qs.components
|
||||
|
|
@ -35,10 +33,10 @@ StyledRect {
|
|||
readonly property alias stateLayer: stateLayer
|
||||
readonly property alias radiusAnim: radiusAnim
|
||||
|
||||
required property color activeColour
|
||||
required property color inactiveColour
|
||||
required property color activeOnColour
|
||||
required property color inactiveOnColour
|
||||
property color activeColour
|
||||
property color inactiveColour
|
||||
property color activeOnColour
|
||||
property color inactiveOnColour
|
||||
property color disabledColour: Qt.alpha(Colours.palette.m3onSurface, 0.1)
|
||||
property color disabledOnColour: Qt.alpha(Colours.palette.m3onSurface, 0.38)
|
||||
|
||||
|
|
@ -60,7 +58,7 @@ StyledRect {
|
|||
if (internalChecked)
|
||||
return checkedRadius;
|
||||
if (isRound)
|
||||
return implicitHeight / 2 * Math.min(1, Tokens.rounding.scale);
|
||||
return (height || implicitHeight) / 2 * Math.min(1, Tokens.rounding.scale);
|
||||
return defaultRadius;
|
||||
}
|
||||
color: type === ButtonBase.Text ? "transparent" : disabled ? disabledColour : internalChecked ? activeColour : inactiveColour
|
||||
|
|
|
|||
|
|
@ -13,10 +13,27 @@ ButtonBase {
|
|||
readonly property alias iconLabel: iconLabel
|
||||
readonly property alias label: label
|
||||
|
||||
activeColour: type === IconTextButton.Filled ? Colours.palette.m3primary : Colours.palette.m3secondary
|
||||
inactiveColour: type === IconTextButton.Filled ? Colours.tPalette.m3surfaceContainer : Colours.palette.m3secondaryContainer
|
||||
activeOnColour: type === IconTextButton.Filled ? Colours.palette.m3onPrimary : Colours.palette.m3onSecondary
|
||||
inactiveOnColour: type === IconTextButton.Filled ? Colours.palette.m3onSurface : Colours.palette.m3onSecondaryContainer
|
||||
horizontalPadding: Tokens.padding.medium
|
||||
verticalPadding: Tokens.padding.small
|
||||
|
||||
activeColour: type === TextButton.Filled ? Colours.palette.m3primary : Colours.palette.m3secondary
|
||||
inactiveColour: {
|
||||
if (!isToggle && type === TextButton.Filled)
|
||||
return Colours.palette.m3primary;
|
||||
return type === TextButton.Filled ? Colours.tPalette.m3surfaceContainer : Colours.palette.m3secondaryContainer;
|
||||
}
|
||||
activeOnColour: {
|
||||
if (type === TextButton.Text)
|
||||
return Colours.palette.m3primary;
|
||||
return type === TextButton.Filled ? Colours.palette.m3onPrimary : Colours.palette.m3onSecondary;
|
||||
}
|
||||
inactiveOnColour: {
|
||||
if (!isToggle && type === TextButton.Filled)
|
||||
return Colours.palette.m3onPrimary;
|
||||
if (type === TextButton.Text)
|
||||
return Colours.palette.m3primary;
|
||||
return type === TextButton.Filled ? Colours.palette.m3onSurface : Colours.palette.m3onSecondaryContainer;
|
||||
}
|
||||
|
||||
implicitWidth: row.implicitWidth + horizontalPadding * 2
|
||||
implicitHeight: row.implicitHeight + verticalPadding * 2
|
||||
|
|
@ -34,6 +51,11 @@ ButtonBase {
|
|||
Layout.topMargin: Math.round(fontInfo.pointSize * 0.0575)
|
||||
color: root.onColour
|
||||
fill: root.internalChecked ? 1 : 0
|
||||
fontStyle: {
|
||||
const f = Qt.font(root.font);
|
||||
f.pointSize = Math.round(root.font.pointSize * 1.1);
|
||||
return f;
|
||||
}
|
||||
|
||||
Behavior on fill {
|
||||
Anim {
|
||||
|
|
@ -48,6 +70,7 @@ ButtonBase {
|
|||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.topMargin: -Math.round(iconLabel.fontInfo.pointSize * 0.0575)
|
||||
color: root.onColour
|
||||
font: root.font
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ MouseArea {
|
|||
|
||||
required property int index
|
||||
required property MenuItem modelData
|
||||
readonly property bool active: modelData === root.active
|
||||
readonly property bool active: modelData === root?.active
|
||||
|
||||
Layout.fillWidth: true
|
||||
implicitWidth: menuOptionRow.implicitWidth + Tokens.padding.medium * 2
|
||||
|
|
@ -125,8 +125,8 @@ MouseArea {
|
|||
radius: active ? Tokens.rounding.medium : Tokens.rounding.extraSmall
|
||||
topLeftRadius: index === 0 ? Tokens.rounding.medium : radius
|
||||
topRightRadius: index === 0 ? Tokens.rounding.medium : radius
|
||||
bottomLeftRadius: index === repeater.count - 1 ? Tokens.rounding.medium : radius
|
||||
bottomRightRadius: index === repeater.count - 1 ? Tokens.rounding.medium : radius
|
||||
bottomLeftRadius: index === repeater?.count - 1 ? Tokens.rounding.medium : radius
|
||||
bottomRightRadius: index === repeater?.count - 1 ? Tokens.rounding.medium : radius
|
||||
|
||||
color: Qt.alpha(Colours.palette.m3tertiaryContainer, active ? 1 : 0)
|
||||
|
||||
|
|
@ -159,21 +159,21 @@ MouseArea {
|
|||
|
||||
MaterialIcon {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
text: item.modelData.icon
|
||||
text: item.modelData?.icon ?? ""
|
||||
color: item.active ? Colours.palette.m3onTertiaryContainer : Colours.palette.m3onSurfaceVariant
|
||||
}
|
||||
|
||||
StyledText {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.fillWidth: true
|
||||
text: item.modelData.text
|
||||
text: item.modelData?.text ?? ""
|
||||
color: item.active ? Colours.palette.m3onTertiaryContainer : Colours.palette.m3onSurface
|
||||
}
|
||||
|
||||
Loader {
|
||||
asynchronous: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
active: item.modelData.trailingIcon.length > 0
|
||||
active: item.modelData?.trailingIcon.length > 0
|
||||
visible: active
|
||||
|
||||
sourceComponent: MaterialIcon {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ Slider {
|
|||
property bool animateWave
|
||||
property real waveFrequency: 6
|
||||
property int waveDuration: 1000
|
||||
property int radius: Tokens.rounding.medium
|
||||
|
||||
property color fgColour: enabled ? Colours.palette.m3primary : Qt.alpha(Colours.palette.m3onSurface, 0.38)
|
||||
property color bgColour: enabled ? Colours.palette.m3secondaryContainer : Qt.alpha(Colours.palette.m3onSurface, 0.1)
|
||||
|
|
@ -43,7 +44,7 @@ Slider {
|
|||
implicitHeight: parent.height * (parent.height <= 12 ? opacity : Math.min(opacity * 2, 1))
|
||||
opacity: Math.min(width, 12) / 12
|
||||
|
||||
radius: Tokens.rounding.medium
|
||||
radius: root.radius
|
||||
topLeftRadius: Tokens.rounding.extraSmall / 2
|
||||
bottomLeftRadius: Tokens.rounding.extraSmall / 2
|
||||
color: root.bgColour
|
||||
|
|
@ -71,9 +72,9 @@ Slider {
|
|||
|
||||
implicitWidth: 4
|
||||
implicitHeight: {
|
||||
const mult = parent.height <= 12 ? 3 : 1.2;
|
||||
const pressMult = parent.height <= 12 ? 4 : 1.5;
|
||||
return parent.height * (mouse.pressed ? pressMult : mult);
|
||||
const t = CUtils.clamp((parent.height - 12) / 16, 0, 1);
|
||||
const lerp = (a, b) => a + (b - a) * t;
|
||||
return parent.height * (mouse.pressed ? lerp(3.5, 1.5) : lerp(3, 1.2));
|
||||
}
|
||||
|
||||
radius: Tokens.rounding.full
|
||||
|
|
@ -103,7 +104,7 @@ Slider {
|
|||
implicitWidth: root.filledWidth
|
||||
implicitHeight: root.height
|
||||
|
||||
radius: Tokens.rounding.medium
|
||||
radius: root.radius
|
||||
topRightRadius: Tokens.rounding.extraSmall / 2
|
||||
bottomRightRadius: Tokens.rounding.extraSmall / 2
|
||||
color: root.fgColour
|
||||
|
|
|
|||
|
|
@ -50,7 +50,10 @@ LazyLoader {
|
|||
|
||||
implicitWidth: 1000
|
||||
implicitHeight: 600
|
||||
minimumSize.width: 400
|
||||
minimumSize.height: 300
|
||||
color: Colours.tPalette.m3surface
|
||||
surfaceFormat.opaque: false
|
||||
title: loader.title
|
||||
|
||||
onVisibleChanged: {
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ StyledRect {
|
|||
return "folder";
|
||||
}
|
||||
color: place.selected ? Colours.palette.m3onSecondaryContainer : Colours.palette.m3onSurface
|
||||
fontStyle: Tokens.font.icon.large
|
||||
fontStyle: Tokens.font.icon.medium
|
||||
fill: place.selected ? 1 : 0
|
||||
|
||||
Behavior on fill {
|
||||
|
|
@ -104,7 +104,7 @@ StyledRect {
|
|||
Layout.fillWidth: true
|
||||
text: place.modelData
|
||||
color: place.selected ? Colours.palette.m3onSecondaryContainer : Colours.palette.m3onSurface
|
||||
font: Tokens.font.body.medium
|
||||
font: Tokens.font.body.small
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,5 +4,5 @@ import Quickshell
|
|||
|
||||
Singleton {
|
||||
property int itemWidth: 103
|
||||
property int sidebarWidth: 200
|
||||
property int sidebarWidth: 230
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,10 +6,15 @@ Image {
|
|||
id: root
|
||||
|
||||
property bool hadPrevious
|
||||
property bool fadingOut
|
||||
property bool preventInit
|
||||
property int fadeOutAnim: Anim.FastEffects
|
||||
property int fadeInAnim: Anim.DefaultEffects
|
||||
property int fadeInLargeAnim: Anim.StandardLarge
|
||||
|
||||
function maybeStartInAnim(): void {
|
||||
if (!opacityInAnim.running && status === Image.Ready) {
|
||||
opacityInAnim.type = hadPrevious ? Anim.DefaultEffects : Anim.StandardLarge;
|
||||
if (!preventInit && !opacityInAnim.running && status === Image.Ready) {
|
||||
opacityInAnim.type = hadPrevious ? fadeInAnim : fadeInLargeAnim;
|
||||
opacityInAnim.start();
|
||||
}
|
||||
}
|
||||
|
|
@ -26,6 +31,7 @@ Image {
|
|||
opacity: 0
|
||||
|
||||
onStatusChanged: maybeStartInAnim()
|
||||
onPreventInitChanged: maybeStartInAnim()
|
||||
|
||||
Anim on opacity {
|
||||
id: opacityInAnim
|
||||
|
|
@ -36,11 +42,24 @@ Image {
|
|||
|
||||
Behavior on source {
|
||||
SequentialAnimation {
|
||||
ScriptAction {
|
||||
script: opacityInAnim.stop()
|
||||
}
|
||||
PropertyAction {
|
||||
target: root
|
||||
property: "fadingOut"
|
||||
value: true
|
||||
}
|
||||
Anim {
|
||||
target: root
|
||||
property: "opacity"
|
||||
to: 0
|
||||
type: Anim.FastEffects
|
||||
type: root.fadeOutAnim
|
||||
}
|
||||
PropertyAction {
|
||||
target: root
|
||||
property: "fadingOut"
|
||||
value: false
|
||||
}
|
||||
PropertyAction {
|
||||
target: root
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import Quickshell.Io
|
|||
import Caelestia
|
||||
import qs.components.misc
|
||||
import qs.services
|
||||
import qs.modules.controlcenter
|
||||
import qs.modules.nexus
|
||||
|
||||
Scope {
|
||||
id: root
|
||||
|
|
@ -15,8 +15,8 @@ Scope {
|
|||
// qmllint disable unresolved-type
|
||||
CustomShortcut {
|
||||
// qmllint enable unresolved-type
|
||||
name: "controlCenter"
|
||||
description: "Open control center"
|
||||
name: "nexus"
|
||||
description: "Open nexus"
|
||||
onPressed: WindowFactory.create()
|
||||
}
|
||||
|
||||
|
|
@ -133,7 +133,7 @@ Scope {
|
|||
WindowFactory.create();
|
||||
}
|
||||
|
||||
target: "controlCenter"
|
||||
target: "nexus"
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import Quickshell.Wayland
|
|||
import Caelestia.Config
|
||||
import qs.components
|
||||
import qs.services
|
||||
import qs.modules.controlcenter
|
||||
import qs.modules.nexus
|
||||
import qs.modules.windowinfo
|
||||
|
||||
Item {
|
||||
|
|
@ -18,7 +18,7 @@ Item {
|
|||
|
||||
readonly property alias content: content
|
||||
readonly property alias winfo: winfo
|
||||
readonly property alias controlCenter: controlCenter
|
||||
readonly property alias nexus: nexus
|
||||
|
||||
readonly property real nonAnimWidth: children.find(c => c.shouldBeActive)?.implicitWidth ?? content.implicitWidth
|
||||
readonly property real nonAnimHeight: children.find(c => c.shouldBeActive)?.implicitHeight ?? content.implicitHeight
|
||||
|
|
@ -128,15 +128,24 @@ Item {
|
|||
}
|
||||
|
||||
Comp {
|
||||
id: controlCenter
|
||||
id: nexus
|
||||
|
||||
shouldBeActive: root.detachedMode === "any"
|
||||
anchors.centerIn: parent
|
||||
|
||||
sourceComponent: ControlCenter {
|
||||
screen: root.screen
|
||||
active: root.queuedMode
|
||||
onClose: root.close()
|
||||
sourceComponent: StyledClippingRect {
|
||||
radius: Tokens.rounding.extraLarge
|
||||
implicitWidth: nexusInner.implicitWidth
|
||||
implicitHeight: nexusInner.implicitHeight
|
||||
|
||||
Nexus {
|
||||
id: nexusInner
|
||||
|
||||
anchors.fill: parent
|
||||
nState.screen: root.screen
|
||||
nState.currentPageIdx: ["appearance", "network", "bluetooth", "audio"].indexOf(root.queuedMode)
|
||||
onClose: root.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -202,7 +202,6 @@ Item {
|
|||
anchors.verticalCenterOffset: Math.round(fontInfo.pointSize * 0.1)
|
||||
|
||||
text: "up " + SysInfo.uptime.split(",").slice(0, 2).join(",") // Max 2 components
|
||||
font: Tokens.font.label.medium
|
||||
width: Tokens.sizes.dashboard.userWidth - x - Tokens.padding.extraLarge
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ Item {
|
|||
id: blobGroup
|
||||
|
||||
color: Colours.palette.m3surfaceContainerHighest
|
||||
smoothing: Tokens.rounding.medium
|
||||
smoothing: root.Tokens.rounding.medium
|
||||
cornerFill: false
|
||||
|
||||
Behavior on color {
|
||||
|
|
|
|||
|
|
@ -39,6 +39,8 @@ StyledWindow {
|
|||
readonly property real shadowOpacity: 0.7 * (1 - fsTransitionProg)
|
||||
readonly property real borderLayoutThickness: hasFullscreen ? 0 : contentItem.Config.border.thickness
|
||||
|
||||
property color surfaceColour: Colours.tPalette.m3surface
|
||||
|
||||
readonly property int dragMaskPadding: {
|
||||
if (focusGrab.active || panels.popouts.isDetached)
|
||||
return 0;
|
||||
|
|
@ -76,6 +78,10 @@ StyledWindow {
|
|||
Anim {}
|
||||
}
|
||||
|
||||
Behavior on surfaceColour {
|
||||
CAnim {}
|
||||
}
|
||||
|
||||
Region {
|
||||
id: emptyRegion
|
||||
|
||||
|
|
@ -129,7 +135,7 @@ StyledWindow {
|
|||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
opacity: Colours.transparency.enabled ? Colours.transparency.base : 1
|
||||
opacity: root.surfaceColour.a
|
||||
layer.enabled: true
|
||||
layer.effect: MultiEffect {
|
||||
shadowEnabled: true
|
||||
|
|
@ -140,12 +146,8 @@ StyledWindow {
|
|||
BlobGroup {
|
||||
id: blobGroup
|
||||
|
||||
color: Colours.palette.m3surface
|
||||
color: root.surfaceColour
|
||||
smoothing: root.contentItem.Config.border.smoothing
|
||||
|
||||
Behavior on color {
|
||||
CAnim {}
|
||||
}
|
||||
}
|
||||
|
||||
BlobInvertedRect {
|
||||
|
|
|
|||
|
|
@ -34,8 +34,8 @@ ColumnLayout {
|
|||
|
||||
ProfilePic {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.topMargin: Tokens.spacing.largeIncreased
|
||||
Layout.bottomMargin: Tokens.spacing.medium
|
||||
Layout.topMargin: Tokens.spacing.extraExtraLarge * root.centerScale
|
||||
Layout.bottomMargin: Tokens.spacing.extraLarge * root.centerScale
|
||||
centerWidth: root.centerWidth
|
||||
}
|
||||
|
||||
|
|
|
|||
26
modules/nexus/NavPane.qml
Normal file
26
modules/nexus/NavPane.qml
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import "navpane"
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Caelestia.Config
|
||||
import qs.modules.nexus
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
required property NexusState nState
|
||||
|
||||
spacing: Tokens.spacing.large
|
||||
|
||||
SearchBar {
|
||||
Layout.fillWidth: true
|
||||
nState: root.nState
|
||||
}
|
||||
|
||||
NavLocations {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.topMargin: -topMargin
|
||||
Layout.bottomMargin: -bottomMargin
|
||||
nState: root.nState
|
||||
}
|
||||
}
|
||||
109
modules/nexus/Nexus.qml
Normal file
109
modules/nexus/Nexus.qml
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Caelestia.Blobs
|
||||
import Caelestia.Config
|
||||
import qs.components
|
||||
import qs.components.controls
|
||||
import qs.services
|
||||
import qs.modules.nexus
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
readonly property NexusState nState: NexusState {
|
||||
id: nState
|
||||
|
||||
onClose: root.close()
|
||||
}
|
||||
property color blobColour: Colours.tPalette.m3surfaceContainerLow
|
||||
|
||||
signal close
|
||||
|
||||
implicitWidth: implicitHeight * Tokens.sizes.nexus.ratio
|
||||
implicitHeight: nState.screen.height * Tokens.sizes.nexus.heightMult
|
||||
|
||||
Behavior on blobColour {
|
||||
CAnim {}
|
||||
}
|
||||
|
||||
BlobGroup {
|
||||
id: blobGroup
|
||||
|
||||
smoothing: root.Tokens.rounding.medium
|
||||
color: root.blobColour
|
||||
}
|
||||
|
||||
BlobInvertedRect {
|
||||
anchors.fill: parent
|
||||
group: blobGroup
|
||||
opacity: root.blobColour.a
|
||||
radius: Tokens.rounding.large
|
||||
|
||||
borderLeft: navPane.width + navPane.anchors.margins * 2
|
||||
borderRight: Tokens.padding.medium
|
||||
borderTop: Tokens.padding.medium
|
||||
borderBottom: Tokens.padding.medium
|
||||
}
|
||||
|
||||
BlobRect {
|
||||
id: windowBtnRect
|
||||
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.margins: root.nState.isWindow ? 0 : Tokens.padding.extraSmall
|
||||
|
||||
group: blobGroup
|
||||
opacity: root.blobColour.a
|
||||
radius: Tokens.rounding.medium
|
||||
|
||||
implicitWidth: windowBtn.implicitWidth + (root.nState.isWindow ? Tokens.padding.extraSmall : Tokens.padding.small) * 2
|
||||
implicitHeight: windowBtn.implicitHeight + (root.nState.isWindow ? Tokens.padding.extraSmall : Tokens.padding.small)
|
||||
}
|
||||
|
||||
IconButton {
|
||||
id: windowBtn
|
||||
|
||||
anchors.centerIn: windowBtnRect
|
||||
icon: nState.isWindow ? "close" : "pip"
|
||||
type: IconButton.Text
|
||||
label.fill: 0
|
||||
inactiveOnColour: hovered ? nState.isWindow ? Colours.palette.m3error : Colours.palette.m3primary : Colours.palette.m3onSurfaceVariant
|
||||
stateLayer.opacity: 0
|
||||
onClicked: {
|
||||
if (!nState.isWindow)
|
||||
WindowFactory.create();
|
||||
root.close();
|
||||
}
|
||||
|
||||
label.scale: pressed ? 0.8 : 1
|
||||
label.renderType: Text.QtRendering
|
||||
|
||||
Behavior on label.scale {
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
|
||||
NavPane {
|
||||
id: navPane
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.margins: Tokens.padding.large
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
30
modules/nexus/NexusState.qml
Normal file
30
modules/nexus/NexusState.qml
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Bluetooth
|
||||
|
||||
QtObject {
|
||||
property ShellScreen screen
|
||||
property bool isWindow
|
||||
property int currentPageIdx
|
||||
property list<int> subPageIdxStack
|
||||
property bool searchOpen
|
||||
|
||||
property string selectedWallpaperCategory
|
||||
property BluetoothDevice selectedBtDevice
|
||||
|
||||
signal close
|
||||
signal subPageOpened(idx: int)
|
||||
signal subPageClosed
|
||||
|
||||
function openSubPage(idx: int): void {
|
||||
subPageIdxStack.push(idx);
|
||||
subPageOpened(idx);
|
||||
}
|
||||
|
||||
function closeSubPage(): void {
|
||||
subPageClosed();
|
||||
subPageIdxStack.pop();
|
||||
}
|
||||
|
||||
onCurrentPageIdxChanged: subPageIdxStack.length = 0
|
||||
}
|
||||
180
modules/nexus/PageCompRegistry.qml
Normal file
180
modules/nexus/PageCompRegistry.qml
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
pragma Singleton
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Caelestia.Config
|
||||
import qs.components
|
||||
import qs.services
|
||||
import qs.modules.nexus.common
|
||||
import qs.modules.nexus.pages
|
||||
import qs.modules.nexus.pages.audio
|
||||
import qs.modules.nexus.pages.bluetooth
|
||||
import qs.modules.nexus.pages.panels
|
||||
import qs.modules.nexus.pages.wallandstyle
|
||||
import qs.modules.nexus.pages.panels.taskbar
|
||||
|
||||
QtObject {
|
||||
id: root
|
||||
|
||||
readonly property list<Component> pageComps: [
|
||||
// Appearance
|
||||
Component {
|
||||
// Wallpaper & style
|
||||
StackPage {
|
||||
Component {
|
||||
WallpaperAndStyle {}
|
||||
}
|
||||
Component {
|
||||
WallpaperSelect {}
|
||||
}
|
||||
Component {
|
||||
WallpaperCategory {}
|
||||
}
|
||||
Component {
|
||||
ColourSelect {}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Connectivity
|
||||
Component {
|
||||
// Network
|
||||
StackPage {
|
||||
Component {
|
||||
NetworkPage {}
|
||||
}
|
||||
}
|
||||
},
|
||||
Component {
|
||||
// Bluetooth
|
||||
StackPage {
|
||||
Component {
|
||||
BluetoothPage {}
|
||||
}
|
||||
Component {
|
||||
BtDeviceInfo {}
|
||||
}
|
||||
Component {
|
||||
BluetoothPairing {}
|
||||
}
|
||||
}
|
||||
},
|
||||
Component {
|
||||
// Audio
|
||||
StackPage {
|
||||
Component {
|
||||
AudioPage {}
|
||||
}
|
||||
Component {
|
||||
AppVolumes {}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// System
|
||||
Component {
|
||||
PlaceholderComp {}
|
||||
},
|
||||
Component {
|
||||
PlaceholderComp {}
|
||||
},
|
||||
|
||||
// Shell
|
||||
Component {
|
||||
// Panels
|
||||
StackPage {
|
||||
Component {
|
||||
PanelsPage {}
|
||||
}
|
||||
Component {
|
||||
DashboardPanel {}
|
||||
}
|
||||
Component {
|
||||
TaskbarPanel {}
|
||||
}
|
||||
Component {
|
||||
LauncherPanel {}
|
||||
}
|
||||
Component {
|
||||
SidebarPanel {}
|
||||
}
|
||||
|
||||
// Taskbar component sub-pages
|
||||
Component {
|
||||
BarWorkspaces {}
|
||||
}
|
||||
Component {
|
||||
BarActiveWindow {}
|
||||
}
|
||||
Component {
|
||||
BarTray {}
|
||||
}
|
||||
Component {
|
||||
BarStatusIcons {}
|
||||
}
|
||||
Component {
|
||||
BarClock {}
|
||||
}
|
||||
}
|
||||
},
|
||||
Component {
|
||||
// Services
|
||||
StackPage {
|
||||
Component {
|
||||
ServicesPage {}
|
||||
}
|
||||
}
|
||||
},
|
||||
Component {
|
||||
// Language & region
|
||||
StackPage {
|
||||
Component {
|
||||
LanguageAndRegion {}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// About
|
||||
Component {
|
||||
StackPage {
|
||||
Component {
|
||||
AboutPage {}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
readonly property Component placeholderComp: Component {
|
||||
PlaceholderComp {}
|
||||
}
|
||||
|
||||
component PlaceholderComp: Item {
|
||||
property NexusState nState // To avoid the warning from non-existent property
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
StyledText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: qsTr("This page will be available in a future update.")
|
||||
color: Colours.palette.m3outlineVariant
|
||||
font: Tokens.font.body.large
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
87
modules/nexus/PageRegistry.qml
Normal file
87
modules/nexus/PageRegistry.qml
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
pragma Singleton
|
||||
|
||||
import QtQuick
|
||||
|
||||
QtObject {
|
||||
id: root
|
||||
|
||||
readonly property list<var> pages: [
|
||||
// Appearance
|
||||
{
|
||||
label: qsTr("Wallpaper & style"),
|
||||
icon: "palette",
|
||||
description: qsTr("Wallpaper, fonts, colours"),
|
||||
category: "appearance"
|
||||
},
|
||||
|
||||
// Connectivity
|
||||
// TODO
|
||||
// {
|
||||
// label: qsTr("Display"),
|
||||
// icon: "monitor",
|
||||
// description: qsTr("Output configuration"),
|
||||
// category: "connectivity"
|
||||
// },
|
||||
{
|
||||
label: qsTr("Network"),
|
||||
icon: "wifi",
|
||||
description: qsTr("Wi-Fi, ethernet"),
|
||||
category: "connectivity"
|
||||
},
|
||||
{
|
||||
label: qsTr("Connected devices"),
|
||||
icon: "devices_other",
|
||||
description: qsTr("Bluetooth, pairing"),
|
||||
category: "connectivity",
|
||||
noFill: true
|
||||
},
|
||||
{
|
||||
label: qsTr("Audio"),
|
||||
icon: "volume_up",
|
||||
description: qsTr("App volumes, sound devices"),
|
||||
category: "connectivity"
|
||||
},
|
||||
|
||||
// System
|
||||
{
|
||||
label: qsTr("Updates"),
|
||||
icon: "update",
|
||||
description: qsTr("System updates"),
|
||||
category: "system"
|
||||
},
|
||||
{
|
||||
label: qsTr("Plugins"),
|
||||
icon: "extension",
|
||||
description: qsTr("Manage plugins"),
|
||||
category: "system"
|
||||
},
|
||||
|
||||
// Shell
|
||||
{
|
||||
label: qsTr("Panels"),
|
||||
icon: "dock_to_bottom",
|
||||
description: qsTr("Dashboard, taskbar, launcher, sidebar"),
|
||||
category: "shell"
|
||||
},
|
||||
{
|
||||
label: qsTr("Services"),
|
||||
icon: "build",
|
||||
description: qsTr("Poll intervals, lyrics backend"),
|
||||
category: "shell"
|
||||
},
|
||||
{
|
||||
label: qsTr("Language & region"),
|
||||
icon: "globe",
|
||||
description: qsTr("UI language, weather location, display units"),
|
||||
category: "shell"
|
||||
},
|
||||
|
||||
// About
|
||||
{
|
||||
label: qsTr("About"),
|
||||
icon: "info",
|
||||
description: qsTr("System information, credits"),
|
||||
category: "about"
|
||||
},
|
||||
]
|
||||
}
|
||||
95
modules/nexus/Pages.qml
Normal file
95
modules/nexus/Pages.qml
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
import QtQuick
|
||||
import Caelestia.Config
|
||||
import qs.components
|
||||
import qs.modules.nexus
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property NexusState nState
|
||||
|
||||
property int lastPageIdx
|
||||
property int animOff
|
||||
property Item currentItem
|
||||
|
||||
function loadPage(idx: int): void {
|
||||
if (currentItem)
|
||||
currentItem.destroy();
|
||||
|
||||
const comp = PageCompRegistry.pageComps[idx] ?? PageCompRegistry.placeholderComp;
|
||||
const incubator = comp.incubateObject(container, {
|
||||
nState
|
||||
});
|
||||
|
||||
const attach = () => {
|
||||
incubator.object.anchors.fill = container;
|
||||
currentItem = incubator.object;
|
||||
};
|
||||
|
||||
if (incubator.status === Component.Ready)
|
||||
attach();
|
||||
else
|
||||
incubator.onStatusChanged = status => {
|
||||
if (status === Component.Ready)
|
||||
attach();
|
||||
};
|
||||
}
|
||||
|
||||
Item {
|
||||
id: container
|
||||
|
||||
anchors.fill: parent
|
||||
layer.enabled: opacity < 1
|
||||
Component.onCompleted: root.loadPage(root.nState.currentPageIdx)
|
||||
}
|
||||
|
||||
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: container
|
||||
property: "opacity"
|
||||
to: 0
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
ScriptAction {
|
||||
script: root.loadPage(root.nState.currentPageIdx)
|
||||
}
|
||||
PropertyAction {
|
||||
target: container.anchors
|
||||
property: "topMargin"
|
||||
value: root.animOff
|
||||
}
|
||||
PropertyAction {
|
||||
target: container.anchors
|
||||
property: "bottomMargin"
|
||||
value: -root.animOff
|
||||
}
|
||||
ParallelAnimation {
|
||||
Anim {
|
||||
target: container
|
||||
property: "opacity"
|
||||
from: 0
|
||||
to: 1
|
||||
type: Anim.SlowEffects
|
||||
}
|
||||
Anim {
|
||||
target: container.anchors
|
||||
properties: "topMargin,bottomMargin"
|
||||
to: 0
|
||||
type: Anim.SlowEffects
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
60
modules/nexus/WindowFactory.qml
Normal file
60
modules/nexus/WindowFactory.qml
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
pragma Singleton
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Caelestia.Config
|
||||
import qs.components
|
||||
import qs.services
|
||||
import qs.modules.nexus
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
function create(parent: Item, props: var): void {
|
||||
nexusComp.createObject(parent ?? dummy, props);
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: dummy
|
||||
}
|
||||
|
||||
Component {
|
||||
id: nexusComp
|
||||
|
||||
FloatingWindow {
|
||||
id: win
|
||||
|
||||
color: Colours.tPalette.m3surface
|
||||
surfaceFormat.opaque: false
|
||||
|
||||
onVisibleChanged: {
|
||||
if (!visible)
|
||||
destroy();
|
||||
}
|
||||
|
||||
implicitWidth: nexus.implicitWidth
|
||||
implicitHeight: nexus.implicitHeight
|
||||
|
||||
minimumSize.width: contentItem.Tokens.sizes.nexus.minWidth
|
||||
minimumSize.height: contentItem.Tokens.sizes.nexus.minHeight
|
||||
|
||||
contentItem.Config.screen: screen.name
|
||||
contentItem.Tokens.screen: screen.name
|
||||
|
||||
title: qsTr("Nexus — %1").arg(PageRegistry.pages[nexus.nState.currentPageIdx].label)
|
||||
|
||||
Nexus {
|
||||
id: nexus
|
||||
|
||||
anchors.fill: parent
|
||||
nState.screen: win.screen
|
||||
nState.isWindow: true
|
||||
onClose: win.destroy()
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
CAnim {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
513
modules/nexus/common/AnimatedLogo.qml
Normal file
513
modules/nexus/common/AnimatedLogo.qml
Normal file
|
|
@ -0,0 +1,513 @@
|
|||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Shapes
|
||||
import qs.components
|
||||
import qs.services
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property real blurAmount: skipIntroAnimation ? 0.0 : 1.0
|
||||
property bool skipIntroAnimation: false
|
||||
|
||||
property real star1Opacity: skipIntroAnimation ? 1.0 : 0.0
|
||||
property real star2Opacity: skipIntroAnimation ? 1.0 : 0.0
|
||||
property real star3Opacity: skipIntroAnimation ? 1.0 : 0.0
|
||||
|
||||
property real star1Scale: skipIntroAnimation ? 1.0 : 0.0
|
||||
property real star2Scale: skipIntroAnimation ? 1.0 : 0.0
|
||||
property real star3Scale: skipIntroAnimation ? 1.0 : 0.0
|
||||
|
||||
readonly property alias topShape: topShape
|
||||
readonly property alias bottomShape: bottomShape
|
||||
readonly property alias star1: star1
|
||||
readonly property alias star2: star2
|
||||
readonly property alias star3: star3
|
||||
|
||||
signal animationCompleted
|
||||
|
||||
implicitWidth: 128
|
||||
implicitHeight: 90.38
|
||||
|
||||
Item {
|
||||
id: logo
|
||||
|
||||
readonly property real designWidth: 128
|
||||
readonly property real designHeight: 90.38
|
||||
|
||||
property color topColour: Colours.palette.m3primary
|
||||
property color bottomColour: Colours.palette.m3onSurface
|
||||
|
||||
implicitWidth: designWidth
|
||||
implicitHeight: designHeight
|
||||
|
||||
transformOrigin: Item.Center
|
||||
scale: root.skipIntroAnimation ? 1.0 : 0.0
|
||||
opacity: root.skipIntroAnimation ? 1.0 : 0.0
|
||||
rotation: 0.0
|
||||
|
||||
layer.enabled: root.blurAmount > 0
|
||||
layer.effect: MultiEffect {
|
||||
blurEnabled: true
|
||||
blur: root.blurAmount
|
||||
blurMax: 60
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
root.star1.opacity = Qt.binding(() => root.star1Opacity);
|
||||
root.star1.scale = Qt.binding(() => root.star1Scale);
|
||||
root.star2.opacity = Qt.binding(() => root.star2Opacity);
|
||||
root.star2.scale = Qt.binding(() => root.star2Scale);
|
||||
root.star3.opacity = Qt.binding(() => root.star3Opacity);
|
||||
root.star3.scale = Qt.binding(() => root.star3Scale);
|
||||
}
|
||||
|
||||
Behavior on topColour {
|
||||
CAnim {}
|
||||
}
|
||||
|
||||
Behavior on bottomColour {
|
||||
CAnim {}
|
||||
}
|
||||
|
||||
Shape {
|
||||
id: topShape
|
||||
|
||||
anchors.centerIn: parent
|
||||
width: logo.designWidth
|
||||
height: logo.designHeight
|
||||
scale: Math.min(logo.width / width, logo.height / height)
|
||||
transformOrigin: Item.Center
|
||||
preferredRendererType: Shape.CurveRenderer
|
||||
|
||||
ShapePath {
|
||||
fillColor: logo.topColour
|
||||
strokeColor: "transparent"
|
||||
|
||||
PathSvg {
|
||||
path: "m42.56,42.96c-7.76,1.6-16.36,4.22-22.44,6.22-.49.16-.88-.44-.53-.82,5.37-5.85,9.66-13.3,9.66-13.3,8.66-14.67,22.97-23.51,39.85-21.14,6.47.91,12.33,3.38,17.26,6.98.99.72,1.14,2.14.31,3.04-.4.44-.95.67-1.51.67-.34,0-.69-.09-1-.26-3.21-1.84-6.82-2.69-10.71-3.24-13.1-1.84-25.41,4.75-31.06,15.83-.94,1.84-.61,3.81.45,5.21.22.3.07.72-.29.8Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Shape {
|
||||
id: bottomShape
|
||||
|
||||
anchors.fill: parent
|
||||
preferredRendererType: Shape.CurveRenderer
|
||||
|
||||
ShapePath {
|
||||
fillColor: logo.bottomColour
|
||||
strokeColor: "transparent"
|
||||
|
||||
PathSvg {
|
||||
path: "m103.02,51.8c-.65.11-1.26-.37-1.28-1.03-.06-1.96.15-3.89-.2-5.78-.28-1.48-1.66-2.5-3.16-2.34h-.05c-6.53.73-24.63,3.1-48,9.32-6.89,1.83-9.83,10-5.67,15.79,4.62,6.44,11.84,10.93,20.41,12.13,11.82,1.66,22.99-3.36,29.21-12.65.54-.81,1.54-1.17,2.47-.86.91.3,1.47,1.15,1.47,2.04,0,.33-.08.66-.24.98-7.23,14.21-22.91,22.95-39.59,20.6-7.84-1.1-14.8-4.5-20.28-9.43,0,0,0,0-.02-.01-7.28-5.14-14.7-9.99-27.24-11.98-18.82-2.98-9.53-8.75.46-13.78,7.36-3.13,25.17-7.9,36.24-10.73.16-.03.31-.06.47-.1,1.52-.4,3.2-.83,5.02-1.29,1.06-.26,1.93-.48,2.58-.64.09-.02.18-.04.26-.06.31-.08.56-.14.73-.18.03,0,.06-.01.08-.02.03,0,.05-.01.07-.02.02,0,.04,0,.06-.01.01,0,.03,0,.04-.01,0,0,.02,0,.03,0,.01,0,.02,0,.02,0,10.62-2.58,24.63-5.62,37.74-7.34,1.02-.13,2.03-.26,3.03-.37,7.49-.87,14.58-1.26,20.42-.81,25.43,1.95-4.71,16.77-15.12,18.61Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Shape {
|
||||
id: star1
|
||||
|
||||
anchors.fill: parent
|
||||
preferredRendererType: Shape.CurveRenderer
|
||||
opacity: 0.0
|
||||
|
||||
ShapePath {
|
||||
fillColor: logo.topColour
|
||||
strokeColor: "transparent"
|
||||
|
||||
PathSvg {
|
||||
path: "m98.12.06c-.29,2.08-1.72,8.42-8.36,9.19-.09,0-.09.13,0,.14,6.64.78,8.07,7.11,8.36,9.19.01.08.13.08.14,0,.29-2.08,1.72-8.42,8.36-9.19.09,0,.09-.13,0-.14-6.64-.78-8.07-7.11-8.36-9.19-.01-.08-.13-.08-.14,0Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Shape {
|
||||
id: star2
|
||||
|
||||
anchors.fill: parent
|
||||
preferredRendererType: Shape.CurveRenderer
|
||||
opacity: 0.0
|
||||
|
||||
ShapePath {
|
||||
fillColor: logo.topColour
|
||||
strokeColor: "transparent"
|
||||
|
||||
PathSvg {
|
||||
path: "m113.36,15.5c-.22,1.29-1.08,4.35-4.38,4.87-.08.01-.08.13,0,.14,3.3.52,4.17,3.58,4.38,4.87.01.08.13.08.14,0,.22-1.29,1.08-4.35,4.38-4.87.08-.01.08-.13,0-.14-3.3-.52-4.17-3.58-4.38-4.87-.01-.08-.13-.08-.14,0Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Shape {
|
||||
id: star3
|
||||
|
||||
anchors.fill: parent
|
||||
preferredRendererType: Shape.CurveRenderer
|
||||
opacity: 0.0
|
||||
|
||||
ShapePath {
|
||||
fillColor: logo.topColour
|
||||
strokeColor: "transparent"
|
||||
|
||||
PathSvg {
|
||||
path: "m112.69,65.22c-.19,1.01-.86,3.15-3.2,3.57-.08.01-.08.13,0,.14,2.34.42,3.01,2.56,3.2,3.57.01.08.13.08.14,0,.19-1.01.86-3.15,3.2-3.57.08-.01.08-.13,0-.14-2.34-.42-3.01-2.56-3.2-3.57-.01-.08-.13-.08-.14,0Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SequentialAnimation {
|
||||
running: !root.skipIntroAnimation
|
||||
onFinished: root.animationCompleted()
|
||||
|
||||
ParallelAnimation {
|
||||
SequentialAnimation {
|
||||
NumberAnimation {
|
||||
target: logo
|
||||
property: "rotation"
|
||||
from: 0
|
||||
to: 750
|
||||
duration: 1000
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
target: logo
|
||||
property: "rotation"
|
||||
from: 750
|
||||
to: 710
|
||||
duration: 300
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
target: logo
|
||||
property: "rotation"
|
||||
from: 710
|
||||
to: 725
|
||||
duration: 350
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
target: logo
|
||||
property: "rotation"
|
||||
from: 725
|
||||
to: 720
|
||||
duration: 250
|
||||
easing.type: Easing.OutQuad
|
||||
}
|
||||
|
||||
ScriptAction {
|
||||
script: logo.rotation = 0
|
||||
}
|
||||
}
|
||||
|
||||
SequentialAnimation {
|
||||
NumberAnimation {
|
||||
target: logo
|
||||
property: "scale"
|
||||
from: 0.0
|
||||
to: 1.08
|
||||
duration: 1000
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
target: logo
|
||||
property: "scale"
|
||||
from: 1.08
|
||||
to: 0.96
|
||||
duration: 200
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
target: logo
|
||||
property: "scale"
|
||||
from: 0.96
|
||||
to: 1.0
|
||||
duration: 250
|
||||
easing.type: Easing.OutBack
|
||||
easing.overshoot: 1.05
|
||||
}
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
target: logo
|
||||
property: "opacity"
|
||||
from: 0.0
|
||||
to: 1.0
|
||||
duration: 600
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
target: root
|
||||
property: "blurAmount"
|
||||
from: 1.0
|
||||
to: 0.0
|
||||
duration: 900
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
|
||||
SequentialAnimation {
|
||||
PauseAnimation {
|
||||
duration: 1100
|
||||
}
|
||||
|
||||
ParallelAnimation {
|
||||
NumberAnimation {
|
||||
target: root
|
||||
property: "star1Opacity"
|
||||
from: 0.0
|
||||
to: 1.0
|
||||
duration: 700
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
|
||||
SequentialAnimation {
|
||||
NumberAnimation {
|
||||
target: root
|
||||
property: "star1Scale"
|
||||
from: 0.0
|
||||
to: 1.08
|
||||
duration: 500
|
||||
easing.type: Easing.OutQuad
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
target: root
|
||||
property: "star1Scale"
|
||||
from: 1.08
|
||||
to: 1.0
|
||||
duration: 400
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SequentialAnimation {
|
||||
PauseAnimation {
|
||||
duration: 1250
|
||||
}
|
||||
|
||||
ParallelAnimation {
|
||||
NumberAnimation {
|
||||
target: root
|
||||
property: "star2Opacity"
|
||||
from: 0.0
|
||||
to: 1.0
|
||||
duration: 700
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
|
||||
SequentialAnimation {
|
||||
NumberAnimation {
|
||||
target: root
|
||||
property: "star2Scale"
|
||||
from: 0.0
|
||||
to: 1.08
|
||||
duration: 500
|
||||
easing.type: Easing.OutQuad
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
target: root
|
||||
property: "star2Scale"
|
||||
from: 1.08
|
||||
to: 1.0
|
||||
duration: 400
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SequentialAnimation {
|
||||
PauseAnimation {
|
||||
duration: 1400
|
||||
}
|
||||
|
||||
ParallelAnimation {
|
||||
NumberAnimation {
|
||||
target: root
|
||||
property: "star3Opacity"
|
||||
from: 0.0
|
||||
to: 1.0
|
||||
duration: 700
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
|
||||
SequentialAnimation {
|
||||
NumberAnimation {
|
||||
target: root
|
||||
property: "star3Scale"
|
||||
from: 0.0
|
||||
to: 1.08
|
||||
duration: 500
|
||||
easing.type: Easing.OutQuad
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
target: root
|
||||
property: "star3Scale"
|
||||
from: 1.08
|
||||
to: 1.0
|
||||
duration: 400
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SequentialAnimation {
|
||||
running: true
|
||||
loops: Animation.Infinite
|
||||
|
||||
PauseAnimation {
|
||||
duration: 2500
|
||||
}
|
||||
|
||||
ParallelAnimation {
|
||||
SequentialAnimation {
|
||||
loops: Animation.Infinite
|
||||
|
||||
NumberAnimation {
|
||||
target: root.star1
|
||||
property: "y"
|
||||
from: root.star1.y
|
||||
to: root.star1.y - 5
|
||||
duration: 2500
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
target: root.star1
|
||||
property: "y"
|
||||
from: root.star1.y - 5
|
||||
to: root.star1.y
|
||||
duration: 2500
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
|
||||
SequentialAnimation {
|
||||
loops: Animation.Infinite
|
||||
|
||||
NumberAnimation {
|
||||
target: root.star2
|
||||
property: "y"
|
||||
from: root.star2.y
|
||||
to: root.star2.y + 5
|
||||
duration: 3000
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
target: root.star2
|
||||
property: "y"
|
||||
from: root.star2.y + 5
|
||||
to: root.star2.y
|
||||
duration: 3000
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
|
||||
SequentialAnimation {
|
||||
loops: Animation.Infinite
|
||||
|
||||
NumberAnimation {
|
||||
target: root.star3
|
||||
property: "y"
|
||||
from: root.star3.y
|
||||
to: root.star3.y - 5
|
||||
duration: 2800
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
target: root.star3
|
||||
property: "y"
|
||||
from: root.star3.y - 5
|
||||
to: root.star3.y
|
||||
duration: 2800
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
|
||||
SequentialAnimation {
|
||||
loops: Animation.Infinite
|
||||
|
||||
NumberAnimation {
|
||||
target: root.star1
|
||||
property: "scale"
|
||||
from: 1.0
|
||||
to: 1.08
|
||||
duration: 2500
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
target: root.star1
|
||||
property: "scale"
|
||||
from: 1.08
|
||||
to: 1.0
|
||||
duration: 2500
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
|
||||
SequentialAnimation {
|
||||
loops: Animation.Infinite
|
||||
|
||||
NumberAnimation {
|
||||
target: root.star2
|
||||
property: "scale"
|
||||
from: 1.0
|
||||
to: 1.12
|
||||
duration: 3000
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
target: root.star2
|
||||
property: "scale"
|
||||
from: 1.12
|
||||
to: 1.0
|
||||
duration: 3000
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
|
||||
SequentialAnimation {
|
||||
loops: Animation.Infinite
|
||||
|
||||
NumberAnimation {
|
||||
target: root.star3
|
||||
property: "scale"
|
||||
from: 1.0
|
||||
to: 1.08
|
||||
duration: 2800
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
target: root.star3
|
||||
property: "scale"
|
||||
from: 1.08
|
||||
to: 1.0
|
||||
duration: 2800
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
97
modules/nexus/common/AudioDeviceList.qml
Normal file
97
modules/nexus/common/AudioDeviceList.qml
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Services.Pipewire
|
||||
import Caelestia.Config
|
||||
import qs.components
|
||||
import qs.services
|
||||
import qs.modules.nexus.common
|
||||
|
||||
ItemList {
|
||||
id: root
|
||||
|
||||
property var nodes: []
|
||||
property int currentId: -1
|
||||
property string iconName: "speaker"
|
||||
|
||||
signal selected(node: PwNode)
|
||||
|
||||
last: true
|
||||
showList: true
|
||||
|
||||
model: ScriptModel {
|
||||
values: [...root.nodes].sort((a, b) => (a.description || a.name || "").localeCompare(b.description || b.name || ""))
|
||||
}
|
||||
|
||||
delegate: Item {
|
||||
id: device
|
||||
|
||||
required property PwNode modelData
|
||||
required property int index
|
||||
readonly property bool active: device.modelData?.id === root.currentId
|
||||
|
||||
anchors.left: root.list.contentItem.left
|
||||
anchors.right: root.list.contentItem.right
|
||||
implicitHeight: deviceLayout.implicitHeight + deviceLayout.anchors.margins * 2
|
||||
|
||||
StateLayer {
|
||||
radius: Tokens.rounding.extraSmall
|
||||
bottomLeftRadius: device.index === root?.list.count - 1 ? Tokens.rounding.extraLarge : radius
|
||||
bottomRightRadius: device.index === root?.list.count - 1 ? Tokens.rounding.extraLarge : radius
|
||||
onClicked: root.selected(device.modelData)
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: deviceLayout
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Tokens.padding.medium
|
||||
anchors.leftMargin: Tokens.padding.largeIncreased
|
||||
anchors.rightMargin: Tokens.padding.largeIncreased
|
||||
spacing: Tokens.spacing.medium
|
||||
|
||||
StyledRect {
|
||||
implicitWidth: implicitHeight
|
||||
implicitHeight: devIcon.implicitHeight + Tokens.padding.small * 2
|
||||
radius: Tokens.rounding.full
|
||||
color: device.active ? Colours.palette.m3primary : Colours.palette.m3secondaryContainer
|
||||
|
||||
MaterialIcon {
|
||||
id: devIcon
|
||||
|
||||
anchors.centerIn: parent
|
||||
text: root.iconName
|
||||
color: device.active ? Colours.palette.m3onPrimary : Colours.palette.m3onSecondaryContainer
|
||||
font: Tokens.font.icon.medium
|
||||
fill: device.active ? 1 : 0
|
||||
|
||||
Behavior on fill {
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
Layout.fillWidth: true
|
||||
text: device.modelData?.description || device.modelData?.name || qsTr("Unknown")
|
||||
font: Tokens.font.body.small
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
text: "check"
|
||||
color: Colours.palette.m3primary
|
||||
font: Tokens.font.icon.medium
|
||||
opacity: device.active ? 1 : 0
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
15
modules/nexus/common/ConnectedRect.qml
Normal file
15
modules/nexus/common/ConnectedRect.qml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import QtQuick
|
||||
import Caelestia.Config
|
||||
import qs.components
|
||||
import qs.services
|
||||
|
||||
StyledRect {
|
||||
property bool first
|
||||
property bool last
|
||||
|
||||
color: Colours.tPalette.m3surfaceContainer
|
||||
topLeftRadius: first ? Tokens.rounding.extraLarge : Tokens.rounding.extraSmall
|
||||
topRightRadius: first ? Tokens.rounding.extraLarge : Tokens.rounding.extraSmall
|
||||
bottomLeftRadius: last ? Tokens.rounding.extraLarge : Tokens.rounding.extraSmall
|
||||
bottomRightRadius: last ? Tokens.rounding.extraLarge : Tokens.rounding.extraSmall
|
||||
}
|
||||
59
modules/nexus/common/InfoRow.qml
Normal file
59
modules/nexus/common/InfoRow.qml
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Caelestia.Config
|
||||
import qs.components
|
||||
import qs.services
|
||||
import qs.modules.nexus.common
|
||||
|
||||
ConnectedRect {
|
||||
id: root
|
||||
|
||||
property alias label: label.text
|
||||
property string subtext
|
||||
property alias value: value.text
|
||||
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: rowLayout.implicitHeight + rowLayout.anchors.margins * 2
|
||||
|
||||
RowLayout {
|
||||
id: rowLayout
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Tokens.padding.medium
|
||||
anchors.leftMargin: Tokens.padding.largeIncreased
|
||||
anchors.rightMargin: Tokens.padding.largeIncreased
|
||||
spacing: Tokens.spacing.medium
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
|
||||
StyledText {
|
||||
id: label
|
||||
|
||||
Layout.fillWidth: true
|
||||
font: Tokens.font.body.small
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
StyledText {
|
||||
Layout.fillWidth: true
|
||||
visible: root.subtext
|
||||
text: root.subtext
|
||||
color: Colours.palette.m3outline
|
||||
font: Tokens.font.label.small
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: value
|
||||
|
||||
Layout.maximumWidth: root.width / 2
|
||||
horizontalAlignment: Text.AlignRight
|
||||
color: Colours.palette.m3onSurfaceVariant
|
||||
font: Tokens.font.body.small
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
}
|
||||
121
modules/nexus/common/ItemList.qml
Normal file
121
modules/nexus/common/ItemList.qml
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Caelestia.Config
|
||||
import qs.components
|
||||
import qs.services
|
||||
|
||||
ConnectedRect {
|
||||
id: root
|
||||
|
||||
property bool showList
|
||||
property string placeholderIcon
|
||||
property string placeholderText
|
||||
property int extraHeight
|
||||
|
||||
property alias model: list.model
|
||||
property alias delegate: list.delegate
|
||||
readonly property alias list: list
|
||||
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: (showList && list.count > 0 ? list.contentHeight : placeholder.implicitHeight + Tokens.padding.extraLarge * 2) + extraHeight
|
||||
color: Colours.tPalette.m3surfaceContainer
|
||||
clip: true
|
||||
|
||||
Behavior on implicitHeight {
|
||||
Anim {}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: placeholder
|
||||
|
||||
anchors.centerIn: parent
|
||||
active: opacity > 0
|
||||
opacity: root.showList && list.count > 0 ? 0 : 1
|
||||
|
||||
sourceComponent: ColumnLayout {
|
||||
spacing: Tokens.spacing.extraSmall
|
||||
|
||||
MaterialIcon {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: root.placeholderIcon
|
||||
color: Colours.palette.m3outline
|
||||
font: Tokens.font.icon.large
|
||||
animate: true
|
||||
}
|
||||
|
||||
StyledText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: root.placeholderText
|
||||
color: Colours.palette.m3outline
|
||||
font: Tokens.font.body.large
|
||||
animate: true
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: list
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
spacing: 0
|
||||
interactive: false
|
||||
opacity: root.showList ? 1 : 0
|
||||
|
||||
add: Transition {
|
||||
Anim {
|
||||
property: "opacity"
|
||||
from: 0
|
||||
to: 1
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
|
||||
remove: Transition {
|
||||
Anim {
|
||||
property: "opacity"
|
||||
to: 0
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
|
||||
move: Transition {
|
||||
Anim {
|
||||
property: "opacity"
|
||||
to: 1
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
Anim {
|
||||
property: "y"
|
||||
}
|
||||
}
|
||||
|
||||
displaced: Transition {
|
||||
Anim {
|
||||
property: "opacity"
|
||||
to: 1
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
Anim {
|
||||
property: "y"
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
72
modules/nexus/common/NavRow.qml
Normal file
72
modules/nexus/common/NavRow.qml
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Caelestia.Config
|
||||
import qs.components
|
||||
import qs.services
|
||||
import qs.modules.nexus.common
|
||||
|
||||
ConnectedRect {
|
||||
id: root
|
||||
|
||||
property alias icon: icon.text
|
||||
property alias label: label.text
|
||||
property alias status: status.text
|
||||
|
||||
signal clicked
|
||||
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: navLayout.implicitHeight + navLayout.anchors.margins * 2
|
||||
|
||||
StateLayer {
|
||||
onClicked: root.clicked()
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: navLayout
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Tokens.padding.medium
|
||||
anchors.leftMargin: Tokens.padding.largeIncreased
|
||||
anchors.rightMargin: Tokens.padding.largeIncreased
|
||||
spacing: Tokens.spacing.medium
|
||||
|
||||
MaterialIcon {
|
||||
id: icon
|
||||
|
||||
color: Colours.palette.m3onSurfaceVariant
|
||||
font: Tokens.font.icon.medium
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
|
||||
StyledText {
|
||||
id: label
|
||||
|
||||
Layout.fillWidth: true
|
||||
font: Tokens.font.body.small
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: status
|
||||
|
||||
Layout.fillWidth: true
|
||||
visible: text
|
||||
color: Colours.palette.m3outline
|
||||
font: Tokens.font.label.small
|
||||
elide: Text.ElideRight
|
||||
animate: true
|
||||
}
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
text: "chevron_right"
|
||||
color: Colours.palette.m3onSurfaceVariant
|
||||
font: Tokens.font.icon.medium
|
||||
}
|
||||
}
|
||||
}
|
||||
61
modules/nexus/common/PageBase.qml
Normal file
61
modules/nexus/common/PageBase.qml
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
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
|
||||
|
||||
spacing: Tokens.spacing.extraLargeIncreased
|
||||
|
||||
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.m3onSurfaceVariant
|
||||
onClicked: root.nState.closeSubPage()
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: root.title
|
||||
font: Tokens.font.title.large
|
||||
}
|
||||
}
|
||||
|
||||
VerticalFadeFlickable {
|
||||
id: flickable
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
Layout.topMargin: -topMargin
|
||||
topMargin: Tokens.padding.large
|
||||
bottomMargin: Tokens.padding.extraLarge
|
||||
|
||||
contentHeight: root.contentChild?.implicitHeight ?? 0
|
||||
contentItem.children: [root.contentChild]
|
||||
}
|
||||
}
|
||||
18
modules/nexus/common/SectionHeader.qml
Normal file
18
modules/nexus/common/SectionHeader.qml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Caelestia.Config
|
||||
import qs.components
|
||||
import qs.services
|
||||
|
||||
StyledText {
|
||||
property bool first
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: first ? 0 : Tokens.spacing.largeIncreased - ((parent as ColumnLayout).spacing ?? 0)
|
||||
Layout.bottomMargin: Tokens.spacing.extraSmall
|
||||
Layout.leftMargin: Tokens.padding.small
|
||||
|
||||
color: Colours.palette.m3onSurfaceVariant
|
||||
font: Tokens.font.label.medium
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
65
modules/nexus/common/SelectRow.qml
Normal file
65
modules/nexus/common/SelectRow.qml
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Caelestia.Config
|
||||
import qs.components
|
||||
import qs.components.controls
|
||||
import qs.services
|
||||
import qs.modules.nexus.common
|
||||
|
||||
ConnectedRect {
|
||||
id: root
|
||||
|
||||
property alias label: label.text
|
||||
property string subtext
|
||||
property alias menuItems: splitButton.menuItems
|
||||
property alias active: splitButton.active
|
||||
property alias fallbackText: splitButton.fallbackText
|
||||
property alias fallbackIcon: splitButton.fallbackIcon
|
||||
property alias menuOnTop: splitButton.menuOnTop
|
||||
|
||||
signal selected(item: MenuItem)
|
||||
|
||||
implicitHeight: rowLayout.implicitHeight + rowLayout.anchors.margins * 2
|
||||
clip: false
|
||||
z: splitButton.expanded ? 1 : 0
|
||||
|
||||
RowLayout {
|
||||
id: rowLayout
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Tokens.padding.medium
|
||||
anchors.leftMargin: Tokens.padding.largeIncreased
|
||||
anchors.rightMargin: Tokens.padding.largeIncreased
|
||||
spacing: Tokens.spacing.medium
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
|
||||
StyledText {
|
||||
id: label
|
||||
|
||||
Layout.fillWidth: true
|
||||
font: Tokens.font.body.small
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
StyledText {
|
||||
Layout.fillWidth: true
|
||||
visible: root.subtext
|
||||
text: root.subtext
|
||||
color: Colours.palette.m3outline
|
||||
font: Tokens.font.label.small
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
|
||||
SplitButton {
|
||||
id: splitButton
|
||||
|
||||
type: SplitButton.Tonal
|
||||
stateLayer.onClicked: splitButton.expanded = !splitButton.expanded
|
||||
menu.onItemSelected: item => root.selected(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
88
modules/nexus/common/SliderRow.qml
Normal file
88
modules/nexus/common/SliderRow.qml
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Caelestia.Config
|
||||
import qs.components
|
||||
import qs.components.controls
|
||||
import qs.services
|
||||
import qs.modules.nexus.common
|
||||
|
||||
ConnectedRect {
|
||||
id: root
|
||||
|
||||
property alias icon: icon.text
|
||||
property alias label: label.text
|
||||
property alias valueLabel: valueLabel.text
|
||||
property real value
|
||||
|
||||
signal moved(value: real)
|
||||
|
||||
implicitHeight: rowLayout.implicitHeight + rowLayout.anchors.margins + rowLayout.anchors.topMargin
|
||||
|
||||
RowLayout {
|
||||
id: rowLayout
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Tokens.padding.largeIncreased
|
||||
anchors.topMargin: Tokens.padding.large
|
||||
spacing: Tokens.spacing.medium
|
||||
|
||||
MaterialIcon {
|
||||
id: icon
|
||||
|
||||
color: Colours.palette.m3onSurfaceVariant
|
||||
font: Tokens.font.icon.medium
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Tokens.spacing.medium
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Tokens.spacing.small
|
||||
|
||||
StyledText {
|
||||
id: label
|
||||
|
||||
Layout.fillWidth: true
|
||||
font: Tokens.font.body.small
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: valueLabel
|
||||
|
||||
color: Colours.palette.m3outline
|
||||
font: Tokens.font.body.small
|
||||
}
|
||||
}
|
||||
|
||||
CustomMouseArea {
|
||||
function onWheel(event: WheelEvent): void {
|
||||
const step = GlobalConfig.services.audioIncrement;
|
||||
if (event.angleDelta.y > 0)
|
||||
root.moved(Math.min(1, root.value + step));
|
||||
else if (event.angleDelta.y < 0)
|
||||
root.moved(Math.max(0, root.value - step));
|
||||
}
|
||||
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: Tokens.padding.medium * 2
|
||||
|
||||
StyledSlider {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
implicitHeight: parent.implicitHeight
|
||||
|
||||
radius: Tokens.rounding.small
|
||||
value: root.value
|
||||
enabled: root.enabled
|
||||
onInteraction: v => root.moved(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
122
modules/nexus/common/StackPage.qml
Normal file
122
modules/nexus/common/StackPage.qml
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Caelestia.Config
|
||||
import qs.components
|
||||
import qs.modules.nexus
|
||||
|
||||
StackView {
|
||||
id: root
|
||||
|
||||
required property NexusState nState
|
||||
default property list<Component> pages
|
||||
readonly property int animMovement: Tokens.padding.extraExtraLarge * 2
|
||||
|
||||
function openSubPage(idx: int, immediate: bool): void {
|
||||
const page = pages[idx];
|
||||
if (page) {
|
||||
push(page, {
|
||||
nState
|
||||
}, immediate ? StackView.Immediate : StackView.PushTransition);
|
||||
} else {
|
||||
console.warn(logCat, "Attempted to open invalid sub-page index", idx);
|
||||
nState.closeSubPage();
|
||||
}
|
||||
}
|
||||
|
||||
clip: busy
|
||||
|
||||
Component.onCompleted: {
|
||||
openSubPage(0, true);
|
||||
for (const page of nState.subPageIdxStack)
|
||||
openSubPage(page, true);
|
||||
}
|
||||
|
||||
pushEnter: Transition {
|
||||
SequentialAnimation {
|
||||
PropertyAction {
|
||||
property: "opacity"
|
||||
value: 0
|
||||
}
|
||||
PauseAnimation {
|
||||
duration: Tokens.anim.durations.expressiveDefaultEffects
|
||||
}
|
||||
ParallelAnimation {
|
||||
Anim {
|
||||
property: "opacity"
|
||||
to: 1
|
||||
type: Anim.SlowEffects
|
||||
}
|
||||
Anim {
|
||||
property: "x"
|
||||
from: root.animMovement
|
||||
to: 0
|
||||
type: Anim.SlowEffects
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pushExit: Transition {
|
||||
Anim {
|
||||
property: "opacity"
|
||||
to: 0
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
|
||||
popEnter: Transition {
|
||||
SequentialAnimation {
|
||||
PropertyAction {
|
||||
property: "opacity"
|
||||
value: 0
|
||||
}
|
||||
PauseAnimation {
|
||||
duration: Tokens.anim.durations.expressiveDefaultEffects
|
||||
}
|
||||
ParallelAnimation {
|
||||
Anim {
|
||||
property: "opacity"
|
||||
to: 1
|
||||
type: Anim.SlowEffects
|
||||
}
|
||||
Anim {
|
||||
property: "x"
|
||||
from: -root.animMovement
|
||||
to: 0
|
||||
type: Anim.SlowEffects
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
popExit: Transition {
|
||||
Anim {
|
||||
property: "opacity"
|
||||
to: 0
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
|
||||
LoggingCategory {
|
||||
id: logCat
|
||||
|
||||
name: "caelestia.nexus"
|
||||
defaultLogLevel: LoggingCategory.Info
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onSubPageOpened(idx: int): void {
|
||||
root.openSubPage(idx, false);
|
||||
}
|
||||
|
||||
function onSubPageClosed(): void {
|
||||
if (root.depth < root.nState.subPageIdxStack.length) {
|
||||
console.log(logCat, "Attempted to close page while depth < stack depth. Ignoring.");
|
||||
return;
|
||||
}
|
||||
root.pop();
|
||||
}
|
||||
|
||||
target: root.nState
|
||||
}
|
||||
}
|
||||
64
modules/nexus/common/StepperRow.qml
Normal file
64
modules/nexus/common/StepperRow.qml
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Caelestia.Config
|
||||
import qs.components
|
||||
import qs.components.controls
|
||||
import qs.services
|
||||
import qs.modules.nexus.common
|
||||
|
||||
ConnectedRect {
|
||||
id: root
|
||||
|
||||
property alias label: label.text
|
||||
property string subtext
|
||||
property real value
|
||||
property real from: 0
|
||||
property real to: 99
|
||||
property real stepSize: 1
|
||||
|
||||
signal moved(value: real)
|
||||
|
||||
implicitHeight: rowLayout.implicitHeight + rowLayout.anchors.margins * 2
|
||||
|
||||
RowLayout {
|
||||
id: rowLayout
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Tokens.padding.medium
|
||||
anchors.leftMargin: Tokens.padding.largeIncreased
|
||||
anchors.rightMargin: Tokens.padding.largeIncreased
|
||||
spacing: Tokens.spacing.medium
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
|
||||
StyledText {
|
||||
id: label
|
||||
|
||||
Layout.fillWidth: true
|
||||
font: Tokens.font.body.small
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
StyledText {
|
||||
Layout.fillWidth: true
|
||||
visible: root.subtext
|
||||
text: root.subtext
|
||||
color: Colours.palette.m3outline
|
||||
font: Tokens.font.label.small
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
|
||||
CustomSpinBox {
|
||||
min: root.from
|
||||
max: root.to
|
||||
step: root.stepSize
|
||||
value: root.value
|
||||
onValueModified: v => root.moved(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
79
modules/nexus/common/ToggleRow.qml
Normal file
79
modules/nexus/common/ToggleRow.qml
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
import QtQuick
|
||||
import Caelestia.Config
|
||||
import qs.components
|
||||
import qs.components.controls
|
||||
import qs.services
|
||||
|
||||
StyledSwitch {
|
||||
id: root
|
||||
|
||||
property string subtext
|
||||
property alias first: bg.first
|
||||
property alias last: bg.last
|
||||
readonly property alias bg: bg
|
||||
|
||||
horizontalPadding: Tokens.padding.largeIncreased
|
||||
verticalPadding: Tokens.padding.medium
|
||||
font: Tokens.font.body.small
|
||||
|
||||
implicitWidth: implicitContentWidth + implicitIndicatorWidth + horizontalPadding * 2
|
||||
implicitHeight: Math.max(implicitContentHeight, implicitIndicatorHeight) + verticalPadding * 2
|
||||
cLayer: 2
|
||||
|
||||
indicator.anchors.verticalCenter: verticalCenter
|
||||
indicator.anchors.right: right
|
||||
indicator.anchors.rightMargin: root.horizontalPadding
|
||||
|
||||
onPressed: stateLayer.press(stateLayer.mouseX, stateLayer.mouseY)
|
||||
|
||||
background: ConnectedRect {
|
||||
id: bg
|
||||
|
||||
StateLayer {
|
||||
id: stateLayer
|
||||
|
||||
manualPressOverride: root.pressed
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: Item {
|
||||
anchors.left: parent.left
|
||||
anchors.right: root.indicator.left
|
||||
anchors.leftMargin: root.horizontalPadding
|
||||
anchors.rightMargin: Tokens.spacing.medium
|
||||
|
||||
implicitWidth: column.implicitWidth
|
||||
implicitHeight: column.implicitHeight
|
||||
|
||||
Column {
|
||||
id: column
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 0
|
||||
|
||||
StyledText {
|
||||
id: label
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
text: root.text
|
||||
font: root.font
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
visible: root.subtext
|
||||
text: root.subtext
|
||||
color: Colours.palette.m3outline
|
||||
font: Tokens.font.label.small
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
105
modules/nexus/common/WallItem.qml
Normal file
105
modules/nexus/common/WallItem.qml
Normal 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()
|
||||
}
|
||||
}
|
||||
130
modules/nexus/navpane/NavLocations.qml
Normal file
130
modules/nexus/navpane/NavLocations.qml
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Caelestia.Config
|
||||
import qs.components
|
||||
import qs.components.containers
|
||||
import qs.services
|
||||
import qs.modules.nexus
|
||||
|
||||
VerticalFadeFlickable {
|
||||
id: root
|
||||
|
||||
required property NexusState nState
|
||||
|
||||
topMargin: Tokens.padding.large
|
||||
bottomMargin: Tokens.padding.large
|
||||
contentHeight: content.implicitHeight
|
||||
|
||||
ColumnLayout {
|
||||
id: content
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
spacing: Tokens.spacing.extraSmall
|
||||
|
||||
Repeater {
|
||||
id: list
|
||||
|
||||
model: PageRegistry.pages
|
||||
|
||||
StyledRect {
|
||||
id: item
|
||||
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
readonly property bool isCurrentPage: index === root.nState.currentPageIdx
|
||||
readonly property bool isCategoryStart: index === 0 || PageRegistry.pages[index - 1].category !== modelData.category
|
||||
readonly property bool isCategoryEnd: index === list.model.length - 1 || PageRegistry.pages[index + 1].category !== modelData.category
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: index !== 0 && isCategoryStart ? Tokens.spacing.medium : 0
|
||||
implicitHeight: {
|
||||
const h = layout.implicitHeight + layout.anchors.margins * 2;
|
||||
return h % 2 === 0 ? h : h + 1;
|
||||
}
|
||||
|
||||
color: isCurrentPage ? Colours.palette.m3secondaryContainer : Colours.layer(Colours.palette.m3surfaceContainerHigh, 2)
|
||||
|
||||
topLeftRadius: stateLayer.pressed ? Tokens.rounding.medium : isCurrentPage ? Tokens.rounding.extraLargeIncreased : isCategoryStart ? Tokens.rounding.extraLarge : Tokens.rounding.extraSmall
|
||||
topRightRadius: stateLayer.pressed ? Tokens.rounding.medium : isCurrentPage ? Tokens.rounding.extraLargeIncreased : isCategoryStart ? Tokens.rounding.extraLarge : Tokens.rounding.extraSmall
|
||||
bottomLeftRadius: stateLayer.pressed ? Tokens.rounding.medium : isCurrentPage ? Tokens.rounding.extraLargeIncreased : isCategoryEnd ? Tokens.rounding.extraLarge : Tokens.rounding.extraSmall
|
||||
bottomRightRadius: stateLayer.pressed ? Tokens.rounding.medium : isCurrentPage ? Tokens.rounding.extraLargeIncreased : isCategoryEnd ? Tokens.rounding.extraLarge : Tokens.rounding.extraSmall
|
||||
|
||||
RadiusBehavior on topLeftRadius {}
|
||||
RadiusBehavior on topRightRadius {}
|
||||
RadiusBehavior on bottomLeftRadius {}
|
||||
RadiusBehavior on bottomRightRadius {}
|
||||
|
||||
StateLayer {
|
||||
id: stateLayer
|
||||
|
||||
anchors.fill: parent
|
||||
topLeftRadius: parent.topLeftRadius
|
||||
topRightRadius: parent.topRightRadius
|
||||
bottomLeftRadius: parent.bottomLeftRadius
|
||||
bottomRightRadius: parent.bottomRightRadius
|
||||
|
||||
onClicked: root.nState.currentPageIdx = item.index
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: layout
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Tokens.padding.large
|
||||
spacing: Tokens.spacing.medium
|
||||
|
||||
StyledRect {
|
||||
Layout.fillHeight: true
|
||||
Layout.topMargin: -1
|
||||
Layout.bottomMargin: -1
|
||||
implicitWidth: height
|
||||
|
||||
radius: Tokens.rounding.full
|
||||
color: item.isCurrentPage ? Colours.palette.m3primary : Colours.palette.m3secondaryContainer
|
||||
|
||||
MaterialIcon {
|
||||
anchors.centerIn: parent
|
||||
anchors.verticalCenterOffset: 1
|
||||
|
||||
text: item.modelData.icon
|
||||
color: item.isCurrentPage ? Colours.palette.m3onPrimary : Colours.palette.m3onSecondaryContainer
|
||||
fontStyle: Tokens.font.icon.builders.medium.weight(Font.Medium).build()
|
||||
grade: 25
|
||||
fill: item.modelData.noFill ? 0 : 1
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
|
||||
StyledText {
|
||||
Layout.fillWidth: true
|
||||
text: item.modelData.label
|
||||
font: Tokens.font.body.medium
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
StyledText {
|
||||
Layout.fillWidth: true
|
||||
text: item.modelData.description
|
||||
color: Colours.palette.m3onSurfaceVariant
|
||||
font: Tokens.font.label.small
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component RadiusBehavior: Behavior {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
}
|
||||
81
modules/nexus/navpane/SearchBar.qml
Normal file
81
modules/nexus/navpane/SearchBar.qml
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Caelestia.Config
|
||||
import qs.components
|
||||
import qs.components.controls
|
||||
import qs.services
|
||||
import qs.modules.nexus
|
||||
|
||||
StyledRect {
|
||||
id: root
|
||||
|
||||
required property NexusState nState
|
||||
|
||||
implicitHeight: searchLayout.implicitHeight + Tokens.padding.medium * 2
|
||||
|
||||
radius: Tokens.rounding.full
|
||||
color: Colours.tPalette.m3surfaceContainerLowest
|
||||
border.color: Colours.palette.m3outlineVariant
|
||||
|
||||
Behavior on border.color {
|
||||
CAnim {}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.IBeamCursor
|
||||
onClicked: searchField.focus = true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: searchLayout
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.margins: Tokens.padding.large
|
||||
|
||||
spacing: Tokens.spacing.small
|
||||
|
||||
MaterialIcon {
|
||||
text: "search"
|
||||
color: Colours.palette.m3onSurfaceVariant
|
||||
fontStyle: Tokens.font.icon.medium
|
||||
}
|
||||
|
||||
StyledTextField {
|
||||
id: searchField
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
placeholderText: qsTr("Search settings")
|
||||
placeholderTextColor: Colours.palette.m3onSurfaceVariant
|
||||
color: Colours.palette.m3onSurfaceVariant
|
||||
font: Tokens.font.body.large
|
||||
|
||||
Binding {
|
||||
target: root.nState
|
||||
property: "searchOpen"
|
||||
value: searchField.text.length > 0
|
||||
}
|
||||
}
|
||||
|
||||
IconButton {
|
||||
icon: "close"
|
||||
font: Tokens.font.icon.medium
|
||||
type: IconButton.Text
|
||||
padding: Tokens.padding.extraSmall
|
||||
isRound: true
|
||||
onClicked: searchField.clear()
|
||||
|
||||
opacity: searchField.text.length > 0 ? 1 : 0
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
157
modules/nexus/pages/AboutPage.qml
Normal file
157
modules/nexus/pages/AboutPage.qml
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell.Io
|
||||
import Caelestia
|
||||
import Caelestia.Config
|
||||
import qs.components
|
||||
import qs.services
|
||||
import qs.utils
|
||||
import qs.modules.nexus.common
|
||||
|
||||
PageBase {
|
||||
id: root
|
||||
|
||||
// Plugin support is not wired up yet; always 0 for now
|
||||
readonly property int pluginCount: 0
|
||||
|
||||
property string quickshellVersion
|
||||
property string cliVersion
|
||||
|
||||
title: qsTr("About")
|
||||
|
||||
ColumnLayout {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
width: root.cappedWidth
|
||||
spacing: Tokens.spacing.extraSmall / 2
|
||||
|
||||
// e.g. "Quickshell 0.3.0 (revision ...)"
|
||||
Process {
|
||||
running: true
|
||||
command: ["quickshell", "--version"]
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: root.quickshellVersion = text.trim().split(" ")[1] ?? ""
|
||||
}
|
||||
}
|
||||
|
||||
// Parsed from the caelestia CLI's package listing; the sh wrapper avoids a
|
||||
// warning when the (optional) CLI isn't installed
|
||||
Process {
|
||||
running: true
|
||||
command: ["sh", "-c", "caelestia --version 2>/dev/null"]
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
const m = text.match(/caelestia-cli\S*\s+(\d+(?:\.\d+)*)/);
|
||||
root.cliVersion = m ? m[1] : "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Hero
|
||||
ConnectedRect {
|
||||
Layout.fillWidth: true
|
||||
first: true
|
||||
last: true
|
||||
implicitHeight: hero.implicitHeight + Tokens.padding.extraLarge * 2
|
||||
|
||||
ColumnLayout {
|
||||
id: hero
|
||||
|
||||
anchors.centerIn: parent
|
||||
width: parent.width - Tokens.padding.largeIncreased * 2
|
||||
spacing: Tokens.spacing.small
|
||||
|
||||
AnimatedLogo {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredWidth: implicitWidth
|
||||
Layout.preferredHeight: implicitHeight
|
||||
}
|
||||
|
||||
StyledText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.topMargin: Tokens.spacing.small
|
||||
text: "Caelestia"
|
||||
font: Tokens.font.headline.builders.large.width(110).build()
|
||||
}
|
||||
|
||||
StyledText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: CUtils.version ? `v${CUtils.version}` : "…"
|
||||
color: Colours.palette.m3onSurfaceVariant
|
||||
font: Tokens.font.body.medium
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// System
|
||||
SectionHeader {
|
||||
text: qsTr("System")
|
||||
}
|
||||
|
||||
InfoRow {
|
||||
first: true
|
||||
label: qsTr("Hostname")
|
||||
value: SysInfo.hostname
|
||||
}
|
||||
|
||||
InfoRow {
|
||||
label: qsTr("Device")
|
||||
value: SysInfo.device
|
||||
}
|
||||
|
||||
InfoRow {
|
||||
label: qsTr("Distro")
|
||||
value: SysInfo.osPrettyName || SysInfo.osName
|
||||
}
|
||||
|
||||
InfoRow {
|
||||
label: qsTr("Kernel")
|
||||
value: SysInfo.kernel
|
||||
}
|
||||
|
||||
InfoRow {
|
||||
last: true
|
||||
label: qsTr("Firmware")
|
||||
value: SysInfo.firmware
|
||||
}
|
||||
|
||||
// Software
|
||||
SectionHeader {
|
||||
text: qsTr("Software")
|
||||
}
|
||||
|
||||
InfoRow {
|
||||
first: true
|
||||
label: qsTr("Shell")
|
||||
value: CUtils.version || "…"
|
||||
}
|
||||
|
||||
InfoRow {
|
||||
label: qsTr("CLI")
|
||||
value: root.cliVersion || "…"
|
||||
}
|
||||
|
||||
InfoRow {
|
||||
label: qsTr("Quickshell")
|
||||
value: root.quickshellVersion || "…"
|
||||
}
|
||||
|
||||
InfoRow {
|
||||
last: true
|
||||
label: qsTr("Qt")
|
||||
value: CUtils.qtVersion || "…"
|
||||
}
|
||||
|
||||
// Plugins
|
||||
SectionHeader {
|
||||
text: qsTr("Plugins")
|
||||
}
|
||||
|
||||
InfoRow {
|
||||
first: true
|
||||
last: true
|
||||
label: qsTr("Loaded plugins")
|
||||
value: root.pluginCount.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
134
modules/nexus/pages/AudioPage.qml
Normal file
134
modules/nexus/pages/AudioPage.qml
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Caelestia.Config
|
||||
import qs.components
|
||||
import qs.services
|
||||
import qs.utils
|
||||
import qs.modules.nexus.common
|
||||
|
||||
PageBase {
|
||||
id: root
|
||||
|
||||
title: qsTr("Audio")
|
||||
|
||||
ColumnLayout {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
width: root.cappedWidth
|
||||
spacing: Tokens.spacing.extraSmall / 2
|
||||
|
||||
// Output
|
||||
SliderRow {
|
||||
Layout.fillWidth: true
|
||||
first: true
|
||||
icon: Icons.getVolumeIcon(Audio.volume, Audio.muted)
|
||||
label: qsTr("Output")
|
||||
valueLabel: Math.round(value * 100) + "%"
|
||||
value: Audio.volume
|
||||
enabled: !Audio.muted
|
||||
onMoved: v => Audio.setVolume(v)
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Muted")
|
||||
checked: Audio.muted
|
||||
onToggled: Audio.setStreamMuted(Audio.sink, checked)
|
||||
}
|
||||
|
||||
AudioDeviceList {
|
||||
nodes: Audio.sinks
|
||||
currentId: Audio.sink?.id ?? -1
|
||||
iconName: "speaker"
|
||||
placeholderIcon: "speaker"
|
||||
placeholderText: qsTr("No output devices")
|
||||
onSelected: node => Audio.setAudioSink(node)
|
||||
}
|
||||
|
||||
// Input
|
||||
SliderRow {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Tokens.spacing.large - parent.spacing
|
||||
first: true
|
||||
icon: Icons.getMicVolumeIcon(Audio.sourceVolume, Audio.sourceMuted)
|
||||
label: qsTr("Input")
|
||||
valueLabel: Math.round(value * 100) + "%"
|
||||
value: Audio.sourceVolume
|
||||
enabled: !Audio.sourceMuted
|
||||
onMoved: v => Audio.setSourceVolume(v)
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Muted")
|
||||
checked: Audio.sourceMuted
|
||||
onToggled: Audio.setStreamMuted(Audio.source, checked)
|
||||
}
|
||||
|
||||
AudioDeviceList {
|
||||
nodes: Audio.sources
|
||||
currentId: Audio.source?.id ?? -1
|
||||
iconName: "mic"
|
||||
placeholderIcon: "mic_off"
|
||||
placeholderText: qsTr("No input devices")
|
||||
onSelected: node => Audio.setAudioSource(node)
|
||||
}
|
||||
|
||||
// Per-app volumes
|
||||
ConnectedRect {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Tokens.spacing.large - parent.spacing
|
||||
implicitHeight: appLayout.implicitHeight + appLayout.anchors.margins * 2
|
||||
first: true
|
||||
last: true
|
||||
|
||||
StateLayer {
|
||||
onClicked: root.nState.openSubPage(1)
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: appLayout
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Tokens.padding.medium
|
||||
anchors.leftMargin: Tokens.padding.largeIncreased
|
||||
anchors.rightMargin: Tokens.padding.largeIncreased
|
||||
spacing: Tokens.spacing.medium
|
||||
|
||||
MaterialIcon {
|
||||
text: "tune"
|
||||
font: Tokens.font.icon.medium
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
|
||||
StyledText {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("App volumes")
|
||||
font: Tokens.font.body.small
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
StyledText {
|
||||
Layout.fillWidth: true
|
||||
text: Audio.streams.length === 0 ? qsTr("No apps playing audio") : Audio.streams.length === 1 ? qsTr("1 app playing audio") : qsTr("%1 apps playing audio").arg(Audio.streams.length)
|
||||
color: Colours.palette.m3outline
|
||||
font: Tokens.font.label.small
|
||||
elide: Text.ElideRight
|
||||
animate: true
|
||||
}
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
text: "chevron_right"
|
||||
color: Colours.palette.m3onSurfaceVariant
|
||||
font: Tokens.font.icon.medium
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
248
modules/nexus/pages/BluetoothPage.qml
Normal file
248
modules/nexus/pages/BluetoothPage.qml
Normal file
|
|
@ -0,0 +1,248 @@
|
|||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Bluetooth
|
||||
import Caelestia.Config
|
||||
import qs.components
|
||||
import qs.components.controls
|
||||
import qs.services
|
||||
import qs.utils
|
||||
import qs.modules.nexus.common
|
||||
|
||||
PageBase {
|
||||
id: root
|
||||
|
||||
readonly property BluetoothAdapter adapter: Bluetooth.defaultAdapter // qmllint disable unresolved-type
|
||||
readonly property bool btEnabled: adapter?.enabled ?? false
|
||||
|
||||
title: qsTr("Connected devices")
|
||||
|
||||
ColumnLayout {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
width: root.cappedWidth
|
||||
spacing: Tokens.spacing.extraSmall / 2
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
first: true
|
||||
text: qsTr("Bluetooth")
|
||||
font: Tokens.font.body.medium
|
||||
horizontalPadding: Tokens.padding.largeIncreased
|
||||
checked: root.btEnabled
|
||||
onToggled: {
|
||||
if (root.adapter)
|
||||
root.adapter.enabled = checked;
|
||||
}
|
||||
}
|
||||
|
||||
ItemList {
|
||||
id: savedList
|
||||
|
||||
showList: root.btEnabled
|
||||
placeholderIcon: root.btEnabled ? "devices_other" : "bluetooth_disabled"
|
||||
placeholderText: root.btEnabled ? qsTr("No saved devices") : qsTr("Bluetooth disabled")
|
||||
|
||||
model: ScriptModel {
|
||||
values: Bluetooth.devices.values.filter(d => d.bonded).sort((a, b) => (b.connected - a.connected) || a.name.localeCompare(b.name)) // qmllint disable unresolved-type
|
||||
}
|
||||
|
||||
delegate: StyledRect {
|
||||
id: device
|
||||
|
||||
required property BluetoothDevice modelData
|
||||
readonly property bool connected: modelData && modelData.state === BluetoothDeviceState.Connected // qmllint disable unresolved-type
|
||||
readonly property bool loading: modelData && (modelData.state === BluetoothDeviceState.Connecting || modelData.state === BluetoothDeviceState.Disconnecting) // qmllint disable unresolved-type
|
||||
property real textOpacity: loading ? 0.5 : 1
|
||||
|
||||
anchors.left: savedList.list.contentItem.left
|
||||
anchors.right: savedList.list.contentItem.right
|
||||
implicitHeight: deviceLayout.implicitHeight + deviceLayout.anchors.margins * 2
|
||||
radius: Tokens.rounding.extraSmall
|
||||
color: "transparent"
|
||||
|
||||
Behavior on textOpacity {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
|
||||
StateLayer {
|
||||
disabled: device.loading
|
||||
onClicked: {
|
||||
if (!device.modelData || device.loading)
|
||||
return;
|
||||
device.modelData.connected = !device.connected;
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: deviceLayout
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Tokens.padding.medium
|
||||
anchors.leftMargin: Tokens.padding.largeIncreased
|
||||
anchors.rightMargin: Tokens.padding.largeIncreased
|
||||
spacing: Tokens.spacing.medium
|
||||
|
||||
StyledRect {
|
||||
implicitWidth: implicitHeight
|
||||
implicitHeight: deviceIcon.implicitHeight + Tokens.padding.small * 2
|
||||
radius: Tokens.rounding.full
|
||||
color: device.connected ? Colours.palette.m3primary : Colours.palette.m3secondaryContainer
|
||||
|
||||
MaterialIcon {
|
||||
id: deviceIcon
|
||||
|
||||
anchors.centerIn: parent
|
||||
text: Icons.getBluetoothIcon(device.modelData?.icon ?? "")
|
||||
color: device.connected ? Colours.palette.m3onPrimary : Colours.palette.m3onSecondaryContainer
|
||||
font: Tokens.font.icon.medium
|
||||
fill: device.connected ? 1 : 0
|
||||
opacity: device.textOpacity
|
||||
|
||||
Behavior on fill {
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
opacity: device.textOpacity
|
||||
|
||||
StyledText {
|
||||
Layout.fillWidth: true
|
||||
text: device.modelData?.name ?? qsTr("Unknown")
|
||||
font: Tokens.font.body.small
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
StyledText {
|
||||
Layout.fillWidth: true
|
||||
text: device.connected ? qsTr("Connected%1").arg(device.modelData?.batteryAvailable ? " • " + Math.round(device.modelData.battery * 100) + "%" : "") : qsTr("Saved")
|
||||
color: Colours.palette.m3outline
|
||||
font: Tokens.font.label.small
|
||||
elide: Text.ElideRight
|
||||
animate: true
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
implicitWidth: height
|
||||
|
||||
AnimLoader {
|
||||
anchors.centerIn: parent
|
||||
sourceComp: device.loading ? loadingComp : btnComp
|
||||
|
||||
Component {
|
||||
id: btnComp
|
||||
|
||||
IconButton {
|
||||
icon: "settings"
|
||||
type: IconButton.Text
|
||||
padding: Tokens.padding.small
|
||||
inactiveOnColour: device.connected ? Colours.palette.m3primary : Colours.palette.m3onSurfaceVariant
|
||||
label.fill: 0
|
||||
|
||||
onClicked: {
|
||||
root.nState.selectedBtDevice = device.modelData;
|
||||
root.nState.openSubPage(1); // Per device info page
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: loadingComp
|
||||
|
||||
LoadingIndicator {
|
||||
implicitSize: Math.round(Tokens.font.icon.medium.pointSize * 1.3)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ConnectedRect {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: pairLayout.implicitHeight + pairLayout.anchors.margins * 2
|
||||
last: true
|
||||
|
||||
StateLayer {
|
||||
disabled: !root.btEnabled
|
||||
onClicked: root.nState.openSubPage(2)
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: pairLayout
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Tokens.padding.medium
|
||||
anchors.leftMargin: Tokens.padding.largeIncreased
|
||||
anchors.rightMargin: Tokens.padding.largeIncreased
|
||||
spacing: Tokens.spacing.medium
|
||||
opacity: root.btEnabled ? 1 : 0.5
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {}
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
text: "add"
|
||||
font: Tokens.font.icon.medium
|
||||
}
|
||||
|
||||
StyledText {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Pair new device")
|
||||
font: Tokens.font.body.small
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Tokens.spacing.large - parent.spacing
|
||||
|
||||
first: true
|
||||
text: qsTr("Discoverable")
|
||||
subtext: qsTr("Allow nearby devices to find this one")
|
||||
enabled: root.btEnabled
|
||||
opacity: root.btEnabled ? 1 : 0.5
|
||||
checked: root.adapter?.discoverable ?? false
|
||||
onToggled: {
|
||||
if (root.adapter)
|
||||
root.adapter.discoverable = checked;
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
last: true
|
||||
text: qsTr("Pairable")
|
||||
subtext: qsTr("Allow nearby devices to pair with this one")
|
||||
enabled: root.btEnabled
|
||||
opacity: root.btEnabled ? 1 : 0.5
|
||||
checked: root.adapter?.pairable ?? false
|
||||
onToggled: {
|
||||
if (root.adapter)
|
||||
root.adapter.pairable = checked;
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
175
modules/nexus/pages/LanguageAndRegion.qml
Normal file
175
modules/nexus/pages/LanguageAndRegion.qml
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Caelestia.Config
|
||||
import qs.components
|
||||
import qs.components.controls
|
||||
import qs.services
|
||||
import qs.modules.nexus.common
|
||||
|
||||
PageBase {
|
||||
id: root
|
||||
|
||||
// Temperature units (index 0 = Celsius, 1 = Fahrenheit — matches Weather.formatTemp)
|
||||
readonly property list<MenuItem> tempItems: [
|
||||
MenuItem {
|
||||
text: "°C"
|
||||
},
|
||||
MenuItem {
|
||||
text: "°F"
|
||||
}
|
||||
]
|
||||
|
||||
// Clock format (index 0 = 24-hour, 1 = 12-hour — matches Time.useTwelveHourClock)
|
||||
readonly property list<MenuItem> clockItems: [
|
||||
MenuItem {
|
||||
text: qsTr("24-hour")
|
||||
},
|
||||
MenuItem {
|
||||
text: qsTr("12-hour")
|
||||
}
|
||||
]
|
||||
|
||||
title: qsTr("Language & region")
|
||||
|
||||
ColumnLayout {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
width: root.cappedWidth
|
||||
spacing: Tokens.spacing.extraSmall / 2
|
||||
|
||||
// Language
|
||||
SectionHeader {
|
||||
first: true
|
||||
text: qsTr("Language")
|
||||
}
|
||||
|
||||
// Read-only: the shell follows the system locale (no in-shell translations yet)
|
||||
ConnectedRect {
|
||||
Layout.fillWidth: true
|
||||
first: true
|
||||
last: true
|
||||
implicitHeight: localeLayout.implicitHeight + localeLayout.anchors.margins * 2
|
||||
|
||||
RowLayout {
|
||||
id: localeLayout
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Tokens.padding.medium
|
||||
anchors.leftMargin: Tokens.padding.largeIncreased
|
||||
anchors.rightMargin: Tokens.padding.largeIncreased
|
||||
spacing: Tokens.spacing.medium
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
|
||||
StyledText {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("System language")
|
||||
font: Tokens.font.body.small
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
StyledText {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Follows your system locale (%1)").arg(Qt.locale().name)
|
||||
color: Colours.palette.m3outline
|
||||
font: Tokens.font.label.small
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: Qt.locale().nativeLanguageName || Qt.locale().name
|
||||
color: Colours.palette.m3onSurfaceVariant
|
||||
font: Tokens.font.body.small
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Weather
|
||||
SectionHeader {
|
||||
text: qsTr("Weather")
|
||||
}
|
||||
|
||||
// Placeholder until the map-based location picker lands
|
||||
ConnectedRect {
|
||||
Layout.fillWidth: true
|
||||
first: true
|
||||
last: true
|
||||
implicitHeight: comingSoon.implicitHeight + Tokens.padding.extraLarge * 2
|
||||
|
||||
ColumnLayout {
|
||||
id: comingSoon
|
||||
|
||||
anchors.centerIn: parent
|
||||
width: parent.width - Tokens.padding.largeIncreased * 2
|
||||
spacing: Tokens.padding.extraSmall
|
||||
|
||||
MaterialIcon {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: "map"
|
||||
color: Colours.palette.m3outlineVariant
|
||||
font: Tokens.font.icon.extraLarge
|
||||
}
|
||||
|
||||
StyledText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: qsTr("Location picker coming soon")
|
||||
color: Colours.palette.m3outlineVariant
|
||||
font: Tokens.font.title.small
|
||||
}
|
||||
|
||||
StyledText {
|
||||
Layout.fillWidth: true
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.WordWrap
|
||||
text: qsTr("Choose your weather location on a map in a future update")
|
||||
color: Colours.palette.m3outlineVariant
|
||||
font: Tokens.font.body.small
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Units
|
||||
SectionHeader {
|
||||
text: qsTr("Units")
|
||||
}
|
||||
|
||||
SelectRow {
|
||||
Layout.fillWidth: true
|
||||
first: true
|
||||
label: qsTr("Temperature")
|
||||
subtext: qsTr("Units for weather temperatures")
|
||||
menuItems: root.tempItems
|
||||
active: root.tempItems[GlobalConfig.services.useFahrenheit ? 1 : 0]
|
||||
onSelected: item => GlobalConfig.services.useFahrenheit = root.tempItems.indexOf(item) === 1
|
||||
}
|
||||
|
||||
SelectRow {
|
||||
Layout.fillWidth: true
|
||||
last: true
|
||||
label: qsTr("System temperatures")
|
||||
subtext: qsTr("Units for CPU and GPU temperatures")
|
||||
menuItems: root.tempItems
|
||||
active: root.tempItems[GlobalConfig.services.useFahrenheitPerformance ? 1 : 0]
|
||||
onSelected: item => GlobalConfig.services.useFahrenheitPerformance = root.tempItems.indexOf(item) === 1
|
||||
}
|
||||
|
||||
// Time & date
|
||||
SectionHeader {
|
||||
text: qsTr("Time & date")
|
||||
}
|
||||
|
||||
SelectRow {
|
||||
Layout.fillWidth: true
|
||||
first: true
|
||||
last: true
|
||||
label: qsTr("Clock format")
|
||||
subtext: qsTr("How times are shown across the shell")
|
||||
menuItems: root.clockItems
|
||||
active: root.clockItems[GlobalConfig.services.useTwelveHourClock ? 1 : 0]
|
||||
onSelected: item => GlobalConfig.services.useTwelveHourClock = root.clockItems.indexOf(item) === 1
|
||||
}
|
||||
}
|
||||
}
|
||||
235
modules/nexus/pages/NetworkPage.qml
Normal file
235
modules/nexus/pages/NetworkPage.qml
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Caelestia.Config
|
||||
import qs.components
|
||||
import qs.components.controls
|
||||
import qs.services
|
||||
import qs.utils
|
||||
import qs.modules.nexus.common
|
||||
|
||||
PageBase {
|
||||
id: root
|
||||
|
||||
signal networkSelected(ap: Nmcli.AccessPoint)
|
||||
|
||||
title: qsTr("Network")
|
||||
|
||||
ColumnLayout {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
width: root.cappedWidth
|
||||
spacing: Tokens.spacing.extraSmall / 2
|
||||
|
||||
Timer {
|
||||
running: root.visible && Nmcli.wifiEnabled
|
||||
repeat: true
|
||||
triggeredOnStart: true
|
||||
interval: GlobalConfig.nexus.networkRescanInterval
|
||||
onTriggered: Nmcli.rescanWifi()
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: wifiScanDelay
|
||||
|
||||
interval: 100
|
||||
onTriggered: Nmcli.rescanWifi()
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onWifiEnabledChanged(): void {
|
||||
if (Nmcli.wifiEnabled)
|
||||
wifiScanDelay.start();
|
||||
}
|
||||
|
||||
target: Nmcli
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
first: true
|
||||
text: qsTr("Wi-Fi")
|
||||
font: Tokens.font.body.medium
|
||||
horizontalPadding: Tokens.padding.largeIncreased
|
||||
checked: Nmcli.wifiEnabled
|
||||
onToggled: Nmcli.enableWifi(checked)
|
||||
}
|
||||
|
||||
ItemList {
|
||||
id: networkList
|
||||
|
||||
showList: Nmcli.wifiEnabled
|
||||
placeholderIcon: Nmcli.wifiEnabled ? "wifi_find" : "signal_wifi_off"
|
||||
placeholderText: Nmcli.wifiEnabled ? qsTr("No networks found") : qsTr("Wi-Fi disabled")
|
||||
extraHeight: Nmcli.scanning ? Tokens.rounding.extraSmall : 0 // Inline so it isn't affected by anim
|
||||
list.anchors.top: scanningIndicator.bottom
|
||||
|
||||
model: ScriptModel {
|
||||
values: {
|
||||
const connecting = Nmcli.connectingSsid();
|
||||
// Lower rank sorts higher in the list
|
||||
const rank = n => n.active ? 0 : n.ssid === connecting ? 1 : Nmcli.hasSavedProfile(n.ssid) ? 2 : 3;
|
||||
return [...Nmcli.networks].sort((a, b) => rank(a) - rank(b) || b.strength - a.strength);
|
||||
}
|
||||
}
|
||||
|
||||
delegate: StateLayer {
|
||||
id: network
|
||||
|
||||
required property Nmcli.AccessPoint modelData
|
||||
property bool currentSelected
|
||||
property real textOpacity: disabled ? 0.5 : 1
|
||||
|
||||
disabled: currentSelected || Nmcli.connectingSsid() === modelData.ssid
|
||||
|
||||
anchors.left: networkList.list.contentItem.left
|
||||
anchors.right: networkList.list.contentItem.right
|
||||
implicitHeight: networkLayout.implicitHeight + networkLayout.anchors.margins * 2
|
||||
radius: Tokens.rounding.extraSmall
|
||||
anchors.fill: undefined
|
||||
|
||||
onClicked: {
|
||||
if (!modelData.active) {
|
||||
NetworkConnection.handleConnect(modelData);
|
||||
currentSelected = true;
|
||||
root.networkSelected(modelData);
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on textOpacity {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onActiveChanged(): void {
|
||||
if (network.modelData.active)
|
||||
network.currentSelected = false;
|
||||
}
|
||||
|
||||
target: network.modelData
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onNetworkSelected(ap: Nmcli.AccessPoint): void {
|
||||
if (ap !== network.modelData)
|
||||
network.currentSelected = false;
|
||||
}
|
||||
|
||||
target: root
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: networkLayout
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Tokens.padding.large
|
||||
anchors.leftMargin: Tokens.padding.extraLarge
|
||||
anchors.rightMargin: Tokens.padding.extraLarge
|
||||
spacing: Tokens.spacing.medium
|
||||
|
||||
MaterialIcon {
|
||||
text: Icons.getNetworkIcon(network.modelData.strength)
|
||||
color: network.modelData.active ? Colours.palette.m3primary : Colours.palette.m3onSurfaceVariant
|
||||
font: Tokens.font.icon.medium
|
||||
opacity: network.textOpacity
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
opacity: network.textOpacity
|
||||
|
||||
StyledText {
|
||||
Layout.fillWidth: true
|
||||
text: network.modelData.ssid
|
||||
font: Tokens.font.body.small
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
StyledText {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Security: %1%2").arg(network.modelData.security).arg(Nmcli.hasSavedProfile(network.modelData.ssid) ? qsTr(" • Saved") : "")
|
||||
color: Colours.palette.m3outline
|
||||
font: Tokens.font.label.small
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
|
||||
AnimLoader {
|
||||
sourceComp: Nmcli.connectingSsid() === network.modelData.ssid ? loadingComp : iconComp
|
||||
|
||||
Component {
|
||||
id: iconComp
|
||||
|
||||
MaterialIcon {
|
||||
text: network.modelData.active ? "settings" : "lock"
|
||||
color: network.modelData.active ? Colours.palette.m3primary : Colours.palette.m3onSurfaceVariant
|
||||
font: Tokens.font.icon.medium
|
||||
opacity: network.textOpacity
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: loadingComp
|
||||
|
||||
LoadingIndicator {
|
||||
implicitSize: Math.round(Tokens.font.icon.medium.pointSize * 1.3)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledProgressBar {
|
||||
id: scanningIndicator
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: 1
|
||||
implicitHeight: Nmcli.scanning ? Tokens.rounding.extraSmall : 0
|
||||
indeterminate: true
|
||||
|
||||
Behavior on implicitHeight {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ConnectedRect {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: addNetworkLayout.implicitHeight + addNetworkLayout.anchors.margins * 2
|
||||
last: true
|
||||
|
||||
StateLayer {}
|
||||
|
||||
RowLayout {
|
||||
id: addNetworkLayout
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Tokens.padding.medium
|
||||
anchors.leftMargin: Tokens.padding.largeIncreased
|
||||
anchors.rightMargin: Tokens.padding.largeIncreased
|
||||
|
||||
spacing: Tokens.spacing.medium
|
||||
|
||||
MaterialIcon {
|
||||
text: "add"
|
||||
font: Tokens.font.icon.medium
|
||||
}
|
||||
|
||||
StyledText {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Add network")
|
||||
font: Tokens.font.body.small
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
46
modules/nexus/pages/PanelsPage.qml
Normal file
46
modules/nexus/pages/PanelsPage.qml
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import QtQuick.Layouts
|
||||
import Caelestia.Config
|
||||
import qs.modules.nexus.common
|
||||
|
||||
PageBase {
|
||||
id: root
|
||||
|
||||
title: qsTr("Panels")
|
||||
|
||||
ColumnLayout {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
width: root.cappedWidth
|
||||
spacing: Tokens.spacing.extraSmall / 2
|
||||
|
||||
NavRow {
|
||||
first: true
|
||||
icon: "dashboard"
|
||||
label: qsTr("Dashboard")
|
||||
status: Config.dashboard.enabled ? qsTr("Enabled") : qsTr("Disabled")
|
||||
onClicked: root.nState.openSubPage(1)
|
||||
}
|
||||
|
||||
NavRow {
|
||||
icon: "dock_to_bottom"
|
||||
label: qsTr("Taskbar")
|
||||
status: Config.bar.persistent ? qsTr("Always visible") : Config.bar.showOnHover ? qsTr("Reveal on hover") : qsTr("Reveal on drag")
|
||||
onClicked: root.nState.openSubPage(2)
|
||||
}
|
||||
|
||||
NavRow {
|
||||
icon: "apps"
|
||||
label: qsTr("Launcher")
|
||||
status: Config.launcher.enabled ? qsTr("Enabled") : qsTr("Disabled")
|
||||
onClicked: root.nState.openSubPage(3)
|
||||
}
|
||||
|
||||
NavRow {
|
||||
last: true
|
||||
icon: "dock_to_right"
|
||||
label: qsTr("Sidebar")
|
||||
status: Config.sidebar.enabled ? qsTr("Enabled") : qsTr("Disabled")
|
||||
onClicked: root.nState.openSubPage(4)
|
||||
}
|
||||
}
|
||||
}
|
||||
225
modules/nexus/pages/ServicesPage.qml
Normal file
225
modules/nexus/pages/ServicesPage.qml
Normal file
|
|
@ -0,0 +1,225 @@
|
|||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Caelestia.Config
|
||||
import Caelestia.Services
|
||||
import qs.components
|
||||
import qs.components.controls
|
||||
import qs.services
|
||||
import qs.modules.nexus.common
|
||||
|
||||
PageBase {
|
||||
id: root
|
||||
|
||||
// Lyrics backends, ordered to match LyricsBackend::Backend (Auto, Local, LRCLIB, NetEase)
|
||||
readonly property list<MenuItem> lyricsItems: [
|
||||
MenuItem {
|
||||
text: qsTr("Auto")
|
||||
},
|
||||
MenuItem {
|
||||
text: "Local"
|
||||
},
|
||||
MenuItem {
|
||||
text: "LRCLIB"
|
||||
},
|
||||
MenuItem {
|
||||
text: "NetEase"
|
||||
}
|
||||
]
|
||||
|
||||
// GPU options + the config string each maps to (see Gpu::parseType)
|
||||
readonly property list<MenuItem> gpuItems: [
|
||||
MenuItem {
|
||||
text: qsTr("Auto")
|
||||
},
|
||||
MenuItem {
|
||||
text: "NVIDIA"
|
||||
},
|
||||
MenuItem {
|
||||
text: qsTr("Generic")
|
||||
},
|
||||
MenuItem {
|
||||
text: qsTr("None")
|
||||
}
|
||||
]
|
||||
readonly property list<string> gpuValues: ["", "NVIDIA", "GENERIC", "None"]
|
||||
|
||||
function gpuKeyToIndex(key: string): int {
|
||||
const u = (key ?? "").trim().toUpperCase();
|
||||
if (u === "")
|
||||
return 0; // Auto
|
||||
if (u === "NVIDIA")
|
||||
return 1;
|
||||
if (u === "GENERIC")
|
||||
return 2;
|
||||
return 3; // None
|
||||
}
|
||||
|
||||
title: qsTr("Services")
|
||||
|
||||
ColumnLayout {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
width: root.cappedWidth
|
||||
spacing: Tokens.spacing.extraSmall / 2
|
||||
|
||||
// Detected running players, used as default-player options
|
||||
Variants {
|
||||
id: playerVariants
|
||||
|
||||
model: [...new Set(Players.list.map(p => Players.getIdentity(p)).filter(id => id))]
|
||||
|
||||
MenuItem {
|
||||
required property string modelData
|
||||
|
||||
text: modelData
|
||||
icon: modelData === GlobalConfig.services.defaultPlayer ? "check" : ""
|
||||
activeIcon: "music_note"
|
||||
}
|
||||
}
|
||||
|
||||
// Polling
|
||||
SectionHeader {
|
||||
first: true
|
||||
text: qsTr("Polling")
|
||||
}
|
||||
|
||||
StepperRow {
|
||||
Layout.fillWidth: true
|
||||
first: true
|
||||
label: qsTr("Media refresh")
|
||||
subtext: qsTr("How often the media position updates (ms)")
|
||||
value: GlobalConfig.dashboard.mediaUpdateInterval
|
||||
from: 100
|
||||
to: 2000
|
||||
stepSize: 50
|
||||
onMoved: v => GlobalConfig.dashboard.mediaUpdateInterval = v
|
||||
}
|
||||
|
||||
StepperRow {
|
||||
Layout.fillWidth: true
|
||||
label: qsTr("System stats refresh")
|
||||
subtext: qsTr("CPU, memory and GPU update interval (seconds)")
|
||||
value: GlobalConfig.dashboard.resourceUpdateInterval / 1000
|
||||
from: 0.5
|
||||
to: 10
|
||||
stepSize: 0.5
|
||||
onMoved: v => GlobalConfig.dashboard.resourceUpdateInterval = Math.round(v * 1000)
|
||||
}
|
||||
|
||||
StepperRow {
|
||||
Layout.fillWidth: true
|
||||
last: true
|
||||
label: qsTr("Wi-Fi rescan")
|
||||
subtext: qsTr("How often available networks are rescanned (seconds)")
|
||||
value: GlobalConfig.nexus.networkRescanInterval / 1000
|
||||
from: 5
|
||||
to: 120
|
||||
stepSize: 5
|
||||
onMoved: v => GlobalConfig.nexus.networkRescanInterval = Math.round(v * 1000)
|
||||
}
|
||||
|
||||
// Media & lyrics
|
||||
SectionHeader {
|
||||
text: qsTr("Media & lyrics")
|
||||
}
|
||||
|
||||
SelectRow {
|
||||
Layout.fillWidth: true
|
||||
first: true
|
||||
label: qsTr("Lyrics backend")
|
||||
subtext: qsTr("Source used to fetch synced lyrics")
|
||||
menuItems: root.lyricsItems
|
||||
active: root.lyricsItems[Lyrics.preferredBackend] ?? root.lyricsItems[0]
|
||||
onSelected: item => Lyrics.preferredBackend = root.lyricsItems.indexOf(item)
|
||||
}
|
||||
|
||||
SelectRow {
|
||||
Layout.fillWidth: true
|
||||
last: true
|
||||
label: qsTr("Default player")
|
||||
subtext: qsTr("Preferred media player when several are open")
|
||||
menuItems: playerVariants.instances
|
||||
active: menuItems.find(i => i.text === GlobalConfig.services.defaultPlayer) ?? null
|
||||
fallbackIcon: "music_note"
|
||||
fallbackText: GlobalConfig.services.defaultPlayer || qsTr("Auto")
|
||||
onSelected: item => GlobalConfig.services.defaultPlayer = item.text
|
||||
}
|
||||
|
||||
// Input increments
|
||||
SectionHeader {
|
||||
text: qsTr("Input increments")
|
||||
}
|
||||
|
||||
StepperRow {
|
||||
Layout.fillWidth: true
|
||||
first: true
|
||||
label: qsTr("Volume step")
|
||||
subtext: qsTr("Amount the volume changes per scroll (%)")
|
||||
value: Math.round(GlobalConfig.services.audioIncrement * 100)
|
||||
from: 1
|
||||
to: 50
|
||||
stepSize: 1
|
||||
onMoved: v => GlobalConfig.services.audioIncrement = v / 100
|
||||
}
|
||||
|
||||
StepperRow {
|
||||
Layout.fillWidth: true
|
||||
label: qsTr("Brightness step")
|
||||
subtext: qsTr("Amount the brightness changes per scroll (%)")
|
||||
value: Math.round(GlobalConfig.services.brightnessIncrement * 100)
|
||||
from: 1
|
||||
to: 50
|
||||
stepSize: 1
|
||||
onMoved: v => GlobalConfig.services.brightnessIncrement = v / 100
|
||||
}
|
||||
|
||||
StepperRow {
|
||||
Layout.fillWidth: true
|
||||
last: true
|
||||
label: qsTr("Max volume")
|
||||
subtext: qsTr("Upper limit for output volume (%)")
|
||||
value: Math.round(GlobalConfig.services.maxVolume * 100)
|
||||
from: 50
|
||||
to: 200
|
||||
stepSize: 5
|
||||
onMoved: v => GlobalConfig.services.maxVolume = v / 100
|
||||
}
|
||||
|
||||
// Service tuning
|
||||
SectionHeader {
|
||||
text: qsTr("Service tuning")
|
||||
}
|
||||
|
||||
StepperRow {
|
||||
Layout.fillWidth: true
|
||||
first: true
|
||||
label: qsTr("Visualiser bars")
|
||||
subtext: qsTr("Number of bars in the audio visualisers")
|
||||
value: GlobalConfig.services.visualiserBars
|
||||
from: 10
|
||||
to: 120
|
||||
stepSize: 2
|
||||
onMoved: v => GlobalConfig.services.visualiserBars = v
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Smart colour scheme")
|
||||
subtext: qsTr("Derive theme mode and variant from the wallpaper")
|
||||
checked: GlobalConfig.services.smartScheme
|
||||
onToggled: GlobalConfig.services.smartScheme = checked
|
||||
}
|
||||
|
||||
SelectRow {
|
||||
Layout.fillWidth: true
|
||||
last: true
|
||||
label: qsTr("GPU")
|
||||
subtext: Gpu.name ? qsTr("Monitoring: %1").arg(Gpu.name) : qsTr("Override for GPU type")
|
||||
menuOnTop: true
|
||||
menuItems: root.gpuItems
|
||||
active: root.gpuItems[root.gpuKeyToIndex(GlobalConfig.services.gpuType)]
|
||||
onSelected: item => GlobalConfig.services.gpuType = root.gpuValues[root.gpuItems.indexOf(item)]
|
||||
}
|
||||
}
|
||||
}
|
||||
202
modules/nexus/pages/WallpaperAndStyle.qml
Normal file
202
modules/nexus/pages/WallpaperAndStyle.qml
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Caelestia.Components
|
||||
import Caelestia.Config
|
||||
import qs.components
|
||||
import qs.components.controls
|
||||
import qs.components.images
|
||||
import qs.services
|
||||
import qs.modules.nexus.common
|
||||
|
||||
PageBase {
|
||||
id: root
|
||||
|
||||
title: qsTr("Wallpaper & style")
|
||||
|
||||
ColumnLayout {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
width: root.cappedWidth
|
||||
spacing: Tokens.spacing.large
|
||||
|
||||
StyledClippingRect {
|
||||
id: wallWrapper
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
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.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 {
|
||||
Anim {
|
||||
type: Anim.SlowEffects
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
opacity: Config.background.wallpaperEnabled ? 1 : 0
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
type: Anim.SlowEffects
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: wallIndicatorLoader
|
||||
|
||||
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 {
|
||||
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
|
||||
disabled: !Config.background.wallpaperEnabled
|
||||
onClicked: root.nState.openSubPage(1) // Wallpaper page
|
||||
}
|
||||
|
||||
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: root.nState.openSubPage(3) // Colours page
|
||||
}
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
|
||||
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.fillWidth: true
|
||||
|
||||
text: qsTr("Transparency")
|
||||
subtext: qsTr("Base %1, layers %2").arg(Colours.transparency.base).arg(Colours.transparency.layers)
|
||||
checked: Colours.transparency.enabled
|
||||
onToggled: GlobalConfig.appearance.transparency.enabled = checked
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.topMargin: Tokens.spacing.extraSmall / 2 - parent.spacing
|
||||
Layout.fillWidth: true
|
||||
|
||||
last: true
|
||||
text: qsTr("Dark theme")
|
||||
checked: !Colours.light
|
||||
onToggled: Colours.setMode(checked ? "dark" : "light")
|
||||
}
|
||||
}
|
||||
}
|
||||
70
modules/nexus/pages/audio/AppVolumes.qml
Normal file
70
modules/nexus/pages/audio/AppVolumes.qml
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Services.Pipewire
|
||||
import Caelestia.Config
|
||||
import qs.components
|
||||
import qs.services
|
||||
import qs.utils
|
||||
import qs.modules.nexus.common
|
||||
|
||||
PageBase {
|
||||
id: root
|
||||
|
||||
title: qsTr("App volumes")
|
||||
isSubPage: true
|
||||
|
||||
ColumnLayout {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
width: root.cappedWidth
|
||||
spacing: Tokens.spacing.extraSmall / 2
|
||||
|
||||
StyledText {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Tokens.padding.small
|
||||
Layout.bottomMargin: Tokens.spacing.medium
|
||||
text: qsTr("Adjust the volume of individual apps currently playing audio.")
|
||||
color: Colours.palette.m3outline
|
||||
font: Tokens.font.body.small
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
ItemList {
|
||||
id: streamList
|
||||
|
||||
first: true
|
||||
last: true
|
||||
showList: true
|
||||
placeholderIcon: "music_off"
|
||||
placeholderText: qsTr("No apps playing audio")
|
||||
color: list.count === 0 ? Colours.tPalette.m3surfaceContainer : "transparent"
|
||||
list.spacing: Tokens.spacing.extraSmall / 2
|
||||
|
||||
model: ScriptModel {
|
||||
values: [...Audio.streams]
|
||||
}
|
||||
|
||||
delegate: SliderRow {
|
||||
id: stream
|
||||
|
||||
required property PwNode modelData
|
||||
required property int index
|
||||
|
||||
anchors.left: streamList.list.contentItem.left
|
||||
anchors.right: streamList.list.contentItem.right
|
||||
first: index === 0
|
||||
last: index === streamList.list.count - 1
|
||||
|
||||
icon: Icons.getVolumeIcon(stream.modelData?.audio?.volume ?? 0, stream.modelData?.audio?.muted ?? false)
|
||||
label: Audio.getStreamName(stream.modelData)
|
||||
valueLabel: Math.round(value * 100) + "%"
|
||||
value: stream.modelData?.audio?.volume ?? 0
|
||||
enabled: !stream.modelData?.audio?.muted
|
||||
onMoved: v => Audio.setStreamVolume(stream.modelData, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
193
modules/nexus/pages/bluetooth/BluetoothPairing.qml
Normal file
193
modules/nexus/pages/bluetooth/BluetoothPairing.qml
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Bluetooth
|
||||
import Caelestia.Config
|
||||
import qs.components
|
||||
import qs.components.controls
|
||||
import qs.services
|
||||
import qs.utils
|
||||
import qs.modules.nexus.common
|
||||
|
||||
PageBase {
|
||||
id: root
|
||||
|
||||
readonly property BluetoothAdapter adapter: Bluetooth.defaultAdapter // qmllint disable unresolved-type
|
||||
|
||||
function setScan(on: bool): void {
|
||||
if (adapter?.enabled)
|
||||
adapter.discovering = on;
|
||||
}
|
||||
|
||||
title: qsTr("Pair new device")
|
||||
isSubPage: true
|
||||
|
||||
Component.onCompleted: setScan(true)
|
||||
Component.onDestruction: setScan(false)
|
||||
onVisibleChanged: setScan(visible)
|
||||
|
||||
ColumnLayout {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
width: root.cappedWidth
|
||||
spacing: Tokens.spacing.extraSmall / 2
|
||||
|
||||
Connections {
|
||||
function onEnabledChanged(): void {
|
||||
if (root.adapter && !root.adapter.enabled)
|
||||
root.nState.closeSubPage();
|
||||
}
|
||||
|
||||
target: root.adapter
|
||||
}
|
||||
|
||||
ConnectedRect {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: headerText.implicitHeight + Tokens.padding.medium * 2
|
||||
first: true
|
||||
|
||||
StyledText {
|
||||
id: headerText
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: Tokens.padding.large
|
||||
anchors.verticalCenterOffset: Math.round(fontInfo.pointSize * 0.2)
|
||||
|
||||
text: qsTr("Available devices")
|
||||
color: Colours.palette.m3onSurfaceVariant
|
||||
font: Tokens.font.body.small
|
||||
}
|
||||
}
|
||||
|
||||
ItemList {
|
||||
id: deviceList
|
||||
|
||||
Layout.fillWidth: true
|
||||
showList: true
|
||||
extraHeight: scanIndicator.implicitHeight
|
||||
last: true
|
||||
placeholderIcon: "bluetooth_searching"
|
||||
placeholderText: qsTr("Searching for devices…")
|
||||
list.anchors.top: scanIndicator.bottom
|
||||
|
||||
model: ScriptModel {
|
||||
values: Bluetooth.devices.values.filter(d => !d.bonded).sort((a, b) => (b.pairing - a.pairing) || a.name.localeCompare(b.name)) // qmllint disable unresolved-type
|
||||
}
|
||||
|
||||
delegate: Item {
|
||||
id: newDevice
|
||||
|
||||
required property BluetoothDevice modelData
|
||||
required property int index
|
||||
property real textOpacity: modelData?.pairing ? 0.5 : 1
|
||||
property bool wasPairing
|
||||
|
||||
anchors.left: deviceList.list.contentItem.left
|
||||
anchors.right: deviceList.list.contentItem.right
|
||||
implicitHeight: newLayout.implicitHeight + newLayout.anchors.margins * 2
|
||||
|
||||
Behavior on textOpacity {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onPairedChanged(): void {
|
||||
if (newDevice.wasPairing && newDevice.modelData?.paired)
|
||||
root.nState.closeSubPage();
|
||||
}
|
||||
|
||||
target: newDevice.modelData
|
||||
}
|
||||
|
||||
StateLayer {
|
||||
radius: Tokens.rounding.extraSmall
|
||||
bottomLeftRadius: newDevice.index === deviceList?.list.count - 1 ? Tokens.rounding.extraLarge : radius
|
||||
bottomRightRadius: newDevice.index === deviceList?.list.count - 1 ? Tokens.rounding.extraLarge : radius
|
||||
disabled: newDevice.modelData?.pairing ?? false
|
||||
|
||||
onClicked: {
|
||||
newDevice.modelData?.pair();
|
||||
newDevice.wasPairing = true;
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: newLayout
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Tokens.padding.medium
|
||||
anchors.leftMargin: Tokens.padding.largeIncreased
|
||||
anchors.rightMargin: Tokens.padding.largeIncreased
|
||||
spacing: Tokens.spacing.medium
|
||||
|
||||
MaterialIcon {
|
||||
text: Icons.getBluetoothIcon(newDevice.modelData?.icon ?? "")
|
||||
color: Colours.palette.m3onSurfaceVariant
|
||||
font: Tokens.font.icon.medium
|
||||
opacity: newDevice.textOpacity
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
opacity: newDevice.textOpacity
|
||||
|
||||
StyledText {
|
||||
Layout.fillWidth: true
|
||||
text: newDevice.modelData?.name || qsTr("Unknown device")
|
||||
font: Tokens.font.body.small
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
StyledText {
|
||||
Layout.fillWidth: true
|
||||
text: newDevice.modelData?.pairing ? qsTr("Pairing...") : (newDevice.modelData?.address ?? "")
|
||||
color: Colours.palette.m3outline
|
||||
font: Tokens.font.label.small
|
||||
elide: Text.ElideRight
|
||||
animate: true
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
asynchronous: true
|
||||
active: opacity > 0
|
||||
opacity: newDevice.modelData?.pairing ? 1 : 0
|
||||
|
||||
sourceComponent: LoadingIndicator {
|
||||
implicitSize: Math.round(Tokens.font.icon.medium.pointSize * 1.3)
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledProgressBar {
|
||||
id: scanIndicator
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.margins: 1
|
||||
implicitHeight: Tokens.rounding.extraSmall
|
||||
indeterminate: true
|
||||
|
||||
Behavior on implicitHeight {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
260
modules/nexus/pages/bluetooth/BtDeviceInfo.qml
Normal file
260
modules/nexus/pages/bluetooth/BtDeviceInfo.qml
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell.Bluetooth
|
||||
import Caelestia.Components
|
||||
import Caelestia.Config
|
||||
import qs.components
|
||||
import qs.components.controls
|
||||
import qs.services
|
||||
import qs.modules.nexus.common
|
||||
|
||||
PageBase {
|
||||
id: root
|
||||
|
||||
readonly property BluetoothDevice device: nState.selectedBtDevice
|
||||
readonly property bool connected: device?.state === BluetoothDeviceState.Connected // qmllint disable unresolved-type
|
||||
readonly property bool loading: device?.state === BluetoothDeviceState.Connecting || device?.state === BluetoothDeviceState.Disconnecting // qmllint disable unresolved-type
|
||||
|
||||
readonly property string statusText: {
|
||||
if (!device)
|
||||
return "";
|
||||
let s = connected ? qsTr("Connected") : (device.bonded ? qsTr("Paired") : qsTr("Not paired"));
|
||||
if (connected && device.batteryAvailable)
|
||||
s += " • " + Math.round(device.battery * 100) + "%";
|
||||
return s;
|
||||
}
|
||||
|
||||
onDeviceChanged: {
|
||||
// Auto close when device lost
|
||||
if (!device)
|
||||
nState.closeSubPage();
|
||||
}
|
||||
|
||||
title: device?.name ?? qsTr("Device")
|
||||
isSubPage: true
|
||||
|
||||
ColumnLayout {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
width: root.cappedWidth
|
||||
spacing: Tokens.spacing.extraSmall / 2
|
||||
|
||||
// Big buttons
|
||||
ButtonRow {
|
||||
Layout.bottomMargin: Tokens.spacing.large - parent.spacing
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.minimumWidth: Math.round(root.cappedWidth * 0.7)
|
||||
spacing: Tokens.spacing.small
|
||||
|
||||
ButtonBase {
|
||||
id: forgetBtn
|
||||
|
||||
fillWidth: true
|
||||
shapeMorph: true
|
||||
isRound: true
|
||||
|
||||
inactiveColour: Colours.palette.m3errorContainer
|
||||
inactiveOnColour: Colours.palette.m3onErrorContainer
|
||||
|
||||
implicitWidth: forgetBtnLayout.implicitWidth + Tokens.padding.extraLarge * 2
|
||||
implicitHeight: forgetBtnLayout.implicitHeight + Tokens.padding.medium * 2
|
||||
|
||||
onClicked: {
|
||||
root.device?.forget();
|
||||
root.nState.closeSubPage();
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: forgetBtnLayout
|
||||
|
||||
anchors.centerIn: parent
|
||||
spacing: 0
|
||||
|
||||
MaterialIcon {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: "delete"
|
||||
color: forgetBtn.onColour
|
||||
font: Tokens.font.icon.medium
|
||||
}
|
||||
|
||||
StyledText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: qsTr("Forget")
|
||||
color: forgetBtn.onColour
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ButtonBase {
|
||||
id: connectBtn
|
||||
|
||||
fillWidth: true
|
||||
shapeMorph: true
|
||||
isRound: true
|
||||
|
||||
inactiveColour: Colours.palette.m3primaryContainer
|
||||
inactiveOnColour: Colours.palette.m3onPrimaryContainer
|
||||
stateLayer.disabled: root.loading
|
||||
|
||||
implicitWidth: connectBtnContent.implicitWidth + Tokens.padding.extraLarge * 2
|
||||
implicitHeight: connectBtnContent.implicitHeight + Tokens.padding.medium * 2
|
||||
|
||||
onClicked: root.device.connected = !root.connected
|
||||
|
||||
AnimLoader {
|
||||
id: connectBtnContent
|
||||
|
||||
anchors.centerIn: parent
|
||||
sourceComp: root.loading ? loadingComp : textComp
|
||||
outAnimType: Anim.SlowEffects
|
||||
inAnimType: Anim.SlowEffects
|
||||
}
|
||||
|
||||
Component {
|
||||
id: loadingComp
|
||||
|
||||
LoadingIndicator {
|
||||
implicitSize: connectBtn.height - Tokens.padding.large * 2
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: textComp
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 0
|
||||
|
||||
MaterialIcon {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: root.connected ? "close" : "add"
|
||||
color: connectBtn.inactiveOnColour
|
||||
font: Tokens.font.icon.medium
|
||||
animate: true
|
||||
}
|
||||
|
||||
StyledText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: root.connected ? qsTr("Disconnect") : qsTr("Connect")
|
||||
color: connectBtn.inactiveOnColour
|
||||
animate: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Connection group
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
verticalPadding: Tokens.padding.large
|
||||
first: true
|
||||
text: qsTr("Trusted")
|
||||
subtext: qsTr("Allow this device to connect automatically")
|
||||
checked: root.device?.trusted ?? false
|
||||
onToggled: {
|
||||
if (root.device)
|
||||
root.device.trusted = checked;
|
||||
}
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
verticalPadding: Tokens.padding.large
|
||||
text: qsTr("Blocked")
|
||||
subtext: qsTr("Prevent this device from connecting")
|
||||
checked: root.device?.blocked ?? false
|
||||
onToggled: {
|
||||
if (root.device)
|
||||
root.device.blocked = checked;
|
||||
}
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
verticalPadding: Tokens.padding.large
|
||||
last: true
|
||||
text: qsTr("Wake allowed")
|
||||
subtext: qsTr("Allow this device to wake the system")
|
||||
checked: root.device?.wakeAllowed ?? false
|
||||
onToggled: {
|
||||
if (root.device)
|
||||
root.device.wakeAllowed = checked;
|
||||
}
|
||||
}
|
||||
|
||||
// Information
|
||||
ConnectedRect {
|
||||
Layout.topMargin: Tokens.spacing.large - parent.spacing
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: batteryLayout.implicitHeight + Tokens.padding.large * 2
|
||||
first: true
|
||||
|
||||
ColumnLayout {
|
||||
id: batteryLayout
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.margins: Tokens.padding.large
|
||||
spacing: Tokens.spacing.small
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Tokens.spacing.medium
|
||||
|
||||
StyledText {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Battery")
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: root.device?.batteryAvailable ? Math.round(root.device.battery * 100) + "%" : qsTr("Unavailable")
|
||||
color: Colours.palette.m3outline
|
||||
font: Tokens.font.body.small
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
Layout.fillWidth: true
|
||||
active: root.device?.batteryAvailable ?? false
|
||||
visible: active
|
||||
asynchronous: true
|
||||
|
||||
sourceComponent: StyledProgressBar {
|
||||
implicitHeight: Tokens.padding.medium
|
||||
value: root.device.battery
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ConnectedRect {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: addrLayout.implicitHeight + Tokens.padding.large * 2
|
||||
last: true
|
||||
|
||||
RowLayout {
|
||||
id: addrLayout
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.margins: Tokens.padding.large
|
||||
spacing: Tokens.spacing.medium
|
||||
|
||||
StyledText {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Address")
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: root.device?.address ?? ""
|
||||
color: Colours.palette.m3outline
|
||||
font: Tokens.font.body.small
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
146
modules/nexus/pages/panels/DashboardPanel.qml
Normal file
146
modules/nexus/pages/panels/DashboardPanel.qml
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Caelestia.Config
|
||||
import qs.components
|
||||
import qs.modules.nexus.common
|
||||
|
||||
PageBase {
|
||||
id: root
|
||||
|
||||
title: qsTr("Dashboard")
|
||||
isSubPage: true
|
||||
|
||||
ColumnLayout {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
width: root.cappedWidth
|
||||
spacing: Tokens.spacing.extraSmall / 2
|
||||
|
||||
// General
|
||||
SectionHeader {
|
||||
first: true
|
||||
text: qsTr("General")
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
first: true
|
||||
text: qsTr("Enabled")
|
||||
checked: Config.dashboard.enabled
|
||||
onToggled: GlobalConfig.dashboard.enabled = checked
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
last: true
|
||||
text: qsTr("Show on hover")
|
||||
subtext: qsTr("Reveal when the cursor reaches the screen edge")
|
||||
checked: Config.dashboard.showOnHover
|
||||
onToggled: GlobalConfig.dashboard.showOnHover = checked
|
||||
}
|
||||
|
||||
// Tabs
|
||||
SectionHeader {
|
||||
text: qsTr("Tabs")
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
first: true
|
||||
text: qsTr("Dashboard")
|
||||
checked: Config.dashboard.showDashboard
|
||||
onToggled: GlobalConfig.dashboard.showDashboard = checked
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Media")
|
||||
checked: Config.dashboard.showMedia
|
||||
onToggled: GlobalConfig.dashboard.showMedia = checked
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Performance")
|
||||
checked: Config.dashboard.showPerformance
|
||||
onToggled: GlobalConfig.dashboard.showPerformance = checked
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
last: true
|
||||
text: qsTr("Weather")
|
||||
checked: Config.dashboard.showWeather
|
||||
onToggled: GlobalConfig.dashboard.showWeather = checked
|
||||
}
|
||||
|
||||
// Performance widgets
|
||||
SectionHeader {
|
||||
text: qsTr("Performance widgets")
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
first: true
|
||||
text: qsTr("Battery")
|
||||
checked: Config.dashboard.performance.showBattery
|
||||
onToggled: GlobalConfig.dashboard.performance.showBattery = checked
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("GPU")
|
||||
checked: Config.dashboard.performance.showGpu
|
||||
onToggled: GlobalConfig.dashboard.performance.showGpu = checked
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("CPU")
|
||||
checked: Config.dashboard.performance.showCpu
|
||||
onToggled: GlobalConfig.dashboard.performance.showCpu = checked
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Memory")
|
||||
checked: Config.dashboard.performance.showMemory
|
||||
onToggled: GlobalConfig.dashboard.performance.showMemory = checked
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Storage")
|
||||
checked: Config.dashboard.performance.showStorage
|
||||
onToggled: GlobalConfig.dashboard.performance.showStorage = checked
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
last: true
|
||||
text: qsTr("Network")
|
||||
checked: Config.dashboard.performance.showNetwork
|
||||
onToggled: GlobalConfig.dashboard.performance.showNetwork = checked
|
||||
}
|
||||
|
||||
// Behaviour
|
||||
SectionHeader {
|
||||
text: qsTr("Behaviour")
|
||||
}
|
||||
|
||||
StepperRow {
|
||||
Layout.fillWidth: true
|
||||
first: true
|
||||
last: true
|
||||
label: qsTr("Drag threshold")
|
||||
subtext: qsTr("Pixels dragged before the dashboard opens")
|
||||
value: Config.dashboard.dragThreshold
|
||||
from: 0
|
||||
to: 200
|
||||
stepSize: 5
|
||||
onMoved: v => GlobalConfig.dashboard.dragThreshold = v
|
||||
}
|
||||
}
|
||||
}
|
||||
147
modules/nexus/pages/panels/LauncherPanel.qml
Normal file
147
modules/nexus/pages/panels/LauncherPanel.qml
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Caelestia.Config
|
||||
import qs.components
|
||||
import qs.modules.nexus.common
|
||||
|
||||
PageBase {
|
||||
id: root
|
||||
|
||||
title: qsTr("Launcher")
|
||||
isSubPage: true
|
||||
|
||||
ColumnLayout {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
width: root.cappedWidth
|
||||
spacing: Tokens.spacing.extraSmall / 2
|
||||
|
||||
// General
|
||||
SectionHeader {
|
||||
first: true
|
||||
text: qsTr("General")
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
first: true
|
||||
text: qsTr("Enabled")
|
||||
checked: Config.launcher.enabled
|
||||
onToggled: GlobalConfig.launcher.enabled = checked
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
last: true
|
||||
text: qsTr("Show on hover")
|
||||
subtext: qsTr("Reveal when the cursor reaches the screen edge")
|
||||
checked: Config.launcher.showOnHover
|
||||
onToggled: GlobalConfig.launcher.showOnHover = checked
|
||||
}
|
||||
|
||||
// Display
|
||||
SectionHeader {
|
||||
text: qsTr("Display")
|
||||
}
|
||||
|
||||
StepperRow {
|
||||
Layout.fillWidth: true
|
||||
first: true
|
||||
label: qsTr("Max items shown")
|
||||
value: Config.launcher.maxShown
|
||||
from: 1
|
||||
to: 20
|
||||
stepSize: 1
|
||||
onMoved: v => GlobalConfig.launcher.maxShown = v
|
||||
}
|
||||
|
||||
StepperRow {
|
||||
Layout.fillWidth: true
|
||||
label: qsTr("Max wallpapers")
|
||||
value: Config.launcher.maxWallpapers
|
||||
from: 1
|
||||
to: 30
|
||||
stepSize: 1
|
||||
onMoved: v => GlobalConfig.launcher.maxWallpapers = v
|
||||
}
|
||||
|
||||
StepperRow {
|
||||
Layout.fillWidth: true
|
||||
last: true
|
||||
label: qsTr("Drag threshold")
|
||||
subtext: qsTr("Pixels dragged before the launcher opens")
|
||||
value: Config.launcher.dragThreshold
|
||||
from: 0
|
||||
to: 200
|
||||
stepSize: 5
|
||||
onMoved: v => GlobalConfig.launcher.dragThreshold = v
|
||||
}
|
||||
|
||||
// Behaviour
|
||||
SectionHeader {
|
||||
text: qsTr("Behaviour")
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
first: true
|
||||
text: qsTr("Vim keybinds")
|
||||
subtext: qsTr("Navigate results with Ctrl+hjkl")
|
||||
checked: GlobalConfig.launcher.vimKeybinds
|
||||
onToggled: GlobalConfig.launcher.vimKeybinds = checked
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
last: true
|
||||
text: qsTr("Enable dangerous actions")
|
||||
subtext: qsTr("Allow actions that shut down or log out")
|
||||
checked: GlobalConfig.launcher.enableDangerousActions
|
||||
onToggled: GlobalConfig.launcher.enableDangerousActions = checked
|
||||
}
|
||||
|
||||
// Fuzzy search
|
||||
SectionHeader {
|
||||
text: qsTr("Fuzzy search")
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
first: true
|
||||
text: qsTr("Apps")
|
||||
checked: GlobalConfig.launcher.useFuzzy.apps
|
||||
onToggled: GlobalConfig.launcher.useFuzzy.apps = checked
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Actions")
|
||||
checked: GlobalConfig.launcher.useFuzzy.actions
|
||||
onToggled: GlobalConfig.launcher.useFuzzy.actions = checked
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Schemes")
|
||||
checked: GlobalConfig.launcher.useFuzzy.schemes
|
||||
onToggled: GlobalConfig.launcher.useFuzzy.schemes = checked
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Variants")
|
||||
checked: GlobalConfig.launcher.useFuzzy.variants
|
||||
onToggled: GlobalConfig.launcher.useFuzzy.variants = checked
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
last: true
|
||||
text: qsTr("Wallpapers")
|
||||
checked: GlobalConfig.launcher.useFuzzy.wallpapers
|
||||
onToggled: GlobalConfig.launcher.useFuzzy.wallpapers = checked
|
||||
}
|
||||
}
|
||||
}
|
||||
46
modules/nexus/pages/panels/SidebarPanel.qml
Normal file
46
modules/nexus/pages/panels/SidebarPanel.qml
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Caelestia.Config
|
||||
import qs.components
|
||||
import qs.modules.nexus.common
|
||||
|
||||
PageBase {
|
||||
id: root
|
||||
|
||||
title: qsTr("Sidebar")
|
||||
isSubPage: true
|
||||
|
||||
ColumnLayout {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
width: root.cappedWidth
|
||||
spacing: Tokens.spacing.extraSmall / 2
|
||||
|
||||
SectionHeader {
|
||||
first: true
|
||||
text: qsTr("General")
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
first: true
|
||||
text: qsTr("Enabled")
|
||||
checked: Config.sidebar.enabled
|
||||
onToggled: GlobalConfig.sidebar.enabled = checked
|
||||
}
|
||||
|
||||
StepperRow {
|
||||
Layout.fillWidth: true
|
||||
last: true
|
||||
label: qsTr("Drag threshold")
|
||||
subtext: qsTr("Pixels dragged before the sidebar opens")
|
||||
value: Config.sidebar.dragThreshold
|
||||
from: 0
|
||||
to: 200
|
||||
stepSize: 5
|
||||
onMoved: v => GlobalConfig.sidebar.dragThreshold = v
|
||||
}
|
||||
}
|
||||
}
|
||||
127
modules/nexus/pages/panels/TaskbarPanel.qml
Normal file
127
modules/nexus/pages/panels/TaskbarPanel.qml
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick.Layouts
|
||||
import Caelestia.Config
|
||||
import qs.modules.nexus.common
|
||||
|
||||
PageBase {
|
||||
id: root
|
||||
|
||||
title: qsTr("Taskbar")
|
||||
isSubPage: true
|
||||
|
||||
ColumnLayout {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
width: root.cappedWidth
|
||||
spacing: Tokens.spacing.extraSmall / 2
|
||||
|
||||
// Behaviour
|
||||
SectionHeader {
|
||||
first: true
|
||||
text: qsTr("Behaviour")
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
first: true
|
||||
text: qsTr("Persistent")
|
||||
subtext: qsTr("Keep the bar visible at all times")
|
||||
checked: Config.bar.persistent
|
||||
onToggled: GlobalConfig.bar.persistent = checked
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Show on hover")
|
||||
subtext: qsTr("Reveal the bar when the cursor reaches the screen edge")
|
||||
checked: Config.bar.showOnHover
|
||||
onToggled: GlobalConfig.bar.showOnHover = checked
|
||||
}
|
||||
|
||||
StepperRow {
|
||||
Layout.fillWidth: true
|
||||
last: true
|
||||
label: qsTr("Drag threshold")
|
||||
subtext: qsTr("Pixels dragged before the bar reveals")
|
||||
value: Config.bar.dragThreshold
|
||||
from: 0
|
||||
to: 200
|
||||
stepSize: 5
|
||||
onMoved: v => GlobalConfig.bar.dragThreshold = v
|
||||
}
|
||||
|
||||
// Components
|
||||
SectionHeader {
|
||||
text: qsTr("Components")
|
||||
}
|
||||
|
||||
NavRow {
|
||||
first: true
|
||||
icon: "workspaces"
|
||||
label: qsTr("Workspaces")
|
||||
status: qsTr("Indicators, window icons")
|
||||
onClicked: root.nState.openSubPage(5)
|
||||
}
|
||||
|
||||
NavRow {
|
||||
icon: "web_asset"
|
||||
label: qsTr("Active window")
|
||||
status: qsTr("Title display, popout")
|
||||
onClicked: root.nState.openSubPage(6)
|
||||
}
|
||||
|
||||
NavRow {
|
||||
icon: "widgets"
|
||||
label: qsTr("Tray")
|
||||
status: qsTr("System tray icons")
|
||||
onClicked: root.nState.openSubPage(7)
|
||||
}
|
||||
|
||||
NavRow {
|
||||
icon: "signal_cellular_alt"
|
||||
label: qsTr("Status icons")
|
||||
status: qsTr("Visible indicators")
|
||||
onClicked: root.nState.openSubPage(8)
|
||||
}
|
||||
|
||||
NavRow {
|
||||
last: true
|
||||
icon: "schedule"
|
||||
label: qsTr("Clock")
|
||||
status: qsTr("Date, icon, background")
|
||||
onClicked: root.nState.openSubPage(9)
|
||||
}
|
||||
|
||||
// Scroll actions
|
||||
SectionHeader {
|
||||
text: qsTr("Scroll actions")
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
first: true
|
||||
text: qsTr("Workspaces")
|
||||
subtext: qsTr("Scroll over the workspace indicator to switch workspaces")
|
||||
checked: Config.bar.scrollActions.workspaces
|
||||
onToggled: GlobalConfig.bar.scrollActions.workspaces = checked
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Volume")
|
||||
subtext: qsTr("Scroll on the top half of the bar to adjust volume")
|
||||
checked: Config.bar.scrollActions.volume
|
||||
onToggled: GlobalConfig.bar.scrollActions.volume = checked
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
last: true
|
||||
text: qsTr("Brightness")
|
||||
subtext: qsTr("Scroll on the bottom half of the bar to adjust brightness")
|
||||
checked: Config.bar.scrollActions.brightness
|
||||
onToggled: GlobalConfig.bar.scrollActions.brightness = checked
|
||||
}
|
||||
}
|
||||
}
|
||||
51
modules/nexus/pages/panels/taskbar/BarActiveWindow.qml
Normal file
51
modules/nexus/pages/panels/taskbar/BarActiveWindow.qml
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick.Layouts
|
||||
import Caelestia.Config
|
||||
import qs.modules.nexus.common
|
||||
|
||||
PageBase {
|
||||
id: root
|
||||
|
||||
title: qsTr("Active window")
|
||||
isSubPage: true
|
||||
|
||||
ColumnLayout {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
width: root.cappedWidth
|
||||
spacing: Tokens.spacing.extraSmall / 2
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
first: true
|
||||
text: qsTr("Compact")
|
||||
checked: Config.bar.activeWindow.compact
|
||||
onToggled: GlobalConfig.bar.activeWindow.compact = checked
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Inverted")
|
||||
checked: Config.bar.activeWindow.inverted
|
||||
onToggled: GlobalConfig.bar.activeWindow.inverted = checked
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Show on hover")
|
||||
subtext: qsTr("Only show the active window title while hovering")
|
||||
checked: Config.bar.activeWindow.showOnHover
|
||||
onToggled: GlobalConfig.bar.activeWindow.showOnHover = checked
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
last: true
|
||||
text: qsTr("Popout on hover")
|
||||
subtext: qsTr("Show a window details popout when hovering")
|
||||
checked: Config.bar.popouts.activeWindow
|
||||
onToggled: GlobalConfig.bar.popouts.activeWindow = checked
|
||||
}
|
||||
}
|
||||
}
|
||||
42
modules/nexus/pages/panels/taskbar/BarClock.qml
Normal file
42
modules/nexus/pages/panels/taskbar/BarClock.qml
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick.Layouts
|
||||
import Caelestia.Config
|
||||
import qs.modules.nexus.common
|
||||
|
||||
PageBase {
|
||||
id: root
|
||||
|
||||
title: qsTr("Clock")
|
||||
isSubPage: true
|
||||
|
||||
ColumnLayout {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
width: root.cappedWidth
|
||||
spacing: Tokens.spacing.extraSmall / 2
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
first: true
|
||||
text: qsTr("Background")
|
||||
checked: Config.bar.clock.background
|
||||
onToggled: GlobalConfig.bar.clock.background = checked
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Show date")
|
||||
checked: Config.bar.clock.showDate
|
||||
onToggled: GlobalConfig.bar.clock.showDate = checked
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
last: true
|
||||
text: qsTr("Show icon")
|
||||
checked: Config.bar.clock.showIcon
|
||||
onToggled: GlobalConfig.bar.clock.showIcon = checked
|
||||
}
|
||||
}
|
||||
}
|
||||
98
modules/nexus/pages/panels/taskbar/BarStatusIcons.qml
Normal file
98
modules/nexus/pages/panels/taskbar/BarStatusIcons.qml
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick.Layouts
|
||||
import Caelestia.Config
|
||||
import qs.modules.nexus.common
|
||||
|
||||
PageBase {
|
||||
id: root
|
||||
|
||||
title: qsTr("Status icons")
|
||||
isSubPage: true
|
||||
|
||||
ColumnLayout {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
width: root.cappedWidth
|
||||
spacing: Tokens.spacing.extraSmall / 2
|
||||
|
||||
// Visible icons
|
||||
SectionHeader {
|
||||
first: true
|
||||
text: qsTr("Visible icons")
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
first: true
|
||||
text: qsTr("Speakers")
|
||||
checked: Config.bar.status.showAudio
|
||||
onToggled: GlobalConfig.bar.status.showAudio = checked
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Microphone")
|
||||
checked: Config.bar.status.showMicrophone
|
||||
onToggled: GlobalConfig.bar.status.showMicrophone = checked
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Keyboard layout")
|
||||
checked: Config.bar.status.showKbLayout
|
||||
onToggled: GlobalConfig.bar.status.showKbLayout = checked
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Network")
|
||||
checked: Config.bar.status.showNetwork
|
||||
onToggled: GlobalConfig.bar.status.showNetwork = checked
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Wi-Fi")
|
||||
checked: Config.bar.status.showWifi
|
||||
onToggled: GlobalConfig.bar.status.showWifi = checked
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Bluetooth")
|
||||
checked: Config.bar.status.showBluetooth
|
||||
onToggled: GlobalConfig.bar.status.showBluetooth = checked
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Battery")
|
||||
checked: Config.bar.status.showBattery
|
||||
onToggled: GlobalConfig.bar.status.showBattery = checked
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
last: true
|
||||
text: qsTr("Caps lock")
|
||||
checked: Config.bar.status.showLockStatus
|
||||
onToggled: GlobalConfig.bar.status.showLockStatus = checked
|
||||
}
|
||||
|
||||
// Behaviour
|
||||
SectionHeader {
|
||||
text: qsTr("Behaviour")
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
first: true
|
||||
last: true
|
||||
text: qsTr("Popout on hover")
|
||||
subtext: qsTr("Show a details popout when hovering the status icons")
|
||||
checked: Config.bar.popouts.statusIcons
|
||||
onToggled: GlobalConfig.bar.popouts.statusIcons = checked
|
||||
}
|
||||
}
|
||||
}
|
||||
50
modules/nexus/pages/panels/taskbar/BarTray.qml
Normal file
50
modules/nexus/pages/panels/taskbar/BarTray.qml
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick.Layouts
|
||||
import Caelestia.Config
|
||||
import qs.modules.nexus.common
|
||||
|
||||
PageBase {
|
||||
id: root
|
||||
|
||||
title: qsTr("Tray")
|
||||
isSubPage: true
|
||||
|
||||
ColumnLayout {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
width: root.cappedWidth
|
||||
spacing: Tokens.spacing.extraSmall / 2
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
first: true
|
||||
text: qsTr("Background")
|
||||
checked: Config.bar.tray.background
|
||||
onToggled: GlobalConfig.bar.tray.background = checked
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Recolour icons")
|
||||
checked: Config.bar.tray.recolour
|
||||
onToggled: GlobalConfig.bar.tray.recolour = checked
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Compact")
|
||||
checked: Config.bar.tray.compact
|
||||
onToggled: GlobalConfig.bar.tray.compact = checked
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
last: true
|
||||
text: qsTr("Popout on hover")
|
||||
subtext: qsTr("Show the tray menu popout when hovering")
|
||||
checked: Config.bar.popouts.tray
|
||||
onToggled: GlobalConfig.bar.popouts.tray = checked
|
||||
}
|
||||
}
|
||||
}
|
||||
86
modules/nexus/pages/panels/taskbar/BarWorkspaces.qml
Normal file
86
modules/nexus/pages/panels/taskbar/BarWorkspaces.qml
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick.Layouts
|
||||
import Caelestia.Config
|
||||
import qs.modules.nexus.common
|
||||
|
||||
PageBase {
|
||||
id: root
|
||||
|
||||
title: qsTr("Workspaces")
|
||||
isSubPage: true
|
||||
|
||||
ColumnLayout {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
width: root.cappedWidth
|
||||
spacing: Tokens.spacing.extraSmall / 2
|
||||
|
||||
StepperRow {
|
||||
Layout.fillWidth: true
|
||||
first: true
|
||||
label: qsTr("Shown")
|
||||
subtext: qsTr("Number of workspaces displayed")
|
||||
value: Config.bar.workspaces.shown
|
||||
from: 1
|
||||
to: 20
|
||||
stepSize: 1
|
||||
onMoved: v => GlobalConfig.bar.workspaces.shown = v
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Active indicator")
|
||||
checked: Config.bar.workspaces.activeIndicator
|
||||
onToggled: GlobalConfig.bar.workspaces.activeIndicator = checked
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Active trail")
|
||||
checked: Config.bar.workspaces.activeTrail
|
||||
onToggled: GlobalConfig.bar.workspaces.activeTrail = checked
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Occupied background")
|
||||
checked: Config.bar.workspaces.occupiedBg
|
||||
onToggled: GlobalConfig.bar.workspaces.occupiedBg = checked
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Show windows")
|
||||
subtext: qsTr("Show icons of open windows on each workspace")
|
||||
checked: Config.bar.workspaces.showWindows
|
||||
onToggled: GlobalConfig.bar.workspaces.showWindows = checked
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Windows on special workspaces")
|
||||
checked: Config.bar.workspaces.showWindowsOnSpecialWorkspaces
|
||||
onToggled: GlobalConfig.bar.workspaces.showWindowsOnSpecialWorkspaces = checked
|
||||
}
|
||||
|
||||
StepperRow {
|
||||
Layout.fillWidth: true
|
||||
label: qsTr("Max window icons")
|
||||
value: Config.bar.workspaces.maxWindowIcons
|
||||
from: 0
|
||||
to: 20
|
||||
stepSize: 1
|
||||
onMoved: v => GlobalConfig.bar.workspaces.maxWindowIcons = v
|
||||
}
|
||||
|
||||
ToggleRow {
|
||||
Layout.fillWidth: true
|
||||
last: true
|
||||
text: qsTr("Per-monitor workspaces")
|
||||
subtext: qsTr("Show each monitor's workspaces independently")
|
||||
checked: GlobalConfig.bar.workspaces.perMonitorWorkspaces
|
||||
onToggled: GlobalConfig.bar.workspaces.perMonitorWorkspaces = checked
|
||||
}
|
||||
}
|
||||
}
|
||||
47
modules/nexus/pages/wallandstyle/ColourSelect.qml
Normal file
47
modules/nexus/pages/wallandstyle/ColourSelect.qml
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Caelestia.Config
|
||||
import qs.components
|
||||
import qs.services
|
||||
import qs.modules.nexus.common
|
||||
|
||||
PageBase {
|
||||
id: root
|
||||
|
||||
title: qsTr("Colours")
|
||||
isSubPage: true
|
||||
|
||||
Item {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
implicitHeight: {
|
||||
const f = parent.parent as Flickable;
|
||||
return f.height - f.topMargin - f.bottomMargin;
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
StyledText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: qsTr("This page will be available in a future update.")
|
||||
color: Colours.palette.m3outlineVariant
|
||||
font: Tokens.font.body.large
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
53
modules/nexus/pages/wallandstyle/WallpaperCategory.qml
Normal file
53
modules/nexus/pages/wallandstyle/WallpaperCategory.qml
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
pragma ComponentBehavior: Bound
|
||||
|
||||
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: {
|
||||
const walls = Wallpapers.list.filter(w => Wallpapers.getCategoryFor(w) === root.nState.selectedWallpaperCategory).sort((a, b) => a.name.localeCompare(b.name));
|
||||
while (walls.length < Config.nexus.wallpapersPerRow)
|
||||
walls.push(null);
|
||||
return walls;
|
||||
}
|
||||
|
||||
WallItem {
|
||||
required property FileSystemEntry modelData
|
||||
|
||||
// Empty placeholders for sizing
|
||||
opacity: modelData ? 1 : 0
|
||||
enabled: modelData
|
||||
|
||||
source: String(modelData?.path ?? "")
|
||||
text: modelData?.name ?? ""
|
||||
onClicked: {
|
||||
Wallpapers.setWallpaper(modelData.path);
|
||||
root.nState.closeSubPage();
|
||||
root.nState.closeSubPage();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
184
modules/nexus/pages/wallandstyle/WallpaperSelect.qml
Normal file
184
modules/nexus/pages/wallandstyle/WallpaperSelect.qml
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Caelestia.Components
|
||||
import Caelestia.Config
|
||||
import Caelestia.Models
|
||||
import qs.components
|
||||
import qs.components.controls
|
||||
import qs.components.filedialog
|
||||
import qs.services
|
||||
import qs.utils
|
||||
import qs.modules.nexus.common
|
||||
|
||||
PageBase {
|
||||
id: root
|
||||
|
||||
title: qsTr("Wallpapers")
|
||||
isSubPage: true
|
||||
|
||||
ColumnLayout {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
width: root.cappedWidth
|
||||
spacing: Tokens.spacing.small
|
||||
|
||||
ButtonRow {
|
||||
Layout.bottomMargin: Tokens.spacing.medium
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
spacing: Tokens.spacing.small
|
||||
|
||||
IconTextButton {
|
||||
icon: "photo_library"
|
||||
text: qsTr("Browse")
|
||||
font: Tokens.font.body.large
|
||||
isRound: true
|
||||
shapeMorph: true
|
||||
horizontalPadding: Tokens.padding.extraLarge
|
||||
verticalPadding: Tokens.padding.medium
|
||||
onClicked: browseDialog.open()
|
||||
|
||||
FileDialog {
|
||||
id: browseDialog
|
||||
|
||||
title: qsTr("Select an image")
|
||||
filterLabel: qsTr("Image files")
|
||||
filters: Images.validImageExtensions
|
||||
onAccepted: path => {
|
||||
Wallpapers.setWallpaper(path);
|
||||
root.nState.closeSubPage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IconTextButton {
|
||||
icon: "shuffle"
|
||||
text: qsTr("Random")
|
||||
font: Tokens.font.body.large
|
||||
isRound: true
|
||||
shapeMorph: true
|
||||
horizontalPadding: Tokens.padding.extraLarge
|
||||
verticalPadding: Tokens.padding.medium
|
||||
type: IconTextButton.Tonal
|
||||
onClicked: {
|
||||
Wallpapers.setRandom();
|
||||
root.nState.closeSubPage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WallItem {
|
||||
imgHeight: Math.round(width * 0.3)
|
||||
radius: Tokens.rounding.extraLarge
|
||||
source: Quickshell.shellPath("assets/wallpaper.webp")
|
||||
text: qsTr("Featured wallpaper")
|
||||
fillLabel: false
|
||||
onClicked: {
|
||||
Wallpapers.setWallpaper(Quickshell.shellPath("assets/wallpaper.webp"));
|
||||
root.nState.closeSubPage();
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
Layout.topMargin: Tokens.spacing.large
|
||||
text: qsTr("Local wallpapers")
|
||||
font: Tokens.font.title.small
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
Layout.fillWidth: true
|
||||
visible: localWalls.count > 0
|
||||
|
||||
columns: Config.nexus.wallpapersPerRow
|
||||
rowSpacing: Tokens.spacing.medium
|
||||
columnSpacing: Tokens.spacing.large
|
||||
|
||||
Repeater {
|
||||
id: localWalls
|
||||
|
||||
model: {
|
||||
const walls = Wallpapers.list;
|
||||
const baseDir = Paths.wallsdir;
|
||||
const categories = {};
|
||||
for (const w of walls) {
|
||||
if (w.parentDir !== baseDir) {
|
||||
const category = Wallpapers.getCategoryFor(w);
|
||||
if (category && (!(category in categories) || categories[category].name.localeCompare(w.name) > 0))
|
||||
categories[category] = w;
|
||||
}
|
||||
}
|
||||
const cats = Object.values(categories);
|
||||
while (cats.length < Config.nexus.wallpapersPerRow)
|
||||
cats.push(null);
|
||||
return cats;
|
||||
}
|
||||
|
||||
WallItem {
|
||||
required property FileSystemEntry modelData
|
||||
|
||||
// Empty placeholders for sizing
|
||||
opacity: modelData ? 1 : 0
|
||||
enabled: modelData
|
||||
|
||||
source: String(modelData?.path ?? "")
|
||||
text: {
|
||||
if (!modelData)
|
||||
return "";
|
||||
|
||||
if (modelData.parentDir !== Paths.wallsdir) {
|
||||
const category = Wallpapers.getCategoryFor(modelData);
|
||||
return category.slice(0, 1).toUpperCase() + category.slice(1);
|
||||
}
|
||||
return modelData.name;
|
||||
}
|
||||
onClicked: {
|
||||
if (modelData.parentDir !== Paths.wallsdir) {
|
||||
root.nState.selectedWallpaperCategory = Wallpapers.getCategoryFor(modelData);
|
||||
root.nState.openSubPage(2); // Category page
|
||||
} else {
|
||||
Wallpapers.setWallpaper(modelData.path);
|
||||
root.nState.closeSubPage();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
Layout.fillWidth: true
|
||||
|
||||
asynchronous: true
|
||||
active: localWalls.count === 0
|
||||
visible: active
|
||||
|
||||
sourceComponent: StyledRect {
|
||||
color: Colours.tPalette.m3surfaceContainer
|
||||
radius: Tokens.rounding.extraLarge
|
||||
implicitHeight: noWallsLayout.implicitHeight + Tokens.padding.extraExtraLarge * 2
|
||||
|
||||
ColumnLayout {
|
||||
id: noWallsLayout
|
||||
|
||||
anchors.centerIn: parent
|
||||
spacing: Tokens.spacing.extraSmall
|
||||
|
||||
MaterialIcon {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: "hide_image"
|
||||
color: Colours.palette.m3outline
|
||||
fontStyle: Tokens.font.icon.extraLarge
|
||||
}
|
||||
|
||||
StyledText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: qsTr("No local wallpapers found")
|
||||
color: Colours.palette.m3outline
|
||||
font: Tokens.font.title.small
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ import Caelestia.Config
|
|||
import qs.components
|
||||
import qs.components.controls
|
||||
import qs.services
|
||||
import qs.modules.nexus
|
||||
import qs.modules.bar.popouts as BarPopouts
|
||||
|
||||
StyledRect {
|
||||
|
|
@ -118,7 +119,7 @@ StyledRect {
|
|||
isToggle: false
|
||||
onClicked: {
|
||||
root.visibilities.utilities = false;
|
||||
root.popouts.detach("network");
|
||||
WindowFactory.create();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,9 @@ qml_module(caelestia
|
|||
PkgConfig::Qalculate
|
||||
)
|
||||
|
||||
# Bake the shell version in so CUtils can expose it without spawning a process
|
||||
target_compile_definitions(caelestia PRIVATE CAELESTIA_VERSION="${VERSION}")
|
||||
|
||||
add_subdirectory(Components)
|
||||
add_subdirectory(Config)
|
||||
add_subdirectory(Internal)
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ qml_module(caelestia-config
|
|||
generalconfig.hpp
|
||||
launcherconfig.hpp
|
||||
lockconfig.hpp
|
||||
nexusconfig.hpp
|
||||
notifsconfig.hpp
|
||||
osdconfig.hpp
|
||||
serviceconfig.hpp
|
||||
|
|
|
|||
|
|
@ -221,7 +221,7 @@ public:
|
|||
m_label->setDefaultFamily(sans);
|
||||
m_label->large()->setDefaults(14, QFont::Medium, vaxes);
|
||||
m_label->medium()->setDefaults(12, QFont::Medium, vaxes);
|
||||
m_label->small()->setDefaults(11, QFont::Medium, vaxes);
|
||||
m_label->small()->setDefaults(11, QFont::Normal, vaxes);
|
||||
|
||||
m_mono->setDefaultFamily(mono);
|
||||
m_mono->large()->setDefaults(16, QFont::Normal);
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
#include "launcherconfig.hpp"
|
||||
#include "lockconfig.hpp"
|
||||
#include "monitorconfigmanager.hpp"
|
||||
#include "nexusconfig.hpp"
|
||||
#include "notifsconfig.hpp"
|
||||
#include "osdconfig.hpp"
|
||||
#include "serviceconfig.hpp"
|
||||
|
|
@ -42,14 +43,15 @@ GlobalConfig::GlobalConfig(QObject* parent)
|
|||
, m_dashboard(new DashboardConfig(this))
|
||||
, m_controlCenter(new ControlCenterConfig(this))
|
||||
, m_launcher(new LauncherConfig(this))
|
||||
, m_lock(new LockConfig(this))
|
||||
, m_nexus(new NexusConfig(this))
|
||||
, m_notifs(new NotifsConfig(this))
|
||||
, m_osd(new OsdConfig(this))
|
||||
, m_session(new SessionConfig(this))
|
||||
, m_winfo(new WInfoConfig(this))
|
||||
, m_lock(new LockConfig(this))
|
||||
, m_utilities(new UtilitiesConfig(this))
|
||||
, m_sidebar(new SidebarConfig(this))
|
||||
, m_services(new ServiceConfig(this))
|
||||
, m_session(new SessionConfig(this))
|
||||
, m_sidebar(new SidebarConfig(this))
|
||||
, m_utilities(new UtilitiesConfig(this))
|
||||
, m_winfo(new WInfoConfig(this))
|
||||
, m_paths(new UserPaths(this)) {
|
||||
setupFileBackend(configDir() + QStringLiteral("shell.json"));
|
||||
}
|
||||
|
|
@ -64,14 +66,15 @@ GlobalConfig::GlobalConfig(GlobalConfig* fallback, const QString& filePath, cons
|
|||
, m_dashboard(new DashboardConfig(this))
|
||||
, m_controlCenter(new ControlCenterConfig(this))
|
||||
, m_launcher(new LauncherConfig(this))
|
||||
, m_lock(new LockConfig(this))
|
||||
, m_nexus(new NexusConfig(this))
|
||||
, m_notifs(new NotifsConfig(this))
|
||||
, m_osd(new OsdConfig(this))
|
||||
, m_session(new SessionConfig(this))
|
||||
, m_winfo(new WInfoConfig(this))
|
||||
, m_lock(new LockConfig(this))
|
||||
, m_utilities(new UtilitiesConfig(this))
|
||||
, m_sidebar(new SidebarConfig(this))
|
||||
, m_services(new ServiceConfig(this))
|
||||
, m_session(new SessionConfig(this))
|
||||
, m_sidebar(new SidebarConfig(this))
|
||||
, m_utilities(new UtilitiesConfig(this))
|
||||
, m_winfo(new WInfoConfig(this))
|
||||
, m_paths(new UserPaths(this)) {
|
||||
if (!filePath.isEmpty())
|
||||
setupFileBackend(filePath, screen);
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ class DashboardConfig;
|
|||
class GeneralConfig;
|
||||
class LauncherConfig;
|
||||
class LockConfig;
|
||||
class NexusConfig;
|
||||
class NotifsConfig;
|
||||
class OsdConfig;
|
||||
class ServiceConfig;
|
||||
|
|
@ -37,6 +38,7 @@ class GlobalConfig : public RootConfig {
|
|||
Q_MOC_INCLUDE("generalconfig.hpp")
|
||||
Q_MOC_INCLUDE("launcherconfig.hpp")
|
||||
Q_MOC_INCLUDE("lockconfig.hpp")
|
||||
Q_MOC_INCLUDE("nexusconfig.hpp")
|
||||
Q_MOC_INCLUDE("notifsconfig.hpp")
|
||||
Q_MOC_INCLUDE("osdconfig.hpp")
|
||||
Q_MOC_INCLUDE("serviceconfig.hpp")
|
||||
|
|
@ -55,14 +57,15 @@ class GlobalConfig : public RootConfig {
|
|||
CONFIG_SUBOBJECT(DashboardConfig, dashboard)
|
||||
CONFIG_SUBOBJECT(ControlCenterConfig, controlCenter)
|
||||
CONFIG_SUBOBJECT(LauncherConfig, launcher)
|
||||
CONFIG_SUBOBJECT(LockConfig, lock)
|
||||
CONFIG_SUBOBJECT(NexusConfig, nexus)
|
||||
CONFIG_SUBOBJECT(NotifsConfig, notifs)
|
||||
CONFIG_SUBOBJECT(OsdConfig, osd)
|
||||
CONFIG_SUBOBJECT(SessionConfig, session)
|
||||
CONFIG_SUBOBJECT(WInfoConfig, winfo)
|
||||
CONFIG_SUBOBJECT(LockConfig, lock)
|
||||
CONFIG_SUBOBJECT(UtilitiesConfig, utilities)
|
||||
CONFIG_SUBOBJECT(SidebarConfig, sidebar)
|
||||
CONFIG_SUBOBJECT(ServiceConfig, services)
|
||||
CONFIG_SUBOBJECT(SessionConfig, session)
|
||||
CONFIG_SUBOBJECT(SidebarConfig, sidebar)
|
||||
CONFIG_SUBOBJECT(UtilitiesConfig, utilities)
|
||||
CONFIG_SUBOBJECT(WInfoConfig, winfo)
|
||||
CONFIG_SUBOBJECT(UserPaths, paths)
|
||||
|
||||
public:
|
||||
|
|
|
|||
|
|
@ -73,14 +73,15 @@ CONFIG_ATTACHED_GETTER(BorderConfig, border)
|
|||
CONFIG_ATTACHED_GETTER(DashboardConfig, dashboard)
|
||||
CONFIG_ATTACHED_GETTER(ControlCenterConfig, controlCenter)
|
||||
CONFIG_ATTACHED_GETTER(LauncherConfig, launcher)
|
||||
CONFIG_ATTACHED_GETTER(LockConfig, lock)
|
||||
CONFIG_ATTACHED_GETTER(NexusConfig, nexus)
|
||||
CONFIG_ATTACHED_GETTER(NotifsConfig, notifs)
|
||||
CONFIG_ATTACHED_GETTER(OsdConfig, osd)
|
||||
CONFIG_ATTACHED_GETTER(SessionConfig, session)
|
||||
CONFIG_ATTACHED_GETTER(WInfoConfig, winfo)
|
||||
CONFIG_ATTACHED_GETTER(LockConfig, lock)
|
||||
CONFIG_ATTACHED_GETTER(UtilitiesConfig, utilities)
|
||||
CONFIG_ATTACHED_GETTER(SidebarConfig, sidebar)
|
||||
CONFIG_ATTACHED_GETTER(ServiceConfig, services)
|
||||
CONFIG_ATTACHED_GETTER(SessionConfig, session)
|
||||
CONFIG_ATTACHED_GETTER(SidebarConfig, sidebar)
|
||||
CONFIG_ATTACHED_GETTER(UtilitiesConfig, utilities)
|
||||
CONFIG_ATTACHED_GETTER(WInfoConfig, winfo)
|
||||
CONFIG_ATTACHED_GETTER(UserPaths, paths)
|
||||
|
||||
#undef CONFIG_ATTACHED_GETTER
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ class Config : public QQuickAttachedPropertyPropagator, public QQmlParserStatus
|
|||
Q_MOC_INCLUDE("generalconfig.hpp")
|
||||
Q_MOC_INCLUDE("launcherconfig.hpp")
|
||||
Q_MOC_INCLUDE("lockconfig.hpp")
|
||||
Q_MOC_INCLUDE("nexusconfig.hpp")
|
||||
Q_MOC_INCLUDE("notifsconfig.hpp")
|
||||
Q_MOC_INCLUDE("osdconfig.hpp")
|
||||
Q_MOC_INCLUDE("serviceconfig.hpp")
|
||||
|
|
@ -39,14 +40,15 @@ class Config : public QQuickAttachedPropertyPropagator, public QQmlParserStatus
|
|||
Q_PROPERTY(const caelestia::config::DashboardConfig* dashboard READ dashboard NOTIFY sourceChanged)
|
||||
Q_PROPERTY(const caelestia::config::ControlCenterConfig* controlCenter READ controlCenter NOTIFY sourceChanged)
|
||||
Q_PROPERTY(const caelestia::config::LauncherConfig* launcher READ launcher NOTIFY sourceChanged)
|
||||
Q_PROPERTY(const caelestia::config::LockConfig* lock READ lock NOTIFY sourceChanged)
|
||||
Q_PROPERTY(const caelestia::config::NexusConfig* nexus READ nexus NOTIFY sourceChanged)
|
||||
Q_PROPERTY(const caelestia::config::NotifsConfig* notifs READ notifs NOTIFY sourceChanged)
|
||||
Q_PROPERTY(const caelestia::config::OsdConfig* osd READ osd NOTIFY sourceChanged)
|
||||
Q_PROPERTY(const caelestia::config::SessionConfig* session READ session NOTIFY sourceChanged)
|
||||
Q_PROPERTY(const caelestia::config::WInfoConfig* winfo READ winfo NOTIFY sourceChanged)
|
||||
Q_PROPERTY(const caelestia::config::LockConfig* lock READ lock NOTIFY sourceChanged)
|
||||
Q_PROPERTY(const caelestia::config::UtilitiesConfig* utilities READ utilities NOTIFY sourceChanged)
|
||||
Q_PROPERTY(const caelestia::config::SidebarConfig* sidebar READ sidebar NOTIFY sourceChanged)
|
||||
Q_PROPERTY(const caelestia::config::ServiceConfig* services READ services NOTIFY sourceChanged)
|
||||
Q_PROPERTY(const caelestia::config::SessionConfig* session READ session NOTIFY sourceChanged)
|
||||
Q_PROPERTY(const caelestia::config::SidebarConfig* sidebar READ sidebar NOTIFY sourceChanged)
|
||||
Q_PROPERTY(const caelestia::config::UtilitiesConfig* utilities READ utilities NOTIFY sourceChanged)
|
||||
Q_PROPERTY(const caelestia::config::WInfoConfig* winfo READ winfo NOTIFY sourceChanged)
|
||||
Q_PROPERTY(const caelestia::config::UserPaths* paths READ paths NOTIFY sourceChanged)
|
||||
|
||||
public:
|
||||
|
|
@ -63,14 +65,15 @@ public:
|
|||
[[nodiscard]] const DashboardConfig* dashboard() const;
|
||||
[[nodiscard]] const ControlCenterConfig* controlCenter() const;
|
||||
[[nodiscard]] const LauncherConfig* launcher() const;
|
||||
[[nodiscard]] const LockConfig* lock() const;
|
||||
[[nodiscard]] const NexusConfig* nexus() const;
|
||||
[[nodiscard]] const NotifsConfig* notifs() const;
|
||||
[[nodiscard]] const OsdConfig* osd() const;
|
||||
[[nodiscard]] const SessionConfig* session() const;
|
||||
[[nodiscard]] const WInfoConfig* winfo() const;
|
||||
[[nodiscard]] const LockConfig* lock() const;
|
||||
[[nodiscard]] const UtilitiesConfig* utilities() const;
|
||||
[[nodiscard]] const SidebarConfig* sidebar() const;
|
||||
[[nodiscard]] const ServiceConfig* services() const;
|
||||
[[nodiscard]] const SessionConfig* session() const;
|
||||
[[nodiscard]] const SidebarConfig* sidebar() const;
|
||||
[[nodiscard]] const UtilitiesConfig* utilities() const;
|
||||
[[nodiscard]] const WInfoConfig* winfo() const;
|
||||
[[nodiscard]] const UserPaths* paths() const;
|
||||
|
||||
[[nodiscard]] Q_INVOKABLE static GlobalConfig* forScreen(const QString& screen);
|
||||
|
|
|
|||
19
plugin/src/Caelestia/Config/nexusconfig.hpp
Normal file
19
plugin/src/Caelestia/Config/nexusconfig.hpp
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
#pragma once
|
||||
|
||||
#include "configobject.hpp"
|
||||
|
||||
namespace caelestia::config {
|
||||
|
||||
class NexusConfig : public ConfigObject {
|
||||
Q_OBJECT
|
||||
QML_ANONYMOUS
|
||||
|
||||
CONFIG_PROPERTY(int, wallpapersPerRow, 4)
|
||||
CONFIG_GLOBAL_PROPERTY(int, networkRescanInterval, 15000)
|
||||
|
||||
public:
|
||||
explicit NexusConfig(QObject* parent = nullptr)
|
||||
: ConfigObject(parent) {}
|
||||
};
|
||||
|
||||
} // namespace caelestia::config
|
||||
|
|
@ -32,7 +32,6 @@ class ServiceConfig : public ConfigObject {
|
|||
CONFIG_GLOBAL_PROPERTY(QString, defaultPlayer, u"Spotify"_s)
|
||||
CONFIG_GLOBAL_PROPERTY(QVariantList, playerAliases,
|
||||
{ vmap({ { u"from"_s, u"com.github.th_ch.youtube_music"_s }, { u"to"_s, u"YT Music"_s } }) })
|
||||
CONFIG_GLOBAL_PROPERTY(bool, showLyrics, false)
|
||||
CONFIG_GLOBAL_PROPERTY(QString, lyricsBackend, u"Auto"_s)
|
||||
|
||||
public:
|
||||
|
|
|
|||
|
|
@ -323,6 +323,21 @@ public:
|
|||
: ConfigObject(parent) {}
|
||||
};
|
||||
|
||||
class NexusTokens : public ConfigObject {
|
||||
Q_OBJECT
|
||||
QML_ANONYMOUS
|
||||
|
||||
CONFIG_PROPERTY(qreal, heightMult, 0.7)
|
||||
CONFIG_PROPERTY(qreal, ratio, 16.0 / 9.0)
|
||||
CONFIG_PROPERTY(int, minWidth, 800)
|
||||
CONFIG_PROPERTY(int, minHeight, 500)
|
||||
CONFIG_PROPERTY(int, maxNavWidth, 600)
|
||||
|
||||
public:
|
||||
explicit NexusTokens(QObject* parent = nullptr)
|
||||
: ConfigObject(parent) {}
|
||||
};
|
||||
|
||||
class SizeTokens : public ConfigObject {
|
||||
Q_OBJECT
|
||||
QML_ANONYMOUS
|
||||
|
|
@ -338,6 +353,7 @@ class SizeTokens : public ConfigObject {
|
|||
CONFIG_SUBOBJECT(LockTokens, lock)
|
||||
CONFIG_SUBOBJECT(WInfoTokens, winfo)
|
||||
CONFIG_SUBOBJECT(ControlCenterTokens, controlCenter)
|
||||
CONFIG_SUBOBJECT(NexusTokens, nexus)
|
||||
|
||||
public:
|
||||
explicit SizeTokens(QObject* parent = nullptr)
|
||||
|
|
@ -352,7 +368,8 @@ public:
|
|||
, m_utilities(new UtilitiesTokens(this))
|
||||
, m_lock(new LockTokens(this))
|
||||
, m_winfo(new WInfoTokens(this))
|
||||
, m_controlCenter(new ControlCenterTokens(this)) {}
|
||||
, m_controlCenter(new ControlCenterTokens(this))
|
||||
, m_nexus(new NexusTokens(this)) {}
|
||||
};
|
||||
|
||||
class TokenConfig : public RootConfig {
|
||||
|
|
|
|||
|
|
@ -142,4 +142,16 @@ qreal CUtils::clamp(qreal value, qreal min, qreal max) {
|
|||
return qBound(min, value, max);
|
||||
}
|
||||
|
||||
#ifndef CAELESTIA_VERSION
|
||||
#define CAELESTIA_VERSION ""
|
||||
#endif
|
||||
|
||||
QString CUtils::version() const {
|
||||
return QStringLiteral(CAELESTIA_VERSION);
|
||||
}
|
||||
|
||||
QString CUtils::qtVersion() const {
|
||||
return QStringLiteral(QT_VERSION_STR);
|
||||
}
|
||||
|
||||
} // namespace caelestia
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@ class CUtils : public QObject {
|
|||
QML_ELEMENT
|
||||
QML_SINGLETON
|
||||
|
||||
Q_PROPERTY(QString version READ version CONSTANT)
|
||||
Q_PROPERTY(QString qtVersion READ qtVersion CONSTANT)
|
||||
|
||||
public:
|
||||
// clang-format off
|
||||
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path);
|
||||
|
|
@ -26,6 +29,9 @@ public:
|
|||
Q_INVOKABLE static QString toLocalFile(const QUrl& url);
|
||||
|
||||
Q_INVOKABLE static qreal clamp(qreal value, qreal min, qreal max);
|
||||
|
||||
[[nodiscard]] QString version() const;
|
||||
[[nodiscard]] QString qtVersion() const;
|
||||
};
|
||||
|
||||
} // namespace caelestia
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ Singleton {
|
|||
property var wirelessInterfaces: []
|
||||
property var ethernetInterfaces: []
|
||||
property bool isConnected: false
|
||||
readonly property bool connecting: wirelessInterfaces.some(i => isConnectingState(i.state))
|
||||
property string activeInterface: ""
|
||||
property string activeConnection: ""
|
||||
property bool wifiEnabled: true
|
||||
|
|
@ -166,6 +167,15 @@ Singleton {
|
|||
return state === "100 (connected)" || state === "connected" || state.startsWith("connected");
|
||||
}
|
||||
|
||||
function isConnectingState(state: string): bool {
|
||||
return !!state && state.startsWith("connecting");
|
||||
}
|
||||
|
||||
function connectingSsid(): string {
|
||||
const iface = root.wirelessInterfaces.find(i => isConnectingState(i.state));
|
||||
return iface ? iface.connection : "";
|
||||
}
|
||||
|
||||
function executeCommand(args: list<string>, callback: var): void {
|
||||
const proc = commandProc.createObject(root);
|
||||
proc.cmdArgs = ["nmcli", ...args];
|
||||
|
|
|
|||
|
|
@ -20,6 +20,17 @@ Searcher {
|
|||
property string actualCurrent
|
||||
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 setRandom(): void {
|
||||
Quickshell.execDetached(["caelestia", "wallpaper", "-r", ...smartArg]);
|
||||
}
|
||||
|
||||
function setWallpaper(path: string): void {
|
||||
actualCurrent = path;
|
||||
Quickshell.execDetached(["caelestia", "wallpaper", "-f", path, ...smartArg]);
|
||||
|
|
|
|||
|
|
@ -21,6 +21,28 @@ Singleton {
|
|||
readonly property string wm: Quickshell.env("XDG_CURRENT_DESKTOP") || Quickshell.env("XDG_SESSION_DESKTOP")
|
||||
readonly property string shell: Quickshell.env("SHELL").split("/").pop()
|
||||
|
||||
property string kernel
|
||||
property string hostname
|
||||
property string firmware
|
||||
|
||||
// DMI vendor/model, combined into a single human-readable device name
|
||||
property string boardVendor
|
||||
property string boardName
|
||||
readonly property string device: {
|
||||
if (!boardName)
|
||||
return boardVendor;
|
||||
if (!boardVendor || boardName.toLowerCase().startsWith(boardVendor.toLowerCase()))
|
||||
return boardName;
|
||||
return `${boardVendor} ${boardName}`;
|
||||
}
|
||||
|
||||
// Strips the placeholder strings OEMs commonly leave in DMI fields
|
||||
function sanitiseDmi(s: string): string {
|
||||
const t = s.trim();
|
||||
const junk = ["to be filled by o.e.m.", "system product name", "system manufacturer", "system version", "default string", "o.e.m.", "not specified", "not applicable", "unknown", "none", ""];
|
||||
return junk.includes(t.toLowerCase()) ? "" : t;
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: osRelease
|
||||
|
||||
|
|
@ -57,6 +79,34 @@ Singleton {
|
|||
target: GlobalConfig.general
|
||||
}
|
||||
|
||||
FileView {
|
||||
path: "/proc/sys/kernel/osrelease"
|
||||
onLoaded: root.kernel = text().trim()
|
||||
}
|
||||
|
||||
FileView {
|
||||
path: "/proc/sys/kernel/hostname"
|
||||
onLoaded: root.hostname = text().trim()
|
||||
}
|
||||
|
||||
FileView {
|
||||
path: "/sys/class/dmi/id/sys_vendor"
|
||||
printErrors: false
|
||||
onLoaded: root.boardVendor = root.sanitiseDmi(text())
|
||||
}
|
||||
|
||||
FileView {
|
||||
path: "/sys/class/dmi/id/product_name"
|
||||
printErrors: false
|
||||
onLoaded: root.boardName = root.sanitiseDmi(text())
|
||||
}
|
||||
|
||||
FileView {
|
||||
path: "/sys/class/dmi/id/bios_version"
|
||||
printErrors: false
|
||||
onLoaded: root.firmware = root.sanitiseDmi(text())
|
||||
}
|
||||
|
||||
Timer {
|
||||
running: true
|
||||
repeat: true
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue