feat: completely remove control center

This commit is contained in:
2 * r + 2 * t 2026-06-07 22:38:43 +10:00
parent 1b4f81f078
commit dca1d63717
66 changed files with 2 additions and 11599 deletions

View file

@ -609,7 +609,7 @@ For example, to disable the bar on DP-1:
"name": "Settings", "name": "Settings",
"icon": "settings", "icon": "settings",
"description": "Configure the shell", "description": "Configure the shell",
"command": ["caelestia", "shell", "controlCenter", "open"], "command": ["caelestia", "shell", "nexus", "open"],
"enabled": true, "enabled": true,
"dangerous": false "dangerous": false
} }

View file

@ -1,99 +0,0 @@
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
required property ShellScreen screen
readonly property int rounding: floating ? 0 : Tokens.rounding.extraLarge
property alias floating: session.floating
property alias active: session.active
property alias navExpanded: session.navExpanded
readonly property bool initialOpeningComplete: panes.initialOpeningComplete
readonly property Session session: Session {
id: session
root: root
}
signal close
implicitWidth: implicitHeight * Tokens.sizes.controlCenter.ratio
implicitHeight: screen.height * Tokens.sizes.controlCenter.heightMult
GridLayout {
anchors.fill: parent
rowSpacing: 0
columnSpacing: 0
rows: root.floating ? 2 : 1
columns: 2
Loader {
Layout.fillWidth: true
Layout.columnSpan: 2
asynchronous: true
active: root.floating
visible: active
sourceComponent: WindowTitle {
screen: root.screen
session: root.session
}
}
StyledRect {
Layout.fillHeight: true
topLeftRadius: root.rounding
bottomLeftRadius: root.rounding
implicitWidth: navRail.implicitWidth
color: Colours.tPalette.m3surfaceContainer
CustomMouseArea {
function onWheel(event: WheelEvent): void {
// Prevent tab switching during initial opening animation to avoid blank pages
if (!panes.initialOpeningComplete) {
return;
}
if (event.angleDelta.y < 0)
root.session.activeIndex = Math.min(root.session.activeIndex + 1, root.session.panes.length - 1);
else if (event.angleDelta.y > 0)
root.session.activeIndex = Math.max(root.session.activeIndex - 1, 0);
}
anchors.fill: parent
}
NavRail {
id: navRail
screen: root.screen
session: root.session
initialOpeningComplete: root.initialOpeningComplete
}
}
Panes {
id: panes
Layout.fillWidth: true
Layout.fillHeight: true
topRightRadius: root.rounding
bottomRightRadius: root.rounding
session: root.session
}
}
}

View file

@ -1,226 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import Caelestia.Config
import qs.components
import qs.services
import qs.modules.controlcenter
Item {
id: root
required property ShellScreen screen
required property Session session
required property bool initialOpeningComplete
implicitWidth: layout.implicitWidth + Tokens.padding.extraExtraLarge
implicitHeight: layout.implicitHeight + Tokens.padding.extraLargeIncreased
ColumnLayout {
id: layout
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Tokens.padding.medium * 2
spacing: Tokens.spacing.medium
states: State {
name: "expanded"
when: root.session.navExpanded
PropertyChanges {
layout.spacing: root.Tokens.spacing.small
}
}
transitions: Transition {
Anim {
properties: "spacing"
}
}
Loader {
Layout.topMargin: Tokens.spacing.largeIncreased
asynchronous: true
active: !root.session.floating
visible: active
sourceComponent: StyledRect {
readonly property int nonAnimWidth: normalWinIcon.implicitWidth + (root.session.navExpanded ? normalWinLabel.anchors.leftMargin + normalWinLabel.implicitWidth : 0) + normalWinIcon.anchors.leftMargin * 2
implicitWidth: nonAnimWidth
implicitHeight: root.session.navExpanded ? normalWinIcon.implicitHeight + Tokens.padding.medium * 2 : nonAnimWidth
color: Colours.palette.m3primaryContainer
radius: Tokens.rounding.medium
StateLayer {
id: normalWinState
onClicked: {
root.session.root.close();
WindowFactory.create(null, {
active: root.session.active,
navExpanded: root.session.navExpanded
});
}
color: Colours.palette.m3onPrimaryContainer
}
MaterialIcon {
id: normalWinIcon
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Tokens.padding.large
text: "select_window"
color: Colours.palette.m3onPrimaryContainer
fontStyle: Tokens.font.icon.large
fill: 1
}
StyledText {
id: normalWinLabel
anchors.left: normalWinIcon.right
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Tokens.spacing.medium
text: qsTr("Float window")
color: Colours.palette.m3onPrimaryContainer
opacity: root.session.navExpanded ? 1 : 0
Behavior on opacity {
Anim {
type: Anim.StandardSmall
}
}
}
Behavior on implicitWidth {
Anim {}
}
Behavior on implicitHeight {
Anim {}
}
}
}
Repeater {
model: PaneRegistry.count
NavItem {
required property int index
Layout.topMargin: index === 0 ? Tokens.spacing.largeIncreased * 2 : 0
icon: PaneRegistry.getByIndex(index).icon
label: PaneRegistry.getByIndex(index).label
}
}
}
component NavItem: Item {
id: item
required property string icon
required property string label
readonly property bool active: root.session.active === label
implicitWidth: background.implicitWidth
implicitHeight: background.implicitHeight + smallLabel.implicitHeight + smallLabel.anchors.topMargin
states: State {
name: "expanded"
when: root.session.navExpanded
PropertyChanges {
expandedLabel.opacity: 1
smallLabel.opacity: 0
background.implicitWidth: icon.implicitWidth + icon.anchors.leftMargin * 2 + expandedLabel.anchors.leftMargin + expandedLabel.implicitWidth
background.implicitHeight: icon.implicitHeight + root.Tokens.padding.medium * 2
item.implicitHeight: background.implicitHeight
}
}
transitions: Transition {
Anim {
property: "opacity"
type: Anim.StandardSmall
}
Anim {
properties: "implicitWidth,implicitHeight"
}
}
StyledRect {
id: background
radius: Tokens.rounding.full
color: Qt.alpha(Colours.palette.m3secondaryContainer, item.active ? 1 : 0)
implicitWidth: icon.implicitWidth + icon.anchors.leftMargin * 2
implicitHeight: icon.implicitHeight + Tokens.padding.extraSmall
StateLayer {
onClicked: {
// Prevent tab switching during initial opening animation to avoid blank pages
if (!root.initialOpeningComplete) {
return;
}
root.session.active = item.label;
}
color: item.active ? Colours.palette.m3onSecondaryContainer : Colours.palette.m3onSurface
}
MaterialIcon {
id: icon
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Tokens.padding.large
text: item.icon
color: item.active ? Colours.palette.m3onSecondaryContainer : Colours.palette.m3onSurface
fontStyle: Tokens.font.icon.large
fill: item.active ? 1 : 0
Behavior on fill {
Anim {
type: Anim.DefaultEffects
}
}
}
StyledText {
id: expandedLabel
anchors.left: icon.right
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Tokens.spacing.medium
opacity: 0
text: item.label
color: item.active ? Colours.palette.m3onSecondaryContainer : Colours.palette.m3onSurface
font: Tokens.font.label.builders.medium.capitalisation(Font.Capitalize).build()
}
StyledText {
id: smallLabel
anchors.horizontalCenter: icon.horizontalCenter
anchors.top: icon.bottom
anchors.topMargin: Tokens.spacing.extraSmall
text: item.label
font: Tokens.font.label.builders.small.capitalisation(Font.Capitalize).build()
}
}
}
}

View file

@ -1,98 +0,0 @@
pragma Singleton
import QtQuick
QtObject {
id: root
readonly property list<QtObject> panes: [
QtObject {
readonly property string id: "network"
readonly property string label: "network"
readonly property string icon: "router"
readonly property string component: "network/NetworkingPane.qml"
},
QtObject {
readonly property string id: "bluetooth"
readonly property string label: "bluetooth"
readonly property string icon: "settings_bluetooth"
readonly property string component: "bluetooth/BtPane.qml"
},
QtObject {
readonly property string id: "audio"
readonly property string label: "audio"
readonly property string icon: "volume_up"
readonly property string component: "audio/AudioPane.qml"
},
QtObject {
readonly property string id: "appearance"
readonly property string label: "appearance"
readonly property string icon: "palette"
readonly property string component: "appearance/AppearancePane.qml"
},
QtObject {
readonly property string id: "taskbar"
readonly property string label: "taskbar"
readonly property string icon: "task_alt"
readonly property string component: "taskbar/TaskbarPane.qml"
},
QtObject {
readonly property string id: "notifications"
readonly property string label: "notifications"
readonly property string icon: "notifications"
readonly property string component: "notifications/NotificationsPane.qml"
},
QtObject {
readonly property string id: "launcher"
readonly property string label: "launcher"
readonly property string icon: "apps"
readonly property string component: "launcher/LauncherPane.qml"
},
QtObject {
readonly property string id: "dashboard"
readonly property string label: "dashboard"
readonly property string icon: "dashboard"
readonly property string component: "dashboard/DashboardPane.qml"
}
]
readonly property int count: panes.length
readonly property var labels: {
const result = [];
for (let i = 0; i < panes.length; i++) {
result.push(panes[i].label);
}
return result;
}
function getByIndex(index: int): var {
if (index >= 0 && index < panes.length) {
return panes[index];
}
return null;
}
function getIndexByLabel(label: string): int {
for (let i = 0; i < panes.length; i++) {
if (panes[i].label === label) {
return i;
}
}
return -1;
}
function getByLabel(label: string): var {
const index = getIndexByLabel(label);
return getByIndex(index);
}
function getById(id: string): var {
for (let i = 0; i < panes.length; i++) {
if (panes[i].id === id) {
return panes[i];
}
}
return null;
}
}

View file

@ -1,182 +0,0 @@
pragma ComponentBehavior: Bound
import "bluetooth"
import "network"
import "audio"
import "appearance"
import "taskbar"
import "notifications"
import "launcher"
import "dashboard"
import QtQuick
import QtQuick.Layouts
import Quickshell.Widgets
import Caelestia.Config
import qs.components
import qs.services
import qs.modules.controlcenter
ClippingRectangle {
id: root
required property Session session
readonly property bool initialOpeningComplete: layout.initialOpeningComplete
color: "transparent"
clip: true
focus: false
activeFocusOnTab: false
MouseArea {
anchors.fill: parent
z: -1
onPressed: function (mouse) {
root.focus = true;
mouse.accepted = false;
}
}
Connections {
function onActiveIndexChanged(): void {
root.focus = true;
}
target: root.session
}
ColumnLayout {
id: layout
property bool animationComplete: true
property bool initialOpeningComplete: false
spacing: 0
y: -root.session.activeIndex * root.height
clip: true
Timer {
id: animationDelayTimer
interval: Tokens.anim.durations.normal
onTriggered: {
layout.animationComplete = true;
}
}
Timer {
id: initialOpeningTimer
interval: Tokens.anim.durations.large
running: true
onTriggered: {
layout.initialOpeningComplete = true;
}
}
Repeater {
model: PaneRegistry.count
Pane {
required property int index
paneIndex: index
componentPath: PaneRegistry.getByIndex(index).component
}
}
Behavior on y {
Anim {}
}
Connections {
function onActiveIndexChanged(): void {
layout.animationComplete = false;
animationDelayTimer.restart();
}
target: root.session
}
}
component Pane: Item {
id: pane
required property int paneIndex
required property string componentPath
property bool hasBeenLoaded: false
function updateActive(): void {
const diff = Math.abs(root.session.activeIndex - pane.paneIndex);
const isActivePane = diff === 0;
let shouldBeActive = false;
if (!layout.initialOpeningComplete) {
shouldBeActive = isActivePane;
} else {
if (diff <= 1) {
shouldBeActive = true;
} else if (pane.hasBeenLoaded) {
shouldBeActive = true;
} else {
shouldBeActive = layout.animationComplete;
}
}
loader.active = shouldBeActive;
}
implicitWidth: root.width
implicitHeight: root.height
Loader {
id: loader
anchors.fill: parent
asynchronous: true
clip: false
active: false
Component.onCompleted: {
Qt.callLater(pane.updateActive);
}
onActiveChanged: {
if (active && !pane.hasBeenLoaded) {
pane.hasBeenLoaded = true;
}
if (active && !item) {
loader.setSource(pane.componentPath, {
"session": root.session
});
}
}
onItemChanged: {
if (item) {
pane.hasBeenLoaded = true;
}
}
}
Connections {
function onActiveIndexChanged(): void {
pane.updateActive();
}
target: root.session
}
Connections {
function onInitialOpeningCompleteChanged(): void {
pane.updateActive();
}
function onAnimationCompleteChanged(): void {
pane.updateActive();
}
target: layout
}
}
}

View file

@ -1,23 +0,0 @@
import "./state"
import QtQuick
import qs.modules.controlcenter
QtObject {
readonly property list<string> panes: PaneRegistry.labels
required property var root
property bool floating: false
property string active: "network"
property int activeIndex: 0
property bool navExpanded: false
readonly property BluetoothState bt: BluetoothState {}
readonly property NetworkState network: NetworkState {}
readonly property EthernetState ethernet: EthernetState {}
readonly property LauncherState launcher: LauncherState {}
readonly property VpnState vpn: VpnState {}
onActiveChanged: activeIndex = Math.max(0, panes.indexOf(active))
onActiveIndexChanged: if (panes[activeIndex])
active = panes[activeIndex]
}

View file

@ -1,59 +0,0 @@
pragma Singleton
import QtQuick
import Quickshell
import qs.components
import qs.services
Singleton {
id: root
function create(parent: Item, props: var): void {
controlCenter.createObject(parent ?? dummy, props);
}
QtObject {
id: dummy
}
Component {
id: controlCenter
FloatingWindow {
id: win
property alias active: cc.active
property alias navExpanded: cc.navExpanded
color: Colours.tPalette.m3surface
onVisibleChanged: {
if (!visible)
destroy();
}
implicitWidth: cc.implicitWidth
implicitHeight: cc.implicitHeight
minimumSize.width: implicitWidth
minimumSize.height: implicitHeight
maximumSize.width: implicitWidth
maximumSize.height: implicitHeight
title: qsTr("Caelestia Settings - %1").arg(cc.active.slice(0, 1).toUpperCase() + cc.active.slice(1))
ControlCenter {
id: cc
anchors.fill: parent
screen: win.screen
onClose: win.destroy()
floating: true
}
Behavior on color {
CAnim {}
}
}
}
}

View file

@ -1,49 +0,0 @@
import QtQuick
import Quickshell
import Caelestia.Config
import qs.components
import qs.services
StyledRect {
id: root
required property ShellScreen screen
required property Session session
implicitHeight: text.implicitHeight + Tokens.padding.medium
color: Colours.tPalette.m3surfaceContainer
StyledText {
id: text
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
text: qsTr("Caelestia Settings - %1").arg(root.session.active)
font: Tokens.font.title.builders.medium.capitalisation(Font.Capitalize).weight(Font.Medium).build()
}
Item {
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: Tokens.padding.medium
implicitWidth: implicitHeight
implicitHeight: closeIcon.implicitHeight + Tokens.padding.extraSmall
StateLayer {
onClicked: {
QsWindow.window.destroy();
}
radius: Tokens.rounding.full
}
MaterialIcon {
id: closeIcon
anchors.centerIn: parent
text: "close"
}
}
}

View file

@ -1,265 +0,0 @@
pragma ComponentBehavior: Bound
import ".."
import "../components"
import "./sections"
import "../../launcher/services"
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Widgets
import Caelestia.Config
import Caelestia.Models
import qs.components
import qs.components.containers
import qs.components.controls
import qs.components.effects
import qs.components.images
import qs.services
import qs.utils
Item {
id: root
required property Session session
property real animDurationsScale: GlobalConfig.appearance.anim.durations.scale ?? 1
property string fontFamilyMaterial: Config.appearance.font.family.material ?? "Material Symbols Rounded"
property string fontFamilyMono: Config.appearance.font.family.mono ?? "CaskaydiaCove NF"
property string fontFamilySans: Config.appearance.font.family.sans ?? "Rubik"
property real fontSizeScale: Config.appearance.font.size.scale ?? 1
property real paddingScale: Config.appearance.padding.scale ?? 1
property real roundingScale: Config.appearance.rounding.scale ?? 1
property real spacingScale: Config.appearance.spacing.scale ?? 1
property bool transparencyEnabled: GlobalConfig.appearance.transparency.enabled ?? false
property real transparencyBase: GlobalConfig.appearance.transparency.base ?? 0.85
property real transparencyLayers: GlobalConfig.appearance.transparency.layers ?? 0.4
property real borderRounding: Config.border.rounding ?? 1
property real borderThickness: Config.border.thickness ?? 1
property bool desktopClockEnabled: Config.background.desktopClock.enabled ?? false
property real desktopClockScale: Config.background.desktopClock.scale ?? 1
property string desktopClockPosition: Config.background.desktopClock.position ?? "bottom-right"
property bool desktopClockShadowEnabled: Config.background.desktopClock.shadow.enabled ?? true
property real desktopClockShadowOpacity: Config.background.desktopClock.shadow.opacity ?? 0.7
property real desktopClockShadowBlur: Config.background.desktopClock.shadow.blur ?? 0.4
property bool desktopClockBackgroundEnabled: Config.background.desktopClock.background.enabled ?? false
property real desktopClockBackgroundOpacity: Config.background.desktopClock.background.opacity ?? 0.7
property bool desktopClockBackgroundBlur: Config.background.desktopClock.background.blur ?? false
property bool desktopClockInvertColors: Config.background.desktopClock.invertColors ?? false
property bool backgroundEnabled: Config.background.enabled ?? true
property bool wallpaperEnabled: Config.background.wallpaperEnabled ?? true
property bool visualiserEnabled: Config.background.visualiser.enabled ?? false
property bool visualiserAutoHide: Config.background.visualiser.autoHide ?? true
property real visualiserRounding: Config.background.visualiser.rounding ?? 1
property real visualiserSpacing: Config.background.visualiser.spacing ?? 1
function saveConfig() {
GlobalConfig.appearance.anim.durations.scale = root.animDurationsScale;
GlobalConfig.appearance.font.family.material = root.fontFamilyMaterial;
GlobalConfig.appearance.font.family.mono = root.fontFamilyMono;
GlobalConfig.appearance.font.family.sans = root.fontFamilySans;
GlobalConfig.appearance.font.size.scale = root.fontSizeScale;
GlobalConfig.appearance.padding.scale = root.paddingScale;
GlobalConfig.appearance.rounding.scale = root.roundingScale;
GlobalConfig.appearance.spacing.scale = root.spacingScale;
GlobalConfig.appearance.transparency.enabled = root.transparencyEnabled;
GlobalConfig.appearance.transparency.base = root.transparencyBase;
GlobalConfig.appearance.transparency.layers = root.transparencyLayers;
GlobalConfig.background.desktopClock.enabled = root.desktopClockEnabled;
GlobalConfig.background.enabled = root.backgroundEnabled;
GlobalConfig.background.desktopClock.scale = root.desktopClockScale;
GlobalConfig.background.desktopClock.position = root.desktopClockPosition;
GlobalConfig.background.desktopClock.shadow.enabled = root.desktopClockShadowEnabled;
GlobalConfig.background.desktopClock.shadow.opacity = root.desktopClockShadowOpacity;
GlobalConfig.background.desktopClock.shadow.blur = root.desktopClockShadowBlur;
GlobalConfig.background.desktopClock.background.enabled = root.desktopClockBackgroundEnabled;
GlobalConfig.background.desktopClock.background.opacity = root.desktopClockBackgroundOpacity;
GlobalConfig.background.desktopClock.background.blur = root.desktopClockBackgroundBlur;
GlobalConfig.background.desktopClock.invertColors = root.desktopClockInvertColors;
GlobalConfig.background.wallpaperEnabled = root.wallpaperEnabled;
GlobalConfig.background.visualiser.enabled = root.visualiserEnabled;
GlobalConfig.background.visualiser.autoHide = root.visualiserAutoHide;
GlobalConfig.background.visualiser.rounding = root.visualiserRounding;
GlobalConfig.background.visualiser.spacing = root.visualiserSpacing;
GlobalConfig.border.rounding = root.borderRounding;
GlobalConfig.border.thickness = root.borderThickness;
}
anchors.fill: parent
Component {
id: appearanceRightContentComponent
Item {
id: rightAppearanceFlickable
ColumnLayout {
id: contentLayout
anchors.fill: parent
spacing: 0
StyledText {
Layout.alignment: Qt.AlignHCenter
Layout.bottomMargin: Tokens.spacing.medium
text: qsTr("Wallpaper")
font: Tokens.font.body.builders.large.size(28).weight(Font.DemiBold).build()
}
Loader {
id: wallpaperLoader
Layout.fillWidth: true
Layout.fillHeight: true
Layout.bottomMargin: -Tokens.padding.extraLargeIncreased
asynchronous: true
active: {
const isActive = root.session.activeIndex === 3;
const isAdjacent = Math.abs(root.session.activeIndex - 3) === 1;
const splitLayout = root.children[0];
const loader = splitLayout && splitLayout.rightLoader ? splitLayout.rightLoader : null;
const shouldActivate = loader && loader.item !== null && (isActive || isAdjacent);
return shouldActivate;
}
onStatusChanged: {
if (status === Loader.Error) {
console.error(lc, "Wallpaper loader error!");
}
}
sourceComponent: WallpaperGrid {
session: root.session
}
}
}
}
}
SplitPaneLayout {
anchors.fill: parent
leftContent: Component {
StyledFlickable {
id: sidebarFlickable
readonly property var rootPane: root
flickableDirection: Flickable.VerticalFlick
contentHeight: sidebarLayout.height
StyledScrollBar.vertical: StyledScrollBar {
flickable: sidebarFlickable
}
ColumnLayout {
id: sidebarLayout
readonly property var rootPane: sidebarFlickable.rootPane
readonly property bool allSectionsExpanded: themeModeSection.expanded && colorVariantSection.expanded && colorSchemeSection.expanded && animationsSection.expanded && fontsSection.expanded && scalesSection.expanded && transparencySection.expanded && borderSection.expanded && backgroundSection.expanded
anchors.left: parent.left
anchors.right: parent.right
spacing: Tokens.spacing.small
RowLayout {
spacing: Tokens.spacing.medium
StyledText {
text: qsTr("Appearance")
font: Tokens.font.body.builders.large.weight(Font.Medium).build()
}
Item {
Layout.fillWidth: true
}
IconButton {
icon: sidebarLayout.allSectionsExpanded ? "unfold_less" : "unfold_more"
type: IconButton.Text
label.animate: true
onClicked: {
const shouldExpand = !sidebarLayout.allSectionsExpanded;
themeModeSection.expanded = shouldExpand;
colorVariantSection.expanded = shouldExpand;
colorSchemeSection.expanded = shouldExpand;
animationsSection.expanded = shouldExpand;
fontsSection.expanded = shouldExpand;
scalesSection.expanded = shouldExpand;
transparencySection.expanded = shouldExpand;
borderSection.expanded = shouldExpand;
backgroundSection.expanded = shouldExpand;
}
}
}
ThemeModeSection {
id: themeModeSection
}
ColorVariantSection {
id: colorVariantSection
}
ColorSchemeSection {
id: colorSchemeSection
}
AnimationsSection {
id: animationsSection
rootPane: sidebarFlickable.rootPane
}
FontsSection {
id: fontsSection
rootPane: sidebarFlickable.rootPane
}
ScalesSection {
id: scalesSection
rootPane: sidebarFlickable.rootPane
}
TransparencySection {
id: transparencySection
rootPane: sidebarFlickable.rootPane
}
BorderSection {
id: borderSection
rootPane: sidebarFlickable.rootPane
}
BackgroundSection {
id: backgroundSection
rootPane: sidebarFlickable.rootPane
}
}
}
}
rightContent: appearanceRightContentComponent
}
LoggingCategory {
id: lc
name: "caelestia.qml.controlcenter.appearance"
defaultLogLevel: LoggingCategory.Info
}
}

View file

@ -1,44 +0,0 @@
pragma ComponentBehavior: Bound
import ".."
import "../../components"
import QtQuick
import QtQuick.Layouts
import Caelestia.Config
import qs.components
import qs.components.containers
import qs.components.controls
import qs.services
CollapsibleSection {
id: root
required property var rootPane
title: qsTr("Animations")
showBackground: true
SectionContainer {
contentSpacing: Tokens.spacing.medium
SliderInput {
Layout.fillWidth: true
label: qsTr("Animation duration scale")
value: rootPane.animDurationsScale
from: 0.1
to: 5.0
decimals: 1
suffix: "×"
validator: DoubleValidator {
bottom: 0.1
top: 5.0
}
onValueModified: newValue => {
rootPane.animDurationsScale = newValue;
rootPane.saveConfig();
}
}
}
}

View file

@ -1,346 +0,0 @@
pragma ComponentBehavior: Bound
import ".."
import "../../components"
import QtQuick
import QtQuick.Layouts
import Caelestia.Config
import qs.components
import qs.components.containers
import qs.components.controls
import qs.services
CollapsibleSection {
id: root
required property var rootPane
title: qsTr("Background")
showBackground: true
SwitchRow {
label: qsTr("Background enabled")
checked: rootPane.backgroundEnabled
onToggled: checked => {
rootPane.backgroundEnabled = checked;
rootPane.saveConfig();
}
}
SwitchRow {
label: qsTr("Wallpaper enabled")
checked: rootPane.wallpaperEnabled
onToggled: checked => {
rootPane.wallpaperEnabled = checked;
rootPane.saveConfig();
}
}
StyledText {
Layout.topMargin: Tokens.spacing.medium
text: qsTr("Desktop Clock")
font: Tokens.font.body.builders.large.weight(Font.Medium).build()
}
SwitchRow {
label: qsTr("Desktop Clock enabled")
checked: rootPane.desktopClockEnabled
onToggled: checked => {
rootPane.desktopClockEnabled = checked;
rootPane.saveConfig();
}
}
SectionContainer {
id: posContainer
readonly property var pos: (rootPane.desktopClockPosition || "top-left").split('-')
readonly property string currentV: pos[0]
readonly property string currentH: pos[1]
function updateClockPos(v, h) {
rootPane.desktopClockPosition = v + "-" + h;
rootPane.saveConfig();
}
contentSpacing: Tokens.spacing.small
z: 1
StyledText {
text: qsTr("Positioning")
font: Tokens.font.body.builders.large.weight(Font.Medium).build()
}
SplitButtonRow {
label: qsTr("Vertical Position")
enabled: rootPane.desktopClockEnabled
menuItems: [
MenuItem {
property string val: "top"
text: qsTr("Top")
icon: "vertical_align_top"
},
MenuItem {
property string val: "middle"
text: qsTr("Middle")
icon: "vertical_align_center"
},
MenuItem {
property string val: "bottom"
text: qsTr("Bottom")
icon: "vertical_align_bottom"
}
]
Component.onCompleted: {
for (let i = 0; i < menuItems.length; i++) {
if (menuItems[i].val === posContainer.currentV)
active = menuItems[i];
}
}
// The signal from SplitButtonRow
onSelected: item => posContainer.updateClockPos(item.val, posContainer.currentH)
}
SplitButtonRow {
label: qsTr("Horizontal Position")
enabled: rootPane.desktopClockEnabled
expandedZ: 99
menuItems: [
MenuItem {
property string val: "left"
text: qsTr("Left")
icon: "align_horizontal_left"
},
MenuItem {
property string val: "center"
text: qsTr("Center")
icon: "align_horizontal_center"
},
MenuItem {
property string val: "right"
text: qsTr("Right")
icon: "align_horizontal_right"
}
]
Component.onCompleted: {
for (let i = 0; i < menuItems.length; i++) {
if (menuItems[i].val === posContainer.currentH)
active = menuItems[i];
}
}
onSelected: item => posContainer.updateClockPos(posContainer.currentV, item.val)
}
}
SwitchRow {
label: qsTr("Invert colors")
checked: rootPane.desktopClockInvertColors
onToggled: checked => {
rootPane.desktopClockInvertColors = checked;
rootPane.saveConfig();
}
}
SectionContainer {
contentSpacing: Tokens.spacing.small
StyledText {
text: qsTr("Shadow")
font: Tokens.font.body.builders.large.weight(Font.Medium).build()
}
SwitchRow {
label: qsTr("Enabled")
checked: rootPane.desktopClockShadowEnabled
onToggled: checked => {
rootPane.desktopClockShadowEnabled = checked;
rootPane.saveConfig();
}
}
SectionContainer {
contentSpacing: Tokens.spacing.medium
SliderInput {
Layout.fillWidth: true
label: qsTr("Opacity")
value: rootPane.desktopClockShadowOpacity * 100
from: 0
to: 100
suffix: "%"
validator: IntValidator {
bottom: 0
top: 100
}
formatValueFunction: val => Math.round(val).toString()
parseValueFunction: text => parseInt(text)
onValueModified: newValue => {
rootPane.desktopClockShadowOpacity = newValue / 100;
rootPane.saveConfig();
}
}
}
SectionContainer {
contentSpacing: Tokens.spacing.medium
SliderInput {
Layout.fillWidth: true
label: qsTr("Blur")
value: rootPane.desktopClockShadowBlur * 100
from: 0
to: 100
suffix: "%"
validator: IntValidator {
bottom: 0
top: 100
}
formatValueFunction: val => Math.round(val).toString()
parseValueFunction: text => parseInt(text)
onValueModified: newValue => {
rootPane.desktopClockShadowBlur = newValue / 100;
rootPane.saveConfig();
}
}
}
}
SectionContainer {
contentSpacing: Tokens.spacing.small
StyledText {
text: qsTr("Background")
font: Tokens.font.body.builders.large.weight(Font.Medium).build()
}
SwitchRow {
label: qsTr("Enabled")
checked: rootPane.desktopClockBackgroundEnabled
onToggled: checked => {
rootPane.desktopClockBackgroundEnabled = checked;
rootPane.saveConfig();
}
}
SwitchRow {
label: qsTr("Blur enabled")
checked: rootPane.desktopClockBackgroundBlur
onToggled: checked => {
rootPane.desktopClockBackgroundBlur = checked;
rootPane.saveConfig();
}
}
SectionContainer {
contentSpacing: Tokens.spacing.medium
SliderInput {
Layout.fillWidth: true
label: qsTr("Opacity")
value: rootPane.desktopClockBackgroundOpacity * 100
from: 0
to: 100
suffix: "%"
validator: IntValidator {
bottom: 0
top: 100
}
formatValueFunction: val => Math.round(val).toString()
parseValueFunction: text => parseInt(text)
onValueModified: newValue => {
rootPane.desktopClockBackgroundOpacity = newValue / 100;
rootPane.saveConfig();
}
}
}
}
StyledText {
Layout.topMargin: Tokens.spacing.medium
text: qsTr("Visualiser")
font: Tokens.font.body.builders.large.weight(Font.Medium).build()
}
SwitchRow {
label: qsTr("Visualiser enabled")
checked: rootPane.visualiserEnabled
onToggled: checked => {
rootPane.visualiserEnabled = checked;
rootPane.saveConfig();
}
}
SwitchRow {
label: qsTr("Visualiser auto hide")
checked: rootPane.visualiserAutoHide
onToggled: checked => {
rootPane.visualiserAutoHide = checked;
rootPane.saveConfig();
}
}
SectionContainer {
contentSpacing: Tokens.spacing.medium
SliderInput {
Layout.fillWidth: true
label: qsTr("Visualiser rounding")
value: rootPane.visualiserRounding
from: 0
to: 10
stepSize: 1
validator: IntValidator {
bottom: 0
top: 10
}
formatValueFunction: val => Math.round(val).toString()
parseValueFunction: text => parseInt(text)
onValueModified: newValue => {
rootPane.visualiserRounding = Math.round(newValue);
rootPane.saveConfig();
}
}
}
SectionContainer {
contentSpacing: Tokens.spacing.medium
SliderInput {
Layout.fillWidth: true
label: qsTr("Visualiser spacing")
value: rootPane.visualiserSpacing
from: 0
to: 2
validator: DoubleValidator {
bottom: 0
top: 2
}
onValueModified: newValue => {
rootPane.visualiserSpacing = newValue;
rootPane.saveConfig();
}
}
}
}

View file

@ -1,68 +0,0 @@
pragma ComponentBehavior: Bound
import ".."
import "../../components"
import QtQuick
import QtQuick.Layouts
import Caelestia.Config
import qs.components
import qs.components.containers
import qs.components.controls
import qs.services
CollapsibleSection {
id: root
required property var rootPane
title: qsTr("Border")
showBackground: true
SectionContainer {
contentSpacing: Tokens.spacing.medium
SliderInput {
Layout.fillWidth: true
label: qsTr("Border rounding")
value: rootPane.borderRounding
from: 0.1
to: 100
decimals: 1
suffix: "px"
validator: DoubleValidator {
bottom: 0.1
top: 100
}
onValueModified: newValue => {
rootPane.borderRounding = newValue;
rootPane.saveConfig();
}
}
}
SectionContainer {
contentSpacing: Tokens.spacing.medium
SliderInput {
Layout.fillWidth: true
label: qsTr("Border thickness")
value: rootPane.borderThickness
from: 0
to: 100
decimals: 1
suffix: "px"
validator: DoubleValidator {
bottom: 0.1
top: 100
}
onValueModified: newValue => {
rootPane.borderThickness = newValue;
rootPane.saveConfig();
}
}
}
}

View file

@ -1,147 +0,0 @@
pragma ComponentBehavior: Bound
import ".."
import "../../../launcher/services"
import QtQuick
import QtQuick.Layouts
import Quickshell
import Caelestia.Config
import qs.components
import qs.components.containers
import qs.components.controls
import qs.services
CollapsibleSection {
title: qsTr("Color scheme")
description: qsTr("Available color schemes")
showBackground: true
ColumnLayout {
Layout.fillWidth: true
spacing: Tokens.spacing.extraSmall
Repeater {
model: Schemes.list
delegate: StyledRect {
required property var modelData
Layout.fillWidth: true
readonly property string schemeKey: `${modelData.name} ${modelData.flavour}`
readonly property bool isCurrent: schemeKey === Schemes.currentScheme
color: Qt.alpha(Colours.tPalette.m3surfaceContainer, isCurrent ? Colours.tPalette.m3surfaceContainer.a : 0)
radius: Tokens.rounding.large
border.width: isCurrent ? 1 : 0
border.color: Colours.palette.m3primary
implicitHeight: schemeRow.implicitHeight + Tokens.padding.medium * 2
StateLayer {
onClicked: {
const name = modelData.name;
const flavour = modelData.flavour;
const schemeKey = `${name} ${flavour}`;
Schemes.currentScheme = schemeKey;
Quickshell.execDetached(["caelestia", "scheme", "set", "-n", name, "-f", flavour]);
Qt.callLater(() => {
reloadTimer.restart();
});
}
}
Timer {
id: reloadTimer
interval: 300
onTriggered: {
Schemes.reload();
}
}
RowLayout {
id: schemeRow
anchors.fill: parent
anchors.margins: Tokens.padding.medium
spacing: Tokens.spacing.medium
StyledRect {
id: preview
Layout.alignment: Qt.AlignVCenter
border.width: 1
border.color: Qt.alpha(`#${modelData.colours?.outline}`, 0.5)
color: `#${modelData.colours?.surface}`
radius: Tokens.rounding.full
implicitWidth: iconPlaceholder.implicitWidth
implicitHeight: iconPlaceholder.implicitWidth
MaterialIcon {
id: iconPlaceholder
visible: false
text: "circle"
fontStyle: Tokens.font.icon.large
}
Item {
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.right: parent.right
implicitWidth: parent.implicitWidth / 2
clip: true
StyledRect {
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.right: parent.right
implicitWidth: preview.implicitWidth
color: `#${modelData.colours?.primary}`
radius: Tokens.rounding.full
}
}
}
Column {
Layout.fillWidth: true
spacing: 0
StyledText {
text: modelData.flavour ?? ""
font: Tokens.font.body.medium
}
StyledText {
text: modelData.name ?? ""
font: Tokens.font.body.small
color: Colours.palette.m3outline
elide: Text.ElideRight
anchors.left: parent.left
anchors.right: parent.right
}
}
Loader {
asynchronous: true
active: isCurrent
sourceComponent: MaterialIcon {
text: "check"
color: Colours.palette.m3onSurfaceVariant
fontStyle: Tokens.font.icon.large
}
}
}
}
}
}
}

View file

@ -1,91 +0,0 @@
pragma ComponentBehavior: Bound
import ".."
import "../../../launcher/services"
import QtQuick
import QtQuick.Layouts
import Quickshell
import Caelestia.Config
import qs.components
import qs.components.containers
import qs.components.controls
import qs.services
CollapsibleSection {
title: qsTr("Color variant")
description: qsTr("Material theme variant")
showBackground: true
ColumnLayout {
Layout.fillWidth: true
spacing: Tokens.spacing.extraSmall
Repeater {
model: M3Variants.list
delegate: StyledRect {
required property var modelData
Layout.fillWidth: true
color: Qt.alpha(Colours.tPalette.m3surfaceContainer, modelData.variant === Schemes.currentVariant ? Colours.tPalette.m3surfaceContainer.a : 0)
radius: Tokens.rounding.large
border.width: modelData.variant === Schemes.currentVariant ? 1 : 0
border.color: Colours.palette.m3primary
implicitHeight: variantRow.implicitHeight + Tokens.padding.medium * 2
StateLayer {
onClicked: {
const variant = modelData.variant;
Schemes.currentVariant = variant;
Quickshell.execDetached(["caelestia", "scheme", "set", "-v", variant]);
Qt.callLater(() => {
reloadTimer.restart();
});
}
}
Timer {
id: reloadTimer
interval: 300
onTriggered: {
Schemes.reload();
}
}
RowLayout {
id: variantRow
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Tokens.padding.medium
spacing: Tokens.spacing.medium
MaterialIcon {
text: modelData.icon
fontStyle: Tokens.font.icon.large
fill: modelData.variant === Schemes.currentVariant ? 1 : 0
}
StyledText {
Layout.fillWidth: true
text: modelData.name
font: modelData.variant === Schemes.currentVariant ? Tokens.font.body.builders.small.weight(Font.Medium).build() : Tokens.font.body.builders.small.weight(Font.Normal).build()
}
MaterialIcon {
visible: modelData.variant === Schemes.currentVariant
text: "check"
color: Colours.palette.m3primary
fontStyle: Tokens.font.icon.large
}
}
}
}
}
}

View file

@ -1,289 +0,0 @@
pragma ComponentBehavior: Bound
import ".."
import "../../components"
import QtQuick
import QtQuick.Layouts
import Caelestia.Config
import qs.components
import qs.components.containers
import qs.components.controls
import qs.services
CollapsibleSection {
id: root
required property var rootPane
title: qsTr("Fonts")
showBackground: true
CollapsibleSection {
id: sansFontSection
title: qsTr("Sans-serif font family")
expanded: true
showBackground: true
nested: true
Loader {
Layout.fillWidth: true
Layout.preferredHeight: item ? Math.min(item.contentHeight, 300) : 0
asynchronous: true
active: sansFontSection.expanded
sourceComponent: StyledListView {
id: sansFontList
property alias contentHeight: sansFontList.contentHeight
clip: true
spacing: Tokens.spacing.extraSmall
model: Qt.fontFamilies()
StyledScrollBar.vertical: StyledScrollBar {
flickable: sansFontList
}
delegate: StyledRect {
required property string modelData
required property int index
readonly property bool isCurrent: modelData === rootPane.fontFamilySans
width: ListView.view.width
color: Qt.alpha(Colours.tPalette.m3surfaceContainer, isCurrent ? Colours.tPalette.m3surfaceContainer.a : 0)
radius: Tokens.rounding.large
border.width: isCurrent ? 1 : 0
border.color: Colours.palette.m3primary
implicitHeight: fontFamilySansRow.implicitHeight + Tokens.padding.medium * 2
StateLayer {
onClicked: {
rootPane.fontFamilySans = modelData;
rootPane.saveConfig();
}
}
RowLayout {
id: fontFamilySansRow
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Tokens.padding.medium
spacing: Tokens.spacing.medium
StyledText {
text: modelData
font: Tokens.font.body.medium
}
Item {
Layout.fillWidth: true
}
Loader {
asynchronous: true
active: isCurrent
sourceComponent: MaterialIcon {
text: "check"
color: Colours.palette.m3onSurfaceVariant
fontStyle: Tokens.font.icon.large
}
}
}
}
}
}
}
CollapsibleSection {
id: monoFontSection
title: qsTr("Monospace font family")
expanded: false
showBackground: true
nested: true
Loader {
Layout.fillWidth: true
Layout.preferredHeight: item ? Math.min(item.contentHeight, 300) : 0
asynchronous: true
active: monoFontSection.expanded
sourceComponent: StyledListView {
id: monoFontList
property alias contentHeight: monoFontList.contentHeight
clip: true
spacing: Tokens.spacing.extraSmall
model: Qt.fontFamilies()
StyledScrollBar.vertical: StyledScrollBar {
flickable: monoFontList
}
delegate: StyledRect {
required property string modelData
required property int index
readonly property bool isCurrent: modelData === rootPane.fontFamilyMono
width: ListView.view.width
color: Qt.alpha(Colours.tPalette.m3surfaceContainer, isCurrent ? Colours.tPalette.m3surfaceContainer.a : 0)
radius: Tokens.rounding.large
border.width: isCurrent ? 1 : 0
border.color: Colours.palette.m3primary
implicitHeight: fontFamilyMonoRow.implicitHeight + Tokens.padding.medium * 2
StateLayer {
onClicked: {
rootPane.fontFamilyMono = modelData;
rootPane.saveConfig();
}
}
RowLayout {
id: fontFamilyMonoRow
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Tokens.padding.medium
spacing: Tokens.spacing.medium
StyledText {
text: modelData
font: Tokens.font.body.medium
}
Item {
Layout.fillWidth: true
}
Loader {
asynchronous: true
active: isCurrent
sourceComponent: MaterialIcon {
text: "check"
color: Colours.palette.m3onSurfaceVariant
fontStyle: Tokens.font.icon.large
}
}
}
}
}
}
}
CollapsibleSection {
id: materialFontSection
title: qsTr("Material font family")
expanded: false
showBackground: true
nested: true
Loader {
id: materialFontLoader
Layout.fillWidth: true
Layout.preferredHeight: item ? Math.min(item.contentHeight, 300) : 0
asynchronous: true
active: materialFontSection.expanded
sourceComponent: StyledListView {
id: materialFontList
property alias contentHeight: materialFontList.contentHeight
clip: true
spacing: Tokens.spacing.extraSmall
model: Qt.fontFamilies().filter(f => f.startsWith("Material Symbols"))
StyledScrollBar.vertical: StyledScrollBar {
flickable: materialFontList
}
delegate: StyledRect {
required property string modelData
required property int index
readonly property bool isCurrent: modelData === rootPane.fontFamilyMaterial
width: ListView.view.width
color: Qt.alpha(Colours.tPalette.m3surfaceContainer, isCurrent ? Colours.tPalette.m3surfaceContainer.a : 0)
radius: Tokens.rounding.large
border.width: isCurrent ? 1 : 0
border.color: Colours.palette.m3primary
implicitHeight: fontFamilyMaterialRow.implicitHeight + Tokens.padding.medium * 2
StateLayer {
onClicked: {
rootPane.fontFamilyMaterial = modelData;
rootPane.saveConfig();
}
}
RowLayout {
id: fontFamilyMaterialRow
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Tokens.padding.medium
spacing: Tokens.spacing.medium
StyledText {
text: modelData
font: Tokens.font.body.medium
}
Item {
Layout.fillWidth: true
}
Loader {
asynchronous: true
active: isCurrent
sourceComponent: MaterialIcon {
text: "check"
color: Colours.palette.m3onSurfaceVariant
fontStyle: Tokens.font.icon.large
}
}
}
}
}
}
}
SectionContainer {
contentSpacing: Tokens.spacing.medium
SliderInput {
Layout.fillWidth: true
label: qsTr("Font size scale")
value: rootPane.fontSizeScale
from: 0.7
to: 1.5
decimals: 2
suffix: "×"
validator: DoubleValidator {
bottom: 0.7
top: 1.5
}
onValueModified: newValue => {
rootPane.fontSizeScale = newValue;
rootPane.saveConfig();
}
}
}
}

View file

@ -1,92 +0,0 @@
pragma ComponentBehavior: Bound
import ".."
import "../../components"
import QtQuick
import QtQuick.Layouts
import Caelestia.Config
import qs.components
import qs.components.containers
import qs.components.controls
import qs.services
CollapsibleSection {
id: root
required property var rootPane
title: qsTr("Scales")
showBackground: true
SectionContainer {
contentSpacing: Tokens.spacing.medium
SliderInput {
Layout.fillWidth: true
label: qsTr("Padding scale")
value: rootPane.paddingScale
from: 0.5
to: 2.0
decimals: 1
suffix: "×"
validator: DoubleValidator {
bottom: 0.5
top: 2.0
}
onValueModified: newValue => {
rootPane.paddingScale = newValue;
rootPane.saveConfig();
}
}
}
SectionContainer {
contentSpacing: Tokens.spacing.medium
SliderInput {
Layout.fillWidth: true
label: qsTr("Rounding scale")
value: rootPane.roundingScale
from: 0.1
to: 5.0
decimals: 1
suffix: "×"
validator: DoubleValidator {
bottom: 0.1
top: 5.0
}
onValueModified: newValue => {
rootPane.roundingScale = newValue;
rootPane.saveConfig();
}
}
}
SectionContainer {
contentSpacing: Tokens.spacing.medium
SliderInput {
Layout.fillWidth: true
label: qsTr("Spacing scale")
value: rootPane.spacingScale
from: 0.1
to: 2.0
decimals: 1
suffix: "×"
validator: DoubleValidator {
bottom: 0.1
top: 2.0
}
onValueModified: newValue => {
rootPane.spacingScale = newValue;
rootPane.saveConfig();
}
}
}
}

View file

@ -1,23 +0,0 @@
pragma ComponentBehavior: Bound
import ".."
import QtQuick
import Caelestia.Config
import qs.components
import qs.components.containers
import qs.components.controls
import qs.services
CollapsibleSection {
title: qsTr("Theme mode")
description: qsTr("Light or dark theme")
showBackground: true
SwitchRow {
label: qsTr("Dark mode")
checked: !Colours.currentLight
onToggled: checked => {
Colours.setMode(checked ? "dark" : "light");
}
}
}

View file

@ -1,79 +0,0 @@
pragma ComponentBehavior: Bound
import ".."
import "../../components"
import QtQuick
import QtQuick.Layouts
import Caelestia.Config
import qs.components
import qs.components.containers
import qs.components.controls
import qs.services
CollapsibleSection {
id: root
required property var rootPane
title: qsTr("Transparency")
showBackground: true
SwitchRow {
label: qsTr("Transparency enabled")
checked: rootPane.transparencyEnabled
onToggled: checked => {
rootPane.transparencyEnabled = checked;
rootPane.saveConfig();
}
}
SectionContainer {
contentSpacing: Tokens.spacing.medium
SliderInput {
Layout.fillWidth: true
label: qsTr("Transparency base")
value: rootPane.transparencyBase * 100
from: 0
to: 100
suffix: "%"
validator: IntValidator {
bottom: 0
top: 100
}
formatValueFunction: val => Math.round(val).toString()
parseValueFunction: text => parseInt(text)
onValueModified: newValue => {
rootPane.transparencyBase = newValue / 100;
rootPane.saveConfig();
}
}
}
SectionContainer {
contentSpacing: Tokens.spacing.medium
SliderInput {
Layout.fillWidth: true
label: qsTr("Transparency layers")
value: rootPane.transparencyLayers * 100
from: 0
to: 100
suffix: "%"
validator: IntValidator {
bottom: 0
top: 100
}
formatValueFunction: val => Math.round(val).toString()
parseValueFunction: text => parseInt(text)
onValueModified: newValue => {
rootPane.transparencyLayers = newValue / 100;
rootPane.saveConfig();
}
}
}
}

View file

@ -1,623 +0,0 @@
pragma ComponentBehavior: Bound
import ".."
import "../components"
import QtQuick
import QtQuick.Layouts
import Quickshell.Widgets
import Caelestia.Config
import qs.components
import qs.components.containers
import qs.components.controls
import qs.components.effects
import qs.services
Item {
id: root
required property Session session
anchors.fill: parent
SplitPaneLayout {
anchors.fill: parent
leftContent: Component {
StyledFlickable {
id: leftAudioFlickable
flickableDirection: Flickable.VerticalFlick
contentHeight: leftContent.height
StyledScrollBar.vertical: StyledScrollBar {
flickable: leftAudioFlickable
}
ColumnLayout {
id: leftContent
anchors.left: parent.left
anchors.right: parent.right
spacing: Tokens.spacing.medium
RowLayout {
Layout.fillWidth: true
spacing: Tokens.spacing.medium
StyledText {
text: qsTr("Audio")
font: Tokens.font.body.builders.large.weight(Font.Medium).build()
}
Item {
Layout.fillWidth: true
}
}
CollapsibleSection {
id: outputDevicesSection
Layout.fillWidth: true
title: qsTr("Output devices")
expanded: true
ColumnLayout {
Layout.fillWidth: true
spacing: Tokens.spacing.small
RowLayout {
Layout.fillWidth: true
spacing: Tokens.spacing.small
StyledText {
text: qsTr("Devices (%1)").arg(Audio.sinks.length)
font: Tokens.font.body.builders.medium.weight(Font.Medium).build()
}
}
StyledText {
Layout.fillWidth: true
text: qsTr("All available output devices")
color: Colours.palette.m3outline
}
Repeater {
Layout.fillWidth: true
model: Audio.sinks
delegate: StyledRect {
required property var modelData
Layout.fillWidth: true
color: Audio.sink?.id === modelData.id ? Colours.layer(Colours.palette.m3surfaceContainer, 2) : "transparent"
radius: Tokens.rounding.large
implicitHeight: outputRowLayout.implicitHeight + Tokens.padding.medium * 2
StateLayer {
onClicked: {
Audio.setAudioSink(modelData);
}
}
RowLayout {
id: outputRowLayout
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Tokens.padding.medium
spacing: Tokens.spacing.medium
MaterialIcon {
text: Audio.sink?.id === modelData.id ? "speaker" : "speaker_group"
fontStyle: Tokens.font.icon.large
fill: Audio.sink?.id === modelData.id ? 1 : 0
}
StyledText {
Layout.fillWidth: true
elide: Text.ElideRight
maximumLineCount: 1
text: modelData.description || qsTr("Unknown")
font: Audio.sink?.id === modelData.id ? Tokens.font.body.builders.small.weight(Font.Medium).build() : Tokens.font.body.builders.small.weight(Font.Normal).build()
}
}
}
}
}
}
CollapsibleSection {
id: inputDevicesSection
Layout.fillWidth: true
title: qsTr("Input devices")
expanded: true
ColumnLayout {
Layout.fillWidth: true
spacing: Tokens.spacing.small
RowLayout {
Layout.fillWidth: true
spacing: Tokens.spacing.small
StyledText {
text: qsTr("Devices (%1)").arg(Audio.sources.length)
font: Tokens.font.body.builders.medium.weight(Font.Medium).build()
}
}
StyledText {
Layout.fillWidth: true
text: qsTr("All available input devices")
color: Colours.palette.m3outline
}
Repeater {
Layout.fillWidth: true
model: Audio.sources
delegate: StyledRect {
required property var modelData
Layout.fillWidth: true
color: Audio.source?.id === modelData.id ? Colours.layer(Colours.palette.m3surfaceContainer, 2) : "transparent"
radius: Tokens.rounding.large
implicitHeight: inputRowLayout.implicitHeight + Tokens.padding.medium * 2
StateLayer {
onClicked: {
Audio.setAudioSource(modelData);
}
}
RowLayout {
id: inputRowLayout
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Tokens.padding.medium
spacing: Tokens.spacing.medium
MaterialIcon {
text: "mic"
fontStyle: Tokens.font.icon.large
fill: Audio.source?.id === modelData.id ? 1 : 0
}
StyledText {
Layout.fillWidth: true
elide: Text.ElideRight
maximumLineCount: 1
text: modelData.description || qsTr("Unknown")
font: Audio.source?.id === modelData.id ? Tokens.font.body.builders.small.weight(Font.Medium).build() : Tokens.font.body.builders.small.weight(Font.Normal).build()
}
}
}
}
}
}
}
}
}
rightContent: Component {
StyledFlickable {
id: rightAudioFlickable
flickableDirection: Flickable.VerticalFlick
contentHeight: contentLayout.height
StyledScrollBar.vertical: StyledScrollBar {
flickable: rightAudioFlickable
}
ColumnLayout {
id: contentLayout
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
spacing: Tokens.spacing.medium
SettingsHeader {
icon: "volume_up"
title: qsTr("Audio Settings")
}
SectionHeader {
title: qsTr("Output volume")
description: qsTr("Control the volume of your output device")
}
SectionContainer {
contentSpacing: Tokens.spacing.medium
ColumnLayout {
Layout.fillWidth: true
spacing: Tokens.spacing.small
RowLayout {
Layout.fillWidth: true
spacing: Tokens.spacing.medium
StyledText {
text: qsTr("Volume")
font: Tokens.font.body.builders.medium.weight(Font.Medium).build()
}
Item {
Layout.fillWidth: true
}
StyledInputField {
id: outputVolumeInput
Layout.preferredWidth: 70
validator: IntValidator {
bottom: 0
top: 100
}
enabled: !Audio.muted
Component.onCompleted: {
text = Math.round(Audio.volume * 100).toString();
}
onTextEdited: text => {
if (hasFocus) {
const val = parseInt(text);
if (!isNaN(val) && val >= 0 && val <= 100) {
Audio.setVolume(val / 100);
}
}
}
onEditingFinished: {
const val = parseInt(text);
if (isNaN(val) || val < 0 || val > 100) {
text = Math.round(Audio.volume * 100).toString();
}
}
Connections {
function onVolumeChanged() {
if (!outputVolumeInput.hasFocus) {
outputVolumeInput.text = Math.round(Audio.volume * 100).toString();
}
}
target: Audio
}
}
StyledText {
text: "%"
color: Colours.palette.m3outline
font: Tokens.font.body.medium
opacity: Audio.muted ? 0.5 : 1
}
StyledRect {
implicitWidth: implicitHeight
implicitHeight: muteIcon.implicitHeight + Tokens.padding.medium * 2
radius: Tokens.rounding.large
color: Audio.muted ? Colours.palette.m3secondary : Colours.palette.m3secondaryContainer
StateLayer {
onClicked: {
if (Audio.sink?.audio) {
Audio.sink.audio.muted = !Audio.sink.audio.muted;
}
}
}
MaterialIcon {
id: muteIcon
anchors.centerIn: parent
text: Audio.muted ? "volume_off" : "volume_up"
color: Audio.muted ? Colours.palette.m3onSecondary : Colours.palette.m3onSecondaryContainer
}
}
}
StyledSlider {
id: outputVolumeSlider
Layout.fillWidth: true
implicitHeight: Tokens.padding.medium * 3
value: Audio.volume
enabled: !Audio.muted
opacity: enabled ? 1 : 0.5
onMoved: {
Audio.setVolume(value);
if (!outputVolumeInput.hasFocus) {
outputVolumeInput.text = Math.round(value * 100).toString();
}
}
}
}
}
SectionHeader {
title: qsTr("Input volume")
description: qsTr("Control the volume of your input device")
}
SectionContainer {
contentSpacing: Tokens.spacing.medium
ColumnLayout {
Layout.fillWidth: true
spacing: Tokens.spacing.small
RowLayout {
Layout.fillWidth: true
spacing: Tokens.spacing.medium
StyledText {
text: qsTr("Volume")
font: Tokens.font.body.builders.medium.weight(Font.Medium).build()
}
Item {
Layout.fillWidth: true
}
StyledInputField {
id: inputVolumeInput
Layout.preferredWidth: 70
validator: IntValidator {
bottom: 0
top: 100
}
enabled: !Audio.sourceMuted
Component.onCompleted: {
text = Math.round(Audio.sourceVolume * 100).toString();
}
onTextEdited: text => {
if (hasFocus) {
const val = parseInt(text);
if (!isNaN(val) && val >= 0 && val <= 100) {
Audio.setSourceVolume(val / 100);
}
}
}
onEditingFinished: {
const val = parseInt(text);
if (isNaN(val) || val < 0 || val > 100) {
text = Math.round(Audio.sourceVolume * 100).toString();
}
}
Connections {
function onSourceVolumeChanged() {
if (!inputVolumeInput.hasFocus) {
inputVolumeInput.text = Math.round(Audio.sourceVolume * 100).toString();
}
}
target: Audio
}
}
StyledText {
text: "%"
color: Colours.palette.m3outline
font: Tokens.font.body.medium
opacity: Audio.sourceMuted ? 0.5 : 1
}
StyledRect {
implicitWidth: implicitHeight
implicitHeight: muteInputIcon.implicitHeight + Tokens.padding.medium * 2
radius: Tokens.rounding.large
color: Audio.sourceMuted ? Colours.palette.m3secondary : Colours.palette.m3secondaryContainer
StateLayer {
onClicked: {
if (Audio.source?.audio) {
Audio.source.audio.muted = !Audio.source.audio.muted;
}
}
}
MaterialIcon {
id: muteInputIcon
anchors.centerIn: parent
text: "mic_off"
color: Audio.sourceMuted ? Colours.palette.m3onSecondary : Colours.palette.m3onSecondaryContainer
}
}
}
StyledSlider {
id: inputVolumeSlider
Layout.fillWidth: true
implicitHeight: Tokens.padding.medium * 3
value: Audio.sourceVolume
enabled: !Audio.sourceMuted
opacity: enabled ? 1 : 0.5
onMoved: {
Audio.setSourceVolume(value);
if (!inputVolumeInput.hasFocus) {
inputVolumeInput.text = Math.round(value * 100).toString();
}
}
}
}
}
SectionHeader {
title: qsTr("Applications")
description: qsTr("Control volume for individual applications")
}
SectionContainer {
contentSpacing: Tokens.spacing.medium
ColumnLayout {
Layout.fillWidth: true
spacing: Tokens.spacing.small
Repeater {
model: Audio.streams
Layout.fillWidth: true
delegate: ColumnLayout {
required property var modelData
required property int index
Layout.fillWidth: true
spacing: Tokens.spacing.medium
RowLayout {
Layout.fillWidth: true
spacing: Tokens.spacing.medium
MaterialIcon {
text: "apps"
fontStyle: Tokens.font.icon.medium
fill: 0
}
StyledText {
Layout.fillWidth: true
elide: Text.ElideRight
maximumLineCount: 1
text: Audio.getStreamName(modelData)
font: Tokens.font.body.builders.medium.weight(Font.Medium).build()
}
StyledInputField {
id: streamVolumeInput
Layout.preferredWidth: 70
validator: IntValidator {
bottom: 0
top: 100
}
enabled: !Audio.getStreamMuted(modelData)
Component.onCompleted: {
text = Math.round(Audio.getStreamVolume(modelData) * 100).toString();
}
onTextEdited: text => {
if (hasFocus) {
const val = parseInt(text);
if (!isNaN(val) && val >= 0 && val <= 100) {
Audio.setStreamVolume(modelData, val / 100);
}
}
}
onEditingFinished: {
const val = parseInt(text);
if (isNaN(val) || val < 0 || val > 100) {
text = Math.round(Audio.getStreamVolume(modelData) * 100).toString();
}
}
Connections {
function onAudioChanged() {
if (!streamVolumeInput.hasFocus && modelData?.audio) {
streamVolumeInput.text = Math.round(modelData.audio.volume * 100).toString();
}
}
target: modelData
}
}
StyledText {
text: "%"
color: Colours.palette.m3outline
font: Tokens.font.body.medium
opacity: Audio.getStreamMuted(modelData) ? 0.5 : 1
}
StyledRect {
implicitWidth: implicitHeight
implicitHeight: streamMuteIcon.implicitHeight + Tokens.padding.medium * 2
radius: Tokens.rounding.large
color: Audio.getStreamMuted(modelData) ? Colours.palette.m3secondary : Colours.palette.m3secondaryContainer
StateLayer {
onClicked: {
Audio.setStreamMuted(modelData, !Audio.getStreamMuted(modelData));
}
}
MaterialIcon {
id: streamMuteIcon
anchors.centerIn: parent
text: Audio.getStreamMuted(modelData) ? "volume_off" : "volume_up"
color: Audio.getStreamMuted(modelData) ? Colours.palette.m3onSecondary : Colours.palette.m3onSecondaryContainer
}
}
}
StyledSlider {
Layout.fillWidth: true
implicitHeight: Tokens.padding.medium * 3
value: Audio.getStreamVolume(modelData)
enabled: !Audio.getStreamMuted(modelData)
opacity: enabled ? 1 : 0.5
onMoved: {
Audio.setStreamVolume(modelData, value);
if (!streamVolumeInput.hasFocus) {
streamVolumeInput.text = Math.round(value * 100).toString();
}
}
Connections {
function onAudioChanged() {
if (modelData?.audio) {
value = modelData.audio.volume;
}
}
target: modelData
}
}
}
}
StyledText {
Layout.fillWidth: true
visible: Audio.streams.length === 0
text: qsTr("No applications currently playing audio")
color: Colours.palette.m3outline
font: Tokens.font.body.small
horizontalAlignment: Text.AlignHCenter
}
}
}
}
}
}
}
}

View file

@ -1,74 +0,0 @@
pragma ComponentBehavior: Bound
import ".."
import "../components"
import "."
import QtQuick
import Quickshell.Bluetooth
import Quickshell.Widgets
import Caelestia.Config
import qs.components
import qs.components.containers
import qs.components.controls
SplitPaneWithDetails {
id: root
required property Session session
anchors.fill: parent
activeItem: session.bt.active
paneIdGenerator: function (item) {
return item ? (item.address || "") : "";
}
leftContent: Component {
StyledFlickable {
id: leftFlickable
flickableDirection: Flickable.VerticalFlick
contentHeight: deviceList.height
StyledScrollBar.vertical: StyledScrollBar {
flickable: leftFlickable
}
DeviceList {
id: deviceList
anchors.left: parent.left
anchors.right: parent.right
session: root.session
}
}
}
rightDetailsComponent: Component {
Details {
session: root.session
}
}
rightSettingsComponent: Component {
StyledFlickable {
id: settingsFlickable
flickableDirection: Flickable.VerticalFlick
contentHeight: settingsInner.height
StyledScrollBar.vertical: StyledScrollBar {
flickable: settingsFlickable
}
Settings {
id: settingsInner
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
session: root.session
}
}
}
}

View file

@ -1,663 +0,0 @@
pragma ComponentBehavior: Bound
import ".."
import "../components"
import QtQuick
import QtQuick.Layouts
import Quickshell.Bluetooth
import Caelestia.Config
import qs.components
import qs.components.containers
import qs.components.controls
import qs.components.effects
import qs.services
import qs.utils
StyledFlickable {
id: root
required property Session session
readonly property BluetoothDevice device: session.bt.active
flickableDirection: Flickable.VerticalFlick
contentHeight: detailsWrapper.height
StyledScrollBar.vertical: StyledScrollBar {
flickable: root
}
Item {
id: detailsWrapper
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
implicitHeight: details.implicitHeight
DeviceDetails {
id: details
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
session: root.session
device: root.device
headerComponent: Component {
SettingsHeader {
icon: Icons.getBluetoothIcon(root.device?.icon ?? "")
title: root.device?.name ?? ""
}
}
sections: [
Component {
ColumnLayout {
spacing: Tokens.spacing.medium
StyledText {
Layout.topMargin: Tokens.spacing.largeIncreased
text: qsTr("Connection status")
font: Tokens.font.body.builders.large.weight(Font.Medium).build()
}
StyledText {
text: qsTr("Connection settings for this device")
color: Colours.palette.m3outline
}
StyledRect {
Layout.fillWidth: true
implicitHeight: deviceStatus.implicitHeight + Tokens.padding.extraLargeIncreased
radius: Tokens.rounding.large
color: Colours.tPalette.m3surfaceContainer
ColumnLayout {
id: deviceStatus
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Tokens.padding.large
spacing: Tokens.spacing.large
Toggle {
label: qsTr("Connected")
checked: root.device?.connected ?? false
toggle.onToggled: root.device.connected = checked
}
Toggle {
label: qsTr("Paired")
checked: root.device?.paired ?? false
toggle.onToggled: {
if (root.device.paired)
root.device.forget();
else
root.device.pair();
}
}
Toggle {
label: qsTr("Blocked")
checked: root.device?.blocked ?? false
toggle.onToggled: root.device.blocked = checked
}
}
}
}
},
Component {
ColumnLayout {
spacing: Tokens.spacing.medium
StyledText {
Layout.topMargin: Tokens.spacing.largeIncreased
text: qsTr("Device properties")
font: Tokens.font.body.builders.large.weight(Font.Medium).build()
}
StyledText {
text: qsTr("Additional settings")
color: Colours.palette.m3outline
}
StyledRect {
Layout.fillWidth: true
implicitHeight: deviceProps.implicitHeight + Tokens.padding.extraLargeIncreased
radius: Tokens.rounding.large
color: Colours.tPalette.m3surfaceContainer
ColumnLayout {
id: deviceProps
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Tokens.padding.large
spacing: Tokens.spacing.large
RowLayout {
Layout.fillWidth: true
spacing: Tokens.spacing.small
Item {
id: renameDevice
Layout.fillWidth: true
Layout.rightMargin: Tokens.spacing.small
implicitHeight: renameLabel.implicitHeight + deviceNameEdit.implicitHeight
states: State {
name: "editingDeviceName"
when: root.session.bt.editingDeviceName
AnchorChanges {
target: deviceNameEdit
anchors.top: renameDevice.top
}
PropertyChanges {
renameDevice.implicitHeight: deviceNameEdit.implicitHeight
renameLabel.opacity: 0
deviceNameEdit.padding: root.Tokens.padding.medium
}
}
transitions: Transition {
AnchorAnim {
type: AnchorAnim.Standard
}
Anim {
properties: "implicitHeight,opacity,padding"
}
}
StyledText {
id: renameLabel
anchors.left: parent.left
text: qsTr("Device name")
color: Colours.palette.m3outline
font: Tokens.font.label.small
}
StyledTextField {
id: deviceNameEdit
anchors.left: parent.left
anchors.right: parent.right
anchors.top: renameLabel.bottom
anchors.leftMargin: root.session.bt.editingDeviceName ? 0 : -Tokens.padding.medium
text: root.device?.name ?? ""
readOnly: !root.session.bt.editingDeviceName
onAccepted: {
root.session.bt.editingDeviceName = false;
root.device.name = text;
}
leftPadding: Tokens.padding.medium
rightPadding: Tokens.padding.medium
background: StyledRect {
radius: Tokens.rounding.medium
border.width: 2
border.color: Colours.palette.m3primary
opacity: root.session.bt.editingDeviceName ? 1 : 0
Behavior on border.color {
CAnim {}
}
Behavior on opacity {
Anim {}
}
}
Behavior on anchors.leftMargin {
Anim {}
}
}
}
StyledRect {
implicitWidth: implicitHeight
implicitHeight: cancelEditIcon.implicitHeight + Tokens.padding.large
radius: Tokens.rounding.medium
color: Colours.palette.m3secondaryContainer
opacity: root.session.bt.editingDeviceName ? 1 : 0
scale: root.session.bt.editingDeviceName ? 1 : 0.5
StateLayer {
onClicked: {
root.session.bt.editingDeviceName = false;
deviceNameEdit.text = Qt.binding(() => root.device?.name ?? "");
}
color: Colours.palette.m3onSecondaryContainer
disabled: !root.session.bt.editingDeviceName
}
MaterialIcon {
id: cancelEditIcon
anchors.centerIn: parent
animate: true
text: "cancel"
color: Colours.palette.m3onSecondaryContainer
}
Behavior on opacity {
Anim {}
}
Behavior on scale {
Anim {
type: Anim.FastSpatial
}
}
}
StyledRect {
implicitWidth: implicitHeight
implicitHeight: editIcon.implicitHeight + Tokens.padding.large
radius: root.session.bt.editingDeviceName ? Tokens.rounding.medium : implicitHeight / 2 * Math.min(1, Tokens.rounding.scale)
color: Qt.alpha(Colours.palette.m3primary, root.session.bt.editingDeviceName ? 1 : 0)
StateLayer {
onClicked: {
root.session.bt.editingDeviceName = !root.session.bt.editingDeviceName;
if (root.session.bt.editingDeviceName)
deviceNameEdit.forceActiveFocus();
else
deviceNameEdit.accepted();
}
color: root.session.bt.editingDeviceName ? Colours.palette.m3onPrimary : Colours.palette.m3onSurface
}
MaterialIcon {
id: editIcon
anchors.centerIn: parent
animate: true
text: root.session.bt.editingDeviceName ? "check_circle" : "edit"
color: root.session.bt.editingDeviceName ? Colours.palette.m3onPrimary : Colours.palette.m3onSurface
}
Behavior on radius {
Anim {}
}
}
}
Toggle {
label: qsTr("Trusted")
checked: root.device?.trusted ?? false
toggle.onToggled: root.device.trusted = checked
}
Toggle {
label: qsTr("Wake allowed")
checked: root.device?.wakeAllowed ?? false
toggle.onToggled: root.device.wakeAllowed = checked
}
}
}
}
},
Component {
ColumnLayout {
spacing: Tokens.spacing.medium
StyledText {
Layout.topMargin: Tokens.spacing.largeIncreased
text: qsTr("Device information")
font: Tokens.font.body.builders.large.weight(Font.Medium).build()
}
StyledText {
text: qsTr("Information about this device")
color: Colours.palette.m3outline
}
StyledRect {
Layout.fillWidth: true
implicitHeight: deviceInfo.implicitHeight + Tokens.padding.extraLargeIncreased
radius: Tokens.rounding.large
color: Colours.tPalette.m3surfaceContainer
ColumnLayout {
id: deviceInfo
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Tokens.padding.large
spacing: Tokens.spacing.extraSmall
StyledText {
text: root.device?.batteryAvailable ? qsTr("Device battery (%1%)").arg(root.device.battery * 100) : qsTr("Battery unavailable")
}
RowLayout {
id: batteryPercent
Layout.topMargin: Tokens.spacing.extraSmall
Layout.fillWidth: true
Layout.preferredHeight: Tokens.padding.small
spacing: Tokens.spacing.extraSmall
StyledRect {
Layout.fillWidth: true
Layout.fillHeight: true
radius: Tokens.rounding.full
color: Colours.palette.m3secondaryContainer
StyledRect {
anchors.left: parent.left
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.margins: parent.height * 0.25
implicitWidth: root.device?.batteryAvailable ? batteryPercent.width * root.device.battery : 0
radius: Tokens.rounding.full
color: Colours.palette.m3primary
}
}
}
StyledText {
Layout.topMargin: Tokens.spacing.medium
text: qsTr("Dbus path")
}
StyledText {
text: root.device?.dbusPath ?? ""
color: Colours.palette.m3outline
font: Tokens.font.body.small
}
StyledText {
Layout.topMargin: Tokens.spacing.medium
text: qsTr("MAC address")
}
StyledText {
text: root.device?.address ?? ""
color: Colours.palette.m3outline
font: Tokens.font.body.small
}
StyledText {
Layout.topMargin: Tokens.spacing.medium
text: qsTr("Bonded")
}
StyledText {
text: root.device?.bonded ? qsTr("Yes") : qsTr("No")
color: Colours.palette.m3outline
font: Tokens.font.body.small
}
StyledText {
Layout.topMargin: Tokens.spacing.medium
text: qsTr("System name")
}
StyledText {
text: root.device?.deviceName ?? ""
color: Colours.palette.m3outline
font: Tokens.font.body.small
}
}
}
}
}
]
}
}
ColumnLayout {
anchors.right: fabRoot.right
anchors.bottom: fabRoot.top
anchors.bottomMargin: Tokens.padding.medium
Repeater {
id: fabMenu
model: ListModel {
ListElement {
name: "trust"
icon: "handshake"
}
ListElement {
name: "block"
icon: "block"
}
ListElement {
name: "pair"
icon: "missing_controller"
}
ListElement {
name: "connect"
icon: "bluetooth_connected"
}
}
StyledClippingRect {
id: fabMenuItem
required property var modelData
required property int index
Layout.alignment: Qt.AlignRight
implicitHeight: fabMenuItemInner.implicitHeight + Tokens.padding.medium * 2
radius: Tokens.rounding.full
color: Colours.palette.m3primaryContainer
opacity: 0
states: State {
name: "visible"
when: root.session.bt.fabMenuOpen
PropertyChanges {
fabMenuItem.implicitWidth: fabMenuItemInner.implicitWidth + root.Tokens.padding.extraLargeIncreased
fabMenuItem.opacity: 1
fabMenuItemInner.opacity: 1
}
}
transitions: [
Transition {
to: "visible"
SequentialAnimation {
PauseAnimation {
duration: (fabMenu.count - 1 - fabMenuItem.index) * Tokens.anim.durations.small / 8
}
ParallelAnimation {
Anim {
property: "implicitWidth"
type: Anim.FastSpatial
}
Anim {
property: "opacity"
type: Anim.StandardSmall
}
}
}
},
Transition {
from: "visible"
SequentialAnimation {
PauseAnimation {
duration: fabMenuItem.index * Tokens.anim.durations.small / 8
}
ParallelAnimation {
Anim {
property: "implicitWidth"
type: Anim.FastSpatial
}
Anim {
property: "opacity"
type: Anim.StandardSmall
}
}
}
}
]
StateLayer {
onClicked: {
root.session.bt.fabMenuOpen = false;
const name = fabMenuItem.modelData.name;
if (fabMenuItem.modelData.name !== "pair")
root.device[`${name}ed`] = !root.device[`${name}ed`];
else if (root.device.paired)
root.device.forget();
else
root.device.pair();
}
}
RowLayout {
id: fabMenuItemInner
anchors.centerIn: parent
spacing: Tokens.spacing.medium
opacity: 0
MaterialIcon {
text: fabMenuItem.modelData.icon
color: Colours.palette.m3onPrimaryContainer
fill: 1
}
StyledText {
animate: true
text: (root.device && root.device[`${fabMenuItem.modelData.name}ed`] ? fabMenuItem.modelData.name === "connect" ? "dis" : "un" : "") + fabMenuItem.modelData.name
color: Colours.palette.m3onPrimaryContainer
font: Tokens.font.body.builders.small.capitalisation(Font.Capitalize).build()
Layout.preferredWidth: implicitWidth
Behavior on Layout.preferredWidth {
Anim {
type: Anim.StandardSmall
}
}
}
}
}
}
}
Item {
id: fabRoot
x: root.contentX + root.width - width
y: root.contentY + root.height - height
width: 64
height: 64
z: 10000
StyledRect {
id: fabBg
anchors.right: parent.right
anchors.top: parent.top
implicitWidth: 64
implicitHeight: 64
radius: Tokens.rounding.large
color: root.session.bt.fabMenuOpen ? Colours.palette.m3primary : Colours.palette.m3primaryContainer
states: State {
name: "expanded"
when: root.session.bt.fabMenuOpen
PropertyChanges {
fabBg.implicitWidth: 48
fabBg.implicitHeight: 48
fabBg.radius: 48 / 2
fab.fontStyle: Tokens.font.icon.large
}
}
transitions: Transition {
Anim {
properties: "implicitWidth,implicitHeight"
type: Anim.FastSpatial
}
Anim {
properties: "radius,font.pointSize"
}
}
Elevation {
anchors.fill: parent
radius: parent.radius
z: -1
level: fabState.containsMouse && !fabState.pressed ? 4 : 3
}
StateLayer {
id: fabState
onClicked: {
root.session.bt.fabMenuOpen = !root.session.bt.fabMenuOpen;
}
color: root.session.bt.fabMenuOpen ? Colours.palette.m3onPrimary : Colours.palette.m3onPrimaryContainer
}
MaterialIcon {
id: fab
anchors.centerIn: parent
animate: true
text: root.session.bt.fabMenuOpen ? "close" : "settings"
color: root.session.bt.fabMenuOpen ? Colours.palette.m3onPrimary : Colours.palette.m3onPrimaryContainer
fontStyle: Tokens.font.icon.large
fill: 1
}
}
}
component Toggle: RowLayout {
required property string label
property alias checked: toggle.checked
property alias toggle: toggle
Layout.fillWidth: true
spacing: Tokens.spacing.medium
StyledText {
Layout.fillWidth: true
text: parent.label
}
StyledSwitch {
id: toggle
cLayer: 2
}
}
}

View file

@ -1,265 +0,0 @@
pragma ComponentBehavior: Bound
import ".."
import "../components"
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Bluetooth
import Caelestia.Config
import qs.components
import qs.components.containers
import qs.components.controls
import qs.services
import qs.utils
DeviceList {
id: root
required property Session session
readonly property bool smallDiscoverable: width <= 540
readonly property bool smallPairable: width <= 480
title: qsTr("Devices (%1)").arg(Bluetooth.devices.values.length)
description: qsTr("All available bluetooth devices")
activeItem: session.bt.active
model: ScriptModel {
id: deviceModel
values: [...Bluetooth.devices.values].sort((a, b) => (b.connected - a.connected) || (b.paired - a.paired) || a.name.localeCompare(b.name))
}
headerComponent: Component {
RowLayout {
spacing: Tokens.spacing.medium
StyledText {
text: qsTr("Bluetooth")
font: Tokens.font.body.builders.large.weight(Font.Medium).build()
}
Item {
Layout.fillWidth: true
}
ToggleButton {
toggled: Bluetooth.defaultAdapter?.enabled ?? false
icon: "power"
accent: "Tertiary"
iconSize: Tokens.font.body.medium.pointSize
horizontalPadding: Tokens.padding.medium
verticalPadding: Tokens.padding.small
tooltip: qsTr("Toggle Bluetooth")
onClicked: {
const adapter = Bluetooth.defaultAdapter;
if (adapter)
adapter.enabled = !adapter.enabled;
}
}
ToggleButton {
toggled: Bluetooth.defaultAdapter?.discoverable ?? false
icon: root.smallDiscoverable ? "group_search" : ""
label: root.smallDiscoverable ? "" : qsTr("Discoverable")
iconSize: Tokens.font.body.medium.pointSize
horizontalPadding: Tokens.padding.medium
verticalPadding: Tokens.padding.small
tooltip: qsTr("Make discoverable")
onClicked: {
const adapter = Bluetooth.defaultAdapter;
if (adapter)
adapter.discoverable = !adapter.discoverable;
}
}
ToggleButton {
toggled: Bluetooth.defaultAdapter?.pairable ?? false
icon: "missing_controller"
label: root.smallPairable ? "" : qsTr("Pairable")
iconSize: Tokens.font.body.medium.pointSize
horizontalPadding: Tokens.padding.medium
verticalPadding: Tokens.padding.small
tooltip: qsTr("Make pairable")
onClicked: {
const adapter = Bluetooth.defaultAdapter;
if (adapter)
adapter.pairable = !adapter.pairable;
}
}
ToggleButton {
toggled: Bluetooth.defaultAdapter?.discovering ?? false
icon: "bluetooth_searching"
accent: "Secondary"
iconSize: Tokens.font.body.medium.pointSize
horizontalPadding: Tokens.padding.medium
verticalPadding: Tokens.padding.small
tooltip: qsTr("Scan for devices")
onClicked: {
const adapter = Bluetooth.defaultAdapter;
if (adapter)
adapter.discovering = !adapter.discovering;
}
}
ToggleButton {
toggled: !root.session.bt.active
icon: "settings"
accent: "Primary"
iconSize: Tokens.font.body.medium.pointSize
horizontalPadding: Tokens.padding.medium
verticalPadding: Tokens.padding.small
tooltip: qsTr("Bluetooth settings")
onClicked: {
if (root.session.bt.active)
root.session.bt.active = null;
else {
root.session.bt.active = root.model.values[0] ?? null;
}
}
}
}
}
delegate: Component {
StyledRect {
id: device
required property BluetoothDevice modelData
readonly property bool loading: modelData && (modelData.state === BluetoothDeviceState.Connecting || modelData.state === BluetoothDeviceState.Disconnecting)
readonly property bool connected: modelData && modelData.state === BluetoothDeviceState.Connected
width: ListView.view ? ListView.view.width : undefined
implicitHeight: deviceInner.implicitHeight + Tokens.padding.medium * 2
color: Qt.alpha(Colours.tPalette.m3surfaceContainer, root.activeItem === modelData ? Colours.tPalette.m3surfaceContainer.a : 0)
radius: Tokens.rounding.large
StateLayer {
id: stateLayer
onClicked: {
if (device.modelData)
root.session.bt.active = device.modelData;
}
}
RowLayout {
id: deviceInner
anchors.fill: parent
anchors.margins: Tokens.padding.medium
spacing: Tokens.spacing.medium
StyledRect {
implicitWidth: implicitHeight
implicitHeight: icon.implicitHeight + Tokens.padding.medium * 2
radius: Tokens.rounding.large
color: device.connected ? Colours.palette.m3primaryContainer : (device.modelData && device.modelData.bonded) ? Colours.palette.m3secondaryContainer : Colours.tPalette.m3surfaceContainerHigh
StyledRect {
anchors.fill: parent
radius: parent.radius
color: Qt.alpha(device.connected ? Colours.palette.m3onPrimaryContainer : (device.modelData && device.modelData.bonded) ? Colours.palette.m3onSecondaryContainer : Colours.palette.m3onSurface, stateLayer.pressed ? 0.1 : stateLayer.containsMouse ? 0.08 : 0)
}
MaterialIcon {
id: icon
anchors.centerIn: parent
text: Icons.getBluetoothIcon(device.modelData ? device.modelData.icon : "")
color: device.connected ? Colours.palette.m3onPrimaryContainer : (device.modelData && device.modelData.bonded) ? Colours.palette.m3onSecondaryContainer : Colours.palette.m3onSurface
fontStyle: Tokens.font.icon.large
fill: device.connected ? 1 : 0
Behavior on fill {
Anim {
type: Anim.DefaultEffects
}
}
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: 0
StyledText {
Layout.fillWidth: true
text: device.modelData ? device.modelData.name : qsTr("Unknown")
elide: Text.ElideRight
}
StyledText {
Layout.fillWidth: true
text: (device.modelData ? device.modelData.address : "") + (device.connected ? qsTr(" (Connected)") : (device.modelData && device.modelData.bonded) ? qsTr(" (Paired)") : "")
color: Colours.palette.m3outline
font: Tokens.font.body.small
elide: Text.ElideRight
}
}
StyledRect {
id: connectBtn
implicitWidth: implicitHeight
implicitHeight: connectIcon.implicitHeight + Tokens.padding.large
radius: Tokens.rounding.full
color: Qt.alpha(Colours.palette.m3primaryContainer, device.connected ? 1 : 0)
CircularIndicator {
anchors.fill: parent
running: device.loading
}
StateLayer {
onClicked: {
if (device.loading)
return;
if (device.connected) {
device.modelData.connected = false;
} else {
if (device.modelData.bonded) {
device.modelData.connected = true;
} else {
device.modelData.pair();
}
}
}
color: device.connected ? Colours.palette.m3onPrimaryContainer : Colours.palette.m3onSurface
disabled: device.loading
}
MaterialIcon {
id: connectIcon
anchors.centerIn: parent
animate: true
text: device.connected ? "link_off" : "link"
color: device.connected ? Colours.palette.m3onPrimaryContainer : Colours.palette.m3onSurface
opacity: device.loading ? 0 : 1
Behavior on opacity {
Anim {}
}
}
}
}
}
}
onItemSelected: item => session.bt.active = item
}

View file

@ -1,521 +0,0 @@
pragma ComponentBehavior: Bound
import ".."
import "../components"
import QtQuick
import QtQuick.Layouts
import Quickshell.Bluetooth
import Caelestia.Config
import qs.components
import qs.components.controls
import qs.components.effects
import qs.services
ColumnLayout {
id: root
required property Session session
spacing: Tokens.spacing.medium
SettingsHeader {
icon: "bluetooth"
title: qsTr("Bluetooth Settings")
}
StyledText {
Layout.topMargin: Tokens.spacing.largeIncreased
text: qsTr("Adapter status")
font: Tokens.font.body.builders.large.weight(Font.Medium).build()
}
StyledText {
text: qsTr("General adapter settings")
color: Colours.palette.m3outline
}
StyledRect {
Layout.fillWidth: true
implicitHeight: adapterStatus.implicitHeight + Tokens.padding.extraLargeIncreased
radius: Tokens.rounding.large
color: Colours.tPalette.m3surfaceContainer
ColumnLayout {
id: adapterStatus
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Tokens.padding.large
spacing: Tokens.spacing.large
Toggle {
label: qsTr("Powered")
checked: Bluetooth.defaultAdapter?.enabled ?? false
toggle.onToggled: {
const adapter = Bluetooth.defaultAdapter;
if (adapter)
adapter.enabled = checked;
}
}
Toggle {
label: qsTr("Discoverable")
checked: Bluetooth.defaultAdapter?.discoverable ?? false
toggle.onToggled: {
const adapter = Bluetooth.defaultAdapter;
if (adapter)
adapter.discoverable = checked;
}
}
Toggle {
label: qsTr("Pairable")
checked: Bluetooth.defaultAdapter?.pairable ?? false
toggle.onToggled: {
const adapter = Bluetooth.defaultAdapter;
if (adapter)
adapter.pairable = checked;
}
}
}
}
StyledText {
Layout.topMargin: Tokens.spacing.largeIncreased
text: qsTr("Adapter properties")
font: Tokens.font.body.builders.large.weight(Font.Medium).build()
}
StyledText {
text: qsTr("Per-adapter settings")
color: Colours.palette.m3outline
}
StyledRect {
Layout.fillWidth: true
implicitHeight: adapterSettings.implicitHeight + Tokens.padding.extraLargeIncreased
radius: Tokens.rounding.large
color: Colours.tPalette.m3surfaceContainer
ColumnLayout {
id: adapterSettings
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Tokens.padding.large
spacing: Tokens.spacing.large
RowLayout {
Layout.fillWidth: true
spacing: Tokens.spacing.medium
StyledText {
Layout.fillWidth: true
text: qsTr("Current adapter")
}
Item {
id: adapterPickerButton
property bool expanded
implicitWidth: adapterPicker.implicitWidth + Tokens.padding.medium * 2
implicitHeight: adapterPicker.implicitHeight + Tokens.padding.large
StateLayer {
onClicked: {
adapterPickerButton.expanded = !adapterPickerButton.expanded;
}
radius: Tokens.rounding.medium
}
RowLayout {
id: adapterPicker
anchors.fill: parent
anchors.margins: Tokens.padding.medium
anchors.topMargin: Tokens.padding.small
anchors.bottomMargin: Tokens.padding.small
spacing: Tokens.spacing.medium
StyledText {
Layout.leftMargin: Tokens.padding.extraSmall
text: Bluetooth.defaultAdapter?.name ?? qsTr("None")
}
MaterialIcon {
text: "expand_more"
}
}
Elevation {
anchors.fill: adapterListBg
radius: adapterListBg.radius
opacity: adapterPickerButton.expanded ? 1 : 0
scale: adapterPickerButton.expanded ? 1 : 0.7
level: 2
Behavior on opacity {
Anim {}
}
Behavior on scale {
Anim {
type: Anim.FastSpatial
}
}
}
StyledClippingRect {
id: adapterListBg
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
implicitHeight: adapterPickerButton.expanded ? adapterList.implicitHeight : adapterPickerButton.implicitHeight
color: Colours.palette.m3secondaryContainer
radius: Tokens.rounding.medium
opacity: adapterPickerButton.expanded ? 1 : 0
scale: adapterPickerButton.expanded ? 1 : 0.7
ColumnLayout {
id: adapterList
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: 0
Repeater {
model: Bluetooth.adapters
Item {
id: adapter
required property BluetoothAdapter modelData
Layout.fillWidth: true
implicitHeight: adapterInner.implicitHeight + Tokens.padding.medium * 2
StateLayer {
onClicked: {
adapterPickerButton.expanded = false;
root.session.bt.currentAdapter = adapter.modelData;
}
disabled: !adapterPickerButton.expanded
}
RowLayout {
id: adapterInner
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Tokens.padding.medium
spacing: Tokens.spacing.medium
StyledText {
Layout.fillWidth: true
Layout.leftMargin: Tokens.padding.extraSmall
text: adapter.modelData.name
color: Colours.palette.m3onSecondaryContainer
}
MaterialIcon {
text: "check"
color: Colours.palette.m3onSecondaryContainer
visible: adapter.modelData === root.session.bt.currentAdapter
}
}
}
}
}
Behavior on opacity {
Anim {}
}
Behavior on scale {
Anim {
type: Anim.FastSpatial
}
}
Behavior on implicitHeight {
Anim {}
}
}
}
}
RowLayout {
Layout.fillWidth: true
spacing: Tokens.spacing.medium
StyledText {
Layout.fillWidth: true
text: qsTr("Discoverable timeout")
}
CustomSpinBox {
min: 0
value: root.session.bt.currentAdapter?.discoverableTimeout ?? 0
onValueModified: value => {
if (root.session.bt.currentAdapter) {
root.session.bt.currentAdapter.discoverableTimeout = value;
}
}
}
}
RowLayout {
Layout.fillWidth: true
spacing: Tokens.spacing.small
Item {
id: renameAdapter
Layout.fillWidth: true
Layout.rightMargin: Tokens.spacing.small
implicitHeight: renameLabel.implicitHeight + adapterNameEdit.implicitHeight
states: State {
name: "editingAdapterName"
when: root.session.bt.editingAdapterName
AnchorChanges {
target: adapterNameEdit
anchors.top: renameAdapter.top
}
PropertyChanges {
renameAdapter.implicitHeight: adapterNameEdit.implicitHeight
renameLabel.opacity: 0
adapterNameEdit.padding: Tokens.padding.medium
}
}
transitions: Transition {
AnchorAnim {
type: AnchorAnim.Standard
}
Anim {
properties: "implicitHeight,opacity,padding"
}
}
StyledText {
id: renameLabel
anchors.left: parent.left
text: qsTr("Rename adapter (currently does not work)")
color: Colours.palette.m3outline
font: Tokens.font.label.small
}
StyledTextField {
id: adapterNameEdit
anchors.left: parent.left
anchors.right: parent.right
anchors.top: renameLabel.bottom
anchors.leftMargin: root.session.bt.editingAdapterName ? 0 : -Tokens.padding.medium
text: root.session.bt.currentAdapter?.name ?? ""
readOnly: !root.session.bt.editingAdapterName
onAccepted: {
root.session.bt.editingAdapterName = false;
}
leftPadding: Tokens.padding.medium
rightPadding: Tokens.padding.medium
background: StyledRect {
radius: Tokens.rounding.medium
border.width: 2
border.color: Colours.palette.m3primary
opacity: root.session.bt.editingAdapterName ? 1 : 0
Behavior on border.color {
CAnim {}
}
Behavior on opacity {
Anim {}
}
}
Behavior on anchors.leftMargin {
Anim {}
}
}
}
StyledRect {
implicitWidth: implicitHeight
implicitHeight: cancelEditIcon.implicitHeight + Tokens.padding.large
radius: Tokens.rounding.medium
color: Colours.palette.m3secondaryContainer
opacity: root.session.bt.editingAdapterName ? 1 : 0
scale: root.session.bt.editingAdapterName ? 1 : 0.5
StateLayer {
onClicked: {
root.session.bt.editingAdapterName = false;
adapterNameEdit.text = Qt.binding(() => root.session.bt.currentAdapter?.name ?? "");
}
color: Colours.palette.m3onSecondaryContainer
disabled: !root.session.bt.editingAdapterName
}
MaterialIcon {
id: cancelEditIcon
anchors.centerIn: parent
animate: true
text: "cancel"
color: Colours.palette.m3onSecondaryContainer
}
Behavior on opacity {
Anim {}
}
Behavior on scale {
Anim {
type: Anim.FastSpatial
}
}
}
StyledRect {
implicitWidth: implicitHeight
implicitHeight: editIcon.implicitHeight + Tokens.padding.large
radius: root.session.bt.editingAdapterName ? Tokens.rounding.medium : implicitHeight / 2 * Math.min(1, Tokens.rounding.scale)
color: Qt.alpha(Colours.palette.m3primary, root.session.bt.editingAdapterName ? 1 : 0)
StateLayer {
onClicked: {
root.session.bt.editingAdapterName = !root.session.bt.editingAdapterName;
if (root.session.bt.editingAdapterName)
adapterNameEdit.forceActiveFocus();
else
adapterNameEdit.accepted();
}
color: root.session.bt.editingAdapterName ? Colours.palette.m3onPrimary : Colours.palette.m3onSurface
}
MaterialIcon {
id: editIcon
anchors.centerIn: parent
animate: true
text: root.session.bt.editingAdapterName ? "check_circle" : "edit"
color: root.session.bt.editingAdapterName ? Colours.palette.m3onPrimary : Colours.palette.m3onSurface
}
Behavior on radius {
Anim {}
}
}
}
}
}
StyledText {
Layout.topMargin: Tokens.spacing.largeIncreased
text: qsTr("Adapter information")
font: Tokens.font.body.builders.large.weight(Font.Medium).build()
}
StyledText {
text: qsTr("Information about the default adapter")
color: Colours.palette.m3outline
}
StyledRect {
Layout.fillWidth: true
implicitHeight: adapterInfo.implicitHeight + Tokens.padding.extraLargeIncreased
radius: Tokens.rounding.large
color: Colours.tPalette.m3surfaceContainer
ColumnLayout {
id: adapterInfo
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Tokens.padding.large
spacing: Tokens.spacing.extraSmall
StyledText {
text: qsTr("Adapter state")
}
StyledText {
text: Bluetooth.defaultAdapter ? BluetoothAdapterState.toString(Bluetooth.defaultAdapter.state) : qsTr("Unknown")
color: Colours.palette.m3outline
font: Tokens.font.body.small
}
StyledText {
Layout.topMargin: Tokens.spacing.medium
text: qsTr("Dbus path")
}
StyledText {
text: Bluetooth.defaultAdapter?.dbusPath ?? ""
color: Colours.palette.m3outline
font: Tokens.font.body.small
}
StyledText {
Layout.topMargin: Tokens.spacing.medium
text: qsTr("Adapter id")
}
StyledText {
text: Bluetooth.defaultAdapter?.adapterId ?? ""
color: Colours.palette.m3outline
font: Tokens.font.body.small
}
}
}
component Toggle: RowLayout {
required property string label
property alias checked: toggle.checked
property alias toggle: toggle
Layout.fillWidth: true
spacing: Tokens.spacing.medium
StyledText {
Layout.fillWidth: true
text: parent.label
}
StyledSwitch {
id: toggle
cLayer: 2
}
}
}

View file

@ -1,114 +0,0 @@
import ".."
import QtQuick
import QtQuick.Layouts
import Caelestia.Config
import qs.components
import qs.components.controls
import qs.components.effects
import qs.services
StyledRect {
id: root
property var options: [] // Array of {label: string, propertyName: string, onToggled: function, state: bool?}
property var rootItem: null // The root item that contains the properties we want to bind to
property string title: "" // Optional title text
property int rows: 1 // Number of rows
Layout.fillWidth: true
implicitHeight: layout.implicitHeight + Tokens.padding.extraLargeIncreased
radius: Tokens.rounding.large
color: Colours.layer(Colours.palette.m3surfaceContainer, 2)
clip: true
Behavior on implicitHeight {
Anim {}
}
ColumnLayout {
id: layout
anchors.fill: parent
anchors.margins: Tokens.padding.large
spacing: Tokens.spacing.medium
StyledText {
visible: root.title !== ""
text: root.title
font: Tokens.font.title.small
}
GridLayout {
id: buttonGrid
Layout.alignment: Qt.AlignHCenter
rowSpacing: Tokens.spacing.small
columnSpacing: Tokens.spacing.small
rows: root.rows
columns: Math.ceil(root.options.length / root.rows)
Repeater {
id: repeater
model: root.options
delegate: TextButton {
id: button
required property int index
required property var modelData
property bool _checked: false
Layout.fillWidth: true
text: modelData.label
checked: _checked
isToggle: false
type: TextButton.Tonal
// Create binding in Component.onCompleted
Component.onCompleted: {
if (modelData.state !== undefined && modelData.state) {
_checked = modelData.state;
} else if (root.rootItem && modelData.propertyName) {
const propName = modelData.propertyName;
const rootItem = root.rootItem;
_checked = Qt.binding(function () {
return rootItem[propName] ?? false;
});
}
}
// Match utilities Toggles radius styling
// Each button has full rounding (not connected) since they have spacing
radius: stateLayer.pressed ? Tokens.rounding.medium / 2 : internalChecked ? Tokens.rounding.medium : Tokens.rounding.large
// Match utilities Toggles inactive color
inactiveColour: Colours.layer(Colours.palette.m3surfaceContainerHighest, 2)
// Adjust width similar to utilities toggles
Layout.preferredWidth: implicitWidth + (stateLayer.pressed ? Tokens.padding.large : internalChecked ? Tokens.padding.small : 0)
onClicked: {
if (modelData.onToggled && root.rootItem && modelData.propertyName) {
const currentValue = root.rootItem[modelData.propertyName] ?? false;
modelData.onToggled(!currentValue);
}
}
Behavior on Layout.preferredWidth {
Anim {
type: Anim.FastSpatial
}
}
Behavior on radius {
Anim {
type: Anim.FastSpatial
}
}
}
}
}
}
}

View file

@ -1,74 +0,0 @@
pragma ComponentBehavior: Bound
import ".."
import QtQuick
import QtQuick.Layouts
import Caelestia.Config
import qs.components
import qs.components.containers
import qs.components.controls
import qs.components.effects
Item {
id: root
property Session session
property var device: null
property Component headerComponent: null
property list<Component> sections: []
property Component topContent: null
property Component bottomContent: null
implicitWidth: layout.implicitWidth
implicitHeight: layout.implicitHeight
ColumnLayout {
id: layout
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
spacing: Tokens.spacing.medium
Loader {
id: headerLoader
Layout.fillWidth: true
asynchronous: true
sourceComponent: root.headerComponent
visible: root.headerComponent !== null
}
Loader {
id: topContentLoader
Layout.fillWidth: true
asynchronous: true
sourceComponent: root.topContent
visible: root.topContent !== null
}
Repeater {
model: root.sections
Loader {
required property Component modelData
Layout.fillWidth: true
asynchronous: true
sourceComponent: modelData
}
}
Loader {
id: bottomContentLoader
Layout.fillWidth: true
asynchronous: true
sourceComponent: root.bottomContent
visible: root.bottomContent !== null
}
}
}

View file

@ -1,84 +0,0 @@
pragma ComponentBehavior: Bound
import ".."
import QtQuick
import QtQuick.Layouts
import Quickshell
import Caelestia.Config
import qs.components
import qs.components.containers
import qs.components.controls
import qs.services
ColumnLayout {
id: root
property Session session: null
property var model: null
property Component delegate: null
property string title: ""
property string description: ""
property var activeItem: null
property Component headerComponent: null
property Component titleSuffix: null
property bool showHeader: true
property alias view: view
signal itemSelected(var item)
spacing: Tokens.spacing.small
Loader {
id: headerLoader
Layout.fillWidth: true
asynchronous: true
sourceComponent: root.headerComponent
visible: root.headerComponent !== null && root.showHeader
}
RowLayout {
Layout.fillWidth: true
Layout.topMargin: root.headerComponent ? 0 : 0
spacing: Tokens.spacing.small
visible: root.title !== "" || root.description !== ""
StyledText {
visible: root.title !== ""
text: root.title
font: Tokens.font.title.builders.medium.weight(Font.Medium).build()
}
Loader {
asynchronous: true
sourceComponent: root.titleSuffix
visible: root.titleSuffix !== null
}
Item {
Layout.fillWidth: true
}
}
StyledText {
visible: root.description !== ""
Layout.fillWidth: true
text: root.description
color: Colours.palette.m3outline
}
StyledListView {
id: view
Layout.fillWidth: true
implicitHeight: contentHeight
model: root.model
delegate: root.delegate
spacing: Tokens.spacing.extraSmall
interactive: false
clip: false
}
}

View file

@ -1,67 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import Caelestia.Config
SequentialAnimation {
id: root
required property Item target
property list<PropertyAction> propertyActions
property real scaleFrom: 1.0
property real scaleTo: 0.8
property real opacityFrom: 1.0
property real opacityTo: 0.0
ParallelAnimation {
NumberAnimation {
target: root.target
property: "opacity"
from: root.opacityFrom
to: root.opacityTo
duration: Tokens.anim.durations.normal / 2
easing: Tokens.anim.standardAccel
}
NumberAnimation {
target: root.target
property: "scale"
from: root.scaleFrom
to: root.scaleTo
duration: Tokens.anim.durations.normal / 2
easing: Tokens.anim.standardAccel
}
}
ScriptAction {
script: {
for (let i = 0; i < root.propertyActions.length; i++) {
const action = root.propertyActions[i];
if (action.target && action.property !== undefined) {
action.target[action.property] = action.value;
}
}
}
}
ParallelAnimation {
NumberAnimation {
target: root.target
property: "opacity"
from: root.opacityTo
to: root.opacityFrom
duration: Tokens.anim.durations.normal / 2
easing: Tokens.anim.standardDecel
}
NumberAnimation {
target: root.target
property: "scale"
from: root.scaleTo
to: root.scaleFrom
duration: Tokens.anim.durations.normal / 2
easing: Tokens.anim.standardDecel
}
}
}

View file

@ -1,67 +0,0 @@
import ".."
import "../components"
import QtQuick
import QtQuick.Layouts
import Caelestia.Config
import qs.components
import qs.components.controls
import qs.services
ColumnLayout {
id: root
property string label: ""
property real value: 0
property real from: 0
property real to: 100
property string suffix: ""
property bool readonly: false
spacing: Tokens.spacing.small
RowLayout {
Layout.fillWidth: true
spacing: Tokens.spacing.medium
StyledText {
visible: root.label !== ""
text: root.label
font: Tokens.font.label.medium
color: root.readonly ? Colours.palette.m3outline : Colours.palette.m3onSurface
}
Item {
Layout.fillWidth: true
}
MaterialIcon {
visible: root.readonly
text: "lock"
color: Colours.palette.m3outline
fontStyle: Tokens.font.icon.small
}
StyledText {
text: Math.round(root.value) + (root.suffix !== "" ? " " + root.suffix : "")
font: Tokens.font.body.medium
color: root.readonly ? Colours.palette.m3outline : Colours.palette.m3onSurface
}
}
StyledRect {
Layout.fillWidth: true
implicitHeight: Tokens.padding.medium
radius: Tokens.rounding.full
color: Colours.layer(Colours.palette.m3surfaceContainerHighest, 1)
opacity: root.readonly ? 0.5 : 1.0
StyledRect {
anchors.left: parent.left
anchors.top: parent.top
anchors.bottom: parent.bottom
width: parent.width * ((root.value - root.from) / (root.to - root.from))
radius: parent.radius
color: root.readonly ? Colours.palette.m3outline : Colours.palette.m3primary
}
}
}

View file

@ -1,35 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Caelestia.Config
import qs.components
Item {
id: root
required property string icon
required property string title
Layout.fillWidth: true
implicitHeight: column.implicitHeight
ColumnLayout {
id: column
anchors.centerIn: parent
spacing: Tokens.spacing.medium
MaterialIcon {
Layout.alignment: Qt.AlignHCenter
text: root.icon
fontStyle: Tokens.font.icon.size(84).weight(Font.Bold).build()
}
StyledText {
Layout.alignment: Qt.AlignHCenter
text: root.title
font: Tokens.font.title.builders.medium.weight(Font.Bold).build()
}
}
}

View file

@ -1,180 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Caelestia.Config
import qs.components
import qs.components.controls
import qs.components.effects
import qs.services
ColumnLayout {
id: root
property string label: ""
property real value: 0
property real from: 0
property real to: 100
property real stepSize: 0
property var validator: null
property string suffix: "" // Optional suffix text (e.g., "×", "px")
property int decimals: 1 // Number of decimal places to show (default: 1)
property var formatValueFunction: null // Optional custom format function
property var parseValueFunction: null // Optional custom parse function
property bool _initialized: false
signal valueModified(real newValue)
function formatValue(val: real): string {
if (formatValueFunction) {
return formatValueFunction(val);
}
// Default format function
// Check if it's an IntValidator (IntValidator doesn't have a 'decimals' property)
if (validator && validator.bottom !== undefined && validator.decimals === undefined) {
return Math.round(val).toString();
}
// For DoubleValidator or no validator, use the decimals property
return val.toFixed(root.decimals);
}
function parseValue(text: string): real {
if (parseValueFunction) {
return parseValueFunction(text);
}
// Default parse function
if (validator && validator.bottom !== undefined) {
// Check if it's an integer validator
if (validator.top !== undefined && validator.top === Math.floor(validator.top)) {
return parseInt(text);
}
}
return parseFloat(text);
}
spacing: Tokens.spacing.small
Component.onCompleted: {
// Set initialized flag after a brief delay to allow component to fully load
Qt.callLater(() => {
_initialized = true;
});
}
// Update input field when value changes externally (slider is already bound)
onValueChanged: {
// Only update if component is initialized to avoid issues during creation
if (root._initialized && !inputField.hasFocus) {
inputField.text = root.formatValue(root.value);
}
}
RowLayout {
Layout.fillWidth: true
spacing: Tokens.spacing.medium
StyledText {
visible: root.label !== ""
text: root.label
font: Tokens.font.label.medium
}
Item {
Layout.fillWidth: true
}
StyledInputField {
id: inputField
Layout.preferredWidth: 70
validator: root.validator
Component.onCompleted: {
// Initialize text without triggering valueModified signal
text = root.formatValue(root.value);
}
onTextEdited: text => {
if (hasFocus) {
const val = root.parseValue(text);
if (!isNaN(val)) {
// Validate against validator bounds if available
let isValid = true;
if (root.validator) {
if (root.validator.bottom !== undefined && val < root.validator.bottom) {
isValid = false;
}
if (root.validator.top !== undefined && val > root.validator.top) {
isValid = false;
}
}
if (isValid) {
root.valueModified(val);
}
}
}
}
onEditingFinished: {
const val = root.parseValue(text);
let isValid = true;
if (root.validator) {
if (root.validator.bottom !== undefined && val < root.validator.bottom) {
isValid = false;
}
if (root.validator.top !== undefined && val > root.validator.top) {
isValid = false;
}
}
if (isNaN(val) || !isValid) {
text = root.formatValue(root.value);
}
}
}
StyledText {
visible: root.suffix !== ""
text: root.suffix
color: Colours.palette.m3outline
font: Tokens.font.body.medium
}
}
StyledSlider {
id: slider
Layout.fillWidth: true
implicitHeight: Tokens.padding.medium * 3
from: root.from
to: root.to
stepSize: root.stepSize
onValueChanged: {
// Update input field text in real-time as slider moves during dragging
// Always update when slider value changes (during dragging or external updates)
if (!inputField.hasFocus) {
const newValue = root.stepSize > 0 ? Math.round(value / root.stepSize) * root.stepSize : value;
inputField.text = root.formatValue(newValue);
}
}
onMoved: {
const newValue = root.stepSize > 0 ? Math.round(value / root.stepSize) * root.stepSize : value;
root.valueModified(newValue);
if (!inputField.hasFocus) {
inputField.text = root.formatValue(newValue);
}
}
// Use Binding to allow slider to move freely during dragging
Binding {
target: slider
property: "value"
value: root.value
when: !slider.pressed
}
}
}

View file

@ -1,109 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell.Widgets
import Caelestia.Config
import qs.components
import qs.components.effects
RowLayout {
id: root
property Component leftContent: null
property Component rightContent: null
property real leftWidthRatio: 0.4
property int leftMinimumWidth: 420
property var leftLoaderProperties: ({})
property var rightLoaderProperties: ({})
property alias leftLoader: leftLoader
property alias rightLoader: rightLoader
spacing: 0
Item {
id: leftPane
Layout.preferredWidth: Math.floor(parent.width * root.leftWidthRatio)
Layout.minimumWidth: root.leftMinimumWidth
Layout.fillHeight: true
ClippingRectangle {
id: leftClippingRect
anchors.fill: parent
anchors.margins: Tokens.padding.medium
anchors.leftMargin: 0
anchors.rightMargin: Tokens.padding.medium / 2
radius: leftBorder.innerRadius
color: "transparent"
Loader {
id: leftLoader
anchors.fill: parent
anchors.margins: Tokens.padding.large + Tokens.padding.medium
anchors.leftMargin: Tokens.padding.large
anchors.rightMargin: Tokens.padding.large + Tokens.padding.medium / 2
asynchronous: true
sourceComponent: root.leftContent
Component.onCompleted: {
for (const key in root.leftLoaderProperties) {
leftLoader[key] = root.leftLoaderProperties[key];
}
}
}
}
InnerBorder {
id: leftBorder
leftThickness: 0
rightThickness: Tokens.padding.medium / 2
}
}
Item {
id: rightPane
Layout.fillWidth: true
Layout.fillHeight: true
ClippingRectangle {
id: rightClippingRect
anchors.fill: parent
anchors.margins: Tokens.padding.medium
anchors.leftMargin: 0
anchors.rightMargin: Tokens.padding.medium / 2
radius: rightBorder.innerRadius
color: "transparent"
Loader {
id: rightLoader
anchors.fill: parent
anchors.margins: Tokens.padding.extraLargeIncreased
asynchronous: true
sourceComponent: root.rightContent
Component.onCompleted: {
for (const key in root.rightLoaderProperties) {
rightLoader[key] = root.rightLoaderProperties[key];
}
}
}
}
InnerBorder {
id: rightBorder
leftThickness: Tokens.padding.medium / 2
}
}
}

View file

@ -1,95 +0,0 @@
pragma ComponentBehavior: Bound
import ".."
import QtQuick
import QtQuick.Layouts
import Quickshell.Widgets
import Caelestia.Config
import qs.components
import qs.components.containers
import qs.components.effects
Item {
id: root
required property Component leftContent
required property Component rightDetailsComponent
required property Component rightSettingsComponent
property var activeItem: null
property var paneIdGenerator: function (item) {
return item ? String(item) : "";
}
property Component overlayComponent: null
SplitPaneLayout {
id: splitLayout
anchors.fill: parent
leftContent: root.leftContent
rightContent: Component {
Item {
id: rightPaneItem
property var pane: root.activeItem
property string paneId: root.paneIdGenerator(pane)
property Component targetComponent: root.rightSettingsComponent
property Component nextComponent: root.rightSettingsComponent
function getComponentForPane() {
return pane ? root.rightDetailsComponent : root.rightSettingsComponent;
}
Component.onCompleted: {
targetComponent = getComponentForPane();
nextComponent = targetComponent;
}
onPaneChanged: {
nextComponent = getComponentForPane();
paneId = root.paneIdGenerator(pane);
}
Loader {
id: rightLoader
anchors.fill: parent
asynchronous: true
opacity: 1
scale: 1
transformOrigin: Item.Center
clip: false
sourceComponent: rightPaneItem.targetComponent
}
Behavior on paneId {
PaneTransition {
target: rightLoader
propertyActions: [
PropertyAction {
target: rightPaneItem
property: "targetComponent"
value: rightPaneItem.nextComponent
}
]
}
}
}
}
}
Loader {
id: overlayLoader
anchors.fill: parent
asynchronous: true
z: 1000
sourceComponent: root.overlayComponent
active: root.overlayComponent !== null
}
}

View file

@ -1,233 +0,0 @@
pragma ComponentBehavior: Bound
import ".."
import QtQuick
import Caelestia.Config
import Caelestia.Models
import qs.components
import qs.components.controls
import qs.components.effects
import qs.components.images
import qs.services
GridView {
id: root
required property Session session
readonly property int minCellWidth: 200 + Tokens.spacing.medium
readonly property int columnsCount: Math.max(1, Math.floor(width / minCellWidth))
cellWidth: width / columnsCount
cellHeight: 140 + Tokens.spacing.medium
model: Wallpapers.list
clip: true
StyledScrollBar.vertical: StyledScrollBar {
flickable: root
}
delegate: Item {
required property var modelData
required property int index
readonly property bool isCurrent: modelData && modelData.path === Wallpapers.actualCurrent
readonly property real itemMargin: Tokens.spacing.medium / 2
readonly property real itemRadius: Tokens.rounding.large
width: root.cellWidth
height: root.cellHeight
StateLayer {
onClicked: {
Wallpapers.setWallpaper(modelData.path);
}
anchors.fill: parent
anchors.leftMargin: itemMargin
anchors.rightMargin: itemMargin
anchors.topMargin: itemMargin
anchors.bottomMargin: itemMargin
radius: itemRadius
}
StyledClippingRect {
id: image
anchors.fill: parent
anchors.leftMargin: itemMargin
anchors.rightMargin: itemMargin
anchors.topMargin: itemMargin
anchors.bottomMargin: itemMargin
color: Colours.tPalette.m3surfaceContainer
radius: itemRadius
antialiasing: true
layer.enabled: true
layer.smooth: true
CachingImage {
id: cachingImage
path: modelData.path
anchors.fill: parent
fillMode: Image.PreserveAspectCrop
cache: true
visible: opacity > 0
antialiasing: true
smooth: true
sourceSize: Qt.size(width, height)
opacity: status === Image.Ready ? 1 : 0
Behavior on opacity {
NumberAnimation {
duration: 1000
easing.type: Easing.OutQuad
}
}
}
// Fallback if CachingImage fails to load
Image {
id: fallbackImage
anchors.fill: parent
source: fallbackTimer.triggered && cachingImage.status !== Image.Ready ? modelData.path : ""
asynchronous: true
fillMode: Image.PreserveAspectCrop
cache: true
visible: opacity > 0
antialiasing: true
smooth: true
sourceSize: Qt.size(width, height)
opacity: status === Image.Ready && cachingImage.status !== Image.Ready ? 1 : 0
Behavior on opacity {
NumberAnimation {
duration: 1000
easing.type: Easing.OutQuad
}
}
}
Timer {
id: fallbackTimer
property bool triggered: false
interval: 800
running: cachingImage.status === Image.Loading || cachingImage.status === Image.Null
onTriggered: triggered = true
}
// Gradient overlay for filename
Rectangle {
id: filenameOverlay
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
implicitHeight: filenameText.implicitHeight + Tokens.padding.medium * 1.5
radius: 0
gradient: Gradient {
GradientStop {
position: 0.0
color: Qt.rgba(Colours.palette.m3surface.r, Colours.palette.m3surface.g, Colours.palette.m3surface.b, 0)
}
GradientStop {
position: 0.3
color: Qt.rgba(Colours.palette.m3surface.r, Colours.palette.m3surface.g, Colours.palette.m3surface.b, 0.7)
}
GradientStop {
position: 0.6
color: Qt.rgba(Colours.palette.m3surface.r, Colours.palette.m3surface.g, Colours.palette.m3surface.b, 0.9)
}
GradientStop {
position: 1.0
color: Qt.rgba(Colours.palette.m3surface.r, Colours.palette.m3surface.g, Colours.palette.m3surface.b, 0.95)
}
}
opacity: 0
Component.onCompleted: {
opacity = 1;
}
Behavior on opacity {
NumberAnimation {
duration: 1000
easing.type: Easing.OutCubic
}
}
}
}
Rectangle {
anchors.fill: parent
anchors.leftMargin: itemMargin
anchors.rightMargin: itemMargin
anchors.topMargin: itemMargin
anchors.bottomMargin: itemMargin
color: "transparent"
radius: itemRadius + border.width
border.width: isCurrent ? 2 : 0
border.color: Colours.palette.m3primary
antialiasing: true
smooth: true
Behavior on border.width {
NumberAnimation {
duration: 150
easing.type: Easing.OutQuad
}
}
MaterialIcon {
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: Tokens.padding.extraSmall
visible: isCurrent
text: "check_circle"
color: Colours.palette.m3primary
fontStyle: Tokens.font.icon.large
}
}
StyledText {
id: filenameText
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.leftMargin: Tokens.padding.medium + Tokens.spacing.medium / 2
anchors.rightMargin: Tokens.padding.medium + Tokens.spacing.medium / 2
anchors.bottomMargin: Tokens.padding.medium
text: modelData.name
font: Tokens.font.body.builders.small.weight(Font.Medium).build()
color: isCurrent ? Colours.palette.m3primary : Colours.palette.m3onSurface
elide: Text.ElideMiddle
maximumLineCount: 1
horizontalAlignment: Text.AlignHCenter
opacity: 0
Component.onCompleted: {
opacity = 1;
}
Behavior on opacity {
NumberAnimation {
duration: 1000
easing.type: Easing.OutCubic
}
}
}
}
}

View file

@ -1,138 +0,0 @@
pragma ComponentBehavior: Bound
import ".."
import "../components"
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Widgets
import Caelestia.Config
import qs.components
import qs.components.containers
import qs.components.controls
import qs.components.effects
import qs.services
import qs.utils
Item {
id: root
required property Session session
// General Settings
property bool enabled: Config.dashboard.enabled ?? true
property bool showOnHover: Config.dashboard.showOnHover ?? true
property int mediaUpdateInterval: GlobalConfig.dashboard.mediaUpdateInterval ?? 1000
property int resourceUpdateInterval: GlobalConfig.dashboard.resourceUpdateInterval ?? 1000
property int dragThreshold: Config.dashboard.dragThreshold ?? 50
// Dashboard Tabs
property bool showDashboard: Config.dashboard.showDashboard ?? true
property bool showMedia: Config.dashboard.showMedia ?? true
property bool showPerformance: Config.dashboard.showPerformance ?? true
property bool showWeather: Config.dashboard.showWeather ?? true
// Performance Resources
property bool showBattery: Config.dashboard.performance.showBattery ?? false
property bool showGpu: Config.dashboard.performance.showGpu ?? true
property bool showCpu: Config.dashboard.performance.showCpu ?? true
property bool showMemory: Config.dashboard.performance.showMemory ?? true
property bool showStorage: Config.dashboard.performance.showStorage ?? true
property bool showNetwork: Config.dashboard.performance.showNetwork ?? true
function saveConfig() {
GlobalConfig.dashboard.enabled = root.enabled;
GlobalConfig.dashboard.showOnHover = root.showOnHover;
GlobalConfig.dashboard.mediaUpdateInterval = root.mediaUpdateInterval;
GlobalConfig.dashboard.resourceUpdateInterval = root.resourceUpdateInterval;
GlobalConfig.dashboard.dragThreshold = root.dragThreshold;
GlobalConfig.dashboard.showDashboard = root.showDashboard;
GlobalConfig.dashboard.showMedia = root.showMedia;
GlobalConfig.dashboard.showPerformance = root.showPerformance;
GlobalConfig.dashboard.showWeather = root.showWeather;
GlobalConfig.dashboard.performance.showBattery = root.showBattery;
GlobalConfig.dashboard.performance.showGpu = root.showGpu;
GlobalConfig.dashboard.performance.showCpu = root.showCpu;
GlobalConfig.dashboard.performance.showMemory = root.showMemory;
GlobalConfig.dashboard.performance.showStorage = root.showStorage;
GlobalConfig.dashboard.performance.showNetwork = root.showNetwork;
// Note: sizes properties are readonly and cannot be modified
}
anchors.fill: parent
ClippingRectangle {
id: dashboardClippingRect
anchors.fill: parent
anchors.margins: Tokens.padding.medium
anchors.leftMargin: 0
anchors.rightMargin: Tokens.padding.medium
radius: dashboardBorder.innerRadius
color: "transparent"
Loader {
id: dashboardLoader
anchors.fill: parent
anchors.margins: Tokens.padding.large + Tokens.padding.medium
anchors.leftMargin: Tokens.padding.large
anchors.rightMargin: Tokens.padding.large
asynchronous: true
sourceComponent: dashboardContentComponent
}
}
InnerBorder {
id: dashboardBorder
leftThickness: 0
rightThickness: Tokens.padding.medium
}
Component {
id: dashboardContentComponent
StyledFlickable {
id: dashboardFlickable
flickableDirection: Flickable.VerticalFlick
contentHeight: dashboardLayout.height
StyledScrollBar.vertical: StyledScrollBar {
flickable: dashboardFlickable
}
ColumnLayout {
id: dashboardLayout
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
spacing: Tokens.spacing.medium
RowLayout {
spacing: Tokens.spacing.medium
StyledText {
text: qsTr("Dashboard")
font: Tokens.font.title.builders.medium.weight(Font.Medium).build()
}
}
// General Settings Section
GeneralSection {
rootItem: root
}
// Performance Resources Section
PerformanceSection {
rootItem: root
}
}
}
}
}

View file

@ -1,128 +0,0 @@
import ".."
import "../components"
import QtQuick
import QtQuick.Layouts
import Caelestia.Config
import qs.components
import qs.components.controls
import qs.services
SectionContainer {
id: root
required property var rootItem
Layout.fillWidth: true
alignTop: true
StyledText {
text: qsTr("General Settings")
font: Tokens.font.title.small
}
SwitchRow {
label: qsTr("Enabled")
checked: root.rootItem.enabled
onToggled: checked => {
root.rootItem.enabled = checked;
root.rootItem.saveConfig();
}
}
SwitchRow {
label: qsTr("Show on hover")
checked: root.rootItem.showOnHover
onToggled: checked => {
root.rootItem.showOnHover = checked;
root.rootItem.saveConfig();
}
}
RowLayout {
Layout.fillWidth: true
spacing: Tokens.spacing.medium
SwitchRow {
Layout.fillWidth: true
label: qsTr("Show Dashboard tab")
checked: root.rootItem.showDashboard
onToggled: checked => {
root.rootItem.showDashboard = checked;
root.rootItem.saveConfig();
}
}
SwitchRow {
Layout.fillWidth: true
label: qsTr("Show Media tab")
checked: root.rootItem.showMedia
onToggled: checked => {
root.rootItem.showMedia = checked;
root.rootItem.saveConfig();
}
}
SwitchRow {
Layout.fillWidth: true
label: qsTr("Show Performance tab")
checked: root.rootItem.showPerformance
onToggled: checked => {
root.rootItem.showPerformance = checked;
root.rootItem.saveConfig();
}
}
SwitchRow {
Layout.fillWidth: true
label: qsTr("Show Weather tab")
checked: root.rootItem.showWeather
onToggled: checked => {
root.rootItem.showWeather = checked;
root.rootItem.saveConfig();
}
}
}
SliderInput {
Layout.fillWidth: true
label: qsTr("Media update interval")
value: root.rootItem.mediaUpdateInterval
from: 100
to: 10000
stepSize: 100
suffix: "ms"
validator: IntValidator {
bottom: 100
top: 10000
}
formatValueFunction: val => Math.round(val).toString()
parseValueFunction: text => parseInt(text)
onValueModified: newValue => {
root.rootItem.mediaUpdateInterval = Math.round(newValue);
root.rootItem.saveConfig();
}
}
SliderInput {
Layout.fillWidth: true
label: qsTr("Drag threshold")
value: root.rootItem.dragThreshold
from: 0
to: 100
suffix: "px"
validator: IntValidator {
bottom: 0
top: 100
}
formatValueFunction: val => Math.round(val).toString()
parseValueFunction: text => parseInt(text)
onValueModified: newValue => {
root.rootItem.dragThreshold = Math.round(newValue);
root.rootItem.saveConfig();
}
}
}

View file

@ -1,107 +0,0 @@
import ".."
import "../components"
import QtQuick
import QtQuick.Layouts
import Quickshell.Services.UPower
import Caelestia.Config
import Caelestia.Services
import qs.components
import qs.components.controls
import qs.services
SectionContainer {
id: root
required property var rootItem
// GPU toggle is hidden when type is Gpu.None (no GPU data available)
readonly property bool gpuAvailable: Gpu.type !== Gpu.None
// Battery toggle is hidden when no laptop battery is present
readonly property bool batteryAvailable: UPower.displayDevice.isLaptopBattery
Layout.fillWidth: true
alignTop: true
StyledText {
text: qsTr("Performance Resources")
font: Tokens.font.title.small
}
ConnectedButtonGroup {
rootItem: root.rootItem
options: {
let opts = [];
if (root.batteryAvailable)
opts.push({
"label": qsTr("Battery"),
"propertyName": "showBattery",
"onToggled": function (checked) {
root.rootItem.showBattery = checked;
root.rootItem.saveConfig();
}
});
if (root.gpuAvailable)
opts.push({
"label": qsTr("GPU"),
"propertyName": "showGpu",
"onToggled": function (checked) {
root.rootItem.showGpu = checked;
root.rootItem.saveConfig();
}
});
opts.push({
"label": qsTr("CPU"),
"propertyName": "showCpu",
"onToggled": function (checked) {
root.rootItem.showCpu = checked;
root.rootItem.saveConfig();
}
}, {
"label": qsTr("Memory"),
"propertyName": "showMemory",
"onToggled": function (checked) {
root.rootItem.showMemory = checked;
root.rootItem.saveConfig();
}
}, {
"label": qsTr("Storage"),
"propertyName": "showStorage",
"onToggled": function (checked) {
root.rootItem.showStorage = checked;
root.rootItem.saveConfig();
}
}, {
"label": qsTr("Network"),
"propertyName": "showNetwork",
"onToggled": function (checked) {
root.rootItem.showNetwork = checked;
root.rootItem.saveConfig();
}
});
return opts;
}
}
SliderInput {
Layout.fillWidth: true
label: qsTr("Resource update interval")
value: root.rootItem.resourceUpdateInterval
from: 100
to: 10000
stepSize: 100
suffix: "ms"
validator: IntValidator {
bottom: 100
top: 10000
}
formatValueFunction: val => Math.round(val).toString()
parseValueFunction: text => parseInt(text)
onValueModified: newValue => {
root.rootItem.resourceUpdateInterval = Math.round(newValue);
root.rootItem.saveConfig();
}
}
}

View file

@ -1,665 +0,0 @@
pragma ComponentBehavior: Bound
import ".."
import "../components"
import "../../launcher/services"
import "../../../utils/scripts/fuzzysort.js" as Fuzzy
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Widgets
import Caelestia
import Caelestia.Config
import qs.components
import qs.components.containers
import qs.components.controls
import qs.components.effects
import qs.services
import qs.utils
Item {
id: root
required property Session session
property var selectedApp: root.session.launcher.active
property bool hideFromLauncherChecked: false
property bool favouriteChecked: false
property string searchText: ""
property list<var> filteredApps: []
function updateToggleState() {
if (!root.selectedApp) {
root.hideFromLauncherChecked = false;
root.favouriteChecked = false;
return;
}
const appId = root.selectedApp.id || root.selectedApp.entry?.id;
root.hideFromLauncherChecked = GlobalConfig.launcher.hiddenApps && GlobalConfig.launcher.hiddenApps.length > 0 && Strings.testRegexList(GlobalConfig.launcher.hiddenApps, appId);
root.favouriteChecked = GlobalConfig.launcher.favouriteApps && GlobalConfig.launcher.favouriteApps.length > 0 && Strings.testRegexList(GlobalConfig.launcher.favouriteApps, appId);
}
function saveHiddenApps(isHidden) {
if (!root.selectedApp) {
return;
}
const appId = root.selectedApp.id || root.selectedApp.entry?.id;
const hiddenApps = GlobalConfig.launcher.hiddenApps ? [...GlobalConfig.launcher.hiddenApps] : [];
if (isHidden) {
if (!hiddenApps.includes(appId)) {
hiddenApps.push(appId);
}
} else {
const index = hiddenApps.indexOf(appId);
if (index !== -1) {
hiddenApps.splice(index, 1);
}
}
GlobalConfig.launcher.hiddenApps = hiddenApps;
}
function filterApps(search: string): list<var> {
if (!search || search.trim() === "") {
const apps = [];
for (let i = 0; i < allAppsDb.apps.length; i++) {
apps.push(allAppsDb.apps[i]);
}
return apps;
}
if (!allAppsDb.apps || allAppsDb.apps.length === 0) {
return [];
}
const preparedApps = [];
for (let i = 0; i < allAppsDb.apps.length; i++) {
const app = allAppsDb.apps[i];
const name = app.name || app.entry?.name || "";
preparedApps.push({
_item: app,
name: Fuzzy.prepare(name)
});
}
const results = Fuzzy.go(search, preparedApps, {
all: true,
keys: ["name"],
scoreFn: r => r[0].score
});
return results.sort((a, b) => b._score - a._score).map(r => r.obj._item);
}
function updateFilteredApps() {
filteredApps = filterApps(searchText);
}
anchors.fill: parent
onSelectedAppChanged: {
root.session.launcher.active = root.selectedApp;
updateToggleState();
}
onSearchTextChanged: {
updateFilteredApps();
}
Component.onCompleted: {
updateFilteredApps();
}
Connections {
function onActiveChanged() {
root.selectedApp = root.session.launcher.active;
updateToggleState();
}
target: root.session.launcher
}
AppDb {
id: allAppsDb
path: `${Paths.state}/apps.sqlite`
favouriteApps: GlobalConfig.launcher.favouriteApps
entries: DesktopEntries.applications.values
}
Connections {
function onAppsChanged() {
updateFilteredApps();
}
target: allAppsDb
}
SplitPaneLayout {
anchors.fill: parent
leftContent: Component {
ColumnLayout {
id: leftLauncherLayout
anchors.fill: parent
spacing: Tokens.spacing.small
RowLayout {
spacing: Tokens.spacing.medium
StyledText {
text: qsTr("Launcher")
font: Tokens.font.title.builders.medium.weight(Font.Medium).build()
}
Item {
Layout.fillWidth: true
}
ToggleButton {
toggled: !root.session.launcher.active
icon: "settings"
accent: "Primary"
iconSize: Tokens.font.body.medium.pointSize
horizontalPadding: Tokens.padding.medium
verticalPadding: Tokens.padding.small
tooltip: qsTr("Launcher settings")
onClicked: {
if (root.session.launcher.active) {
root.session.launcher.active = null;
} else {
if (root.filteredApps.length > 0) {
root.session.launcher.active = root.filteredApps[0];
}
}
}
}
}
StyledText {
Layout.topMargin: Tokens.spacing.largeIncreased
text: qsTr("Applications (%1)").arg(root.searchText ? root.filteredApps.length : allAppsDb.apps.length)
font: Tokens.font.title.builders.small.weight(Font.Medium).build()
}
StyledText {
text: qsTr("All applications available in the launcher")
color: Colours.palette.m3outline
}
StyledRect {
Layout.fillWidth: true
Layout.topMargin: Tokens.spacing.medium
Layout.bottomMargin: Tokens.spacing.small
color: Colours.layer(Colours.palette.m3surfaceContainer, 2)
radius: Tokens.rounding.full
implicitHeight: Math.max(searchIcon.implicitHeight, searchField.implicitHeight, clearIcon.implicitHeight)
MaterialIcon {
id: searchIcon
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: Tokens.padding.medium
text: "search"
color: Colours.palette.m3onSurfaceVariant
}
StyledTextField {
id: searchField
anchors.left: searchIcon.right
anchors.right: clearIcon.left
anchors.leftMargin: Tokens.spacing.small
anchors.rightMargin: Tokens.spacing.small
topPadding: Tokens.padding.medium
bottomPadding: Tokens.padding.medium
placeholderText: qsTr("Search applications...")
onTextChanged: {
root.searchText = text;
}
}
MaterialIcon {
id: clearIcon
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
anchors.rightMargin: Tokens.padding.medium
width: searchField.text ? implicitWidth : implicitWidth / 2
opacity: {
if (!searchField.text)
return 0;
if (clearMouse.pressed)
return 0.7;
if (clearMouse.containsMouse)
return 0.8;
return 1;
}
text: "close"
color: Colours.palette.m3onSurfaceVariant
MouseArea {
id: clearMouse
anchors.fill: parent
hoverEnabled: true
cursorShape: searchField.text ? Qt.PointingHandCursor : undefined
onClicked: searchField.text = ""
}
Behavior on width {
Anim {
type: Anim.StandardSmall
}
}
Behavior on opacity {
Anim {
type: Anim.StandardSmall
}
}
}
}
Loader {
id: appsListLoader
Layout.fillWidth: true
Layout.fillHeight: true
asynchronous: true
active: true
sourceComponent: StyledListView {
id: appsListView
Layout.fillWidth: true
Layout.fillHeight: true
model: root.filteredApps
spacing: Tokens.spacing.extraSmall
clip: true
StyledScrollBar.vertical: StyledScrollBar {
flickable: parent
}
delegate: StyledRect {
required property var modelData
readonly property bool isSelected: root.selectedApp === modelData
width: parent ? parent.width : 0
implicitHeight: 40
color: isSelected ? Colours.layer(Colours.palette.m3surfaceContainer, 2) : "transparent"
radius: Tokens.rounding.large
opacity: 0
Component.onCompleted: {
opacity = 1;
}
Behavior on opacity {
NumberAnimation {
duration: 1000
easing.type: Easing.OutCubic
}
}
StateLayer {
onClicked: {
root.session.launcher.active = modelData;
}
}
RowLayout {
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Tokens.padding.medium
spacing: Tokens.spacing.medium
IconImage {
asynchronous: true
Layout.alignment: Qt.AlignVCenter
implicitSize: 32
source: {
const entry = modelData.entry;
return entry ? Quickshell.iconPath(entry.icon, "image-missing") : "image-missing";
}
}
StyledText {
Layout.fillWidth: true
text: modelData.name || modelData.entry?.name || qsTr("Unknown")
font: Tokens.font.body.medium
}
Loader {
readonly property bool isHidden: modelData ? Strings.testRegexList(GlobalConfig.launcher.hiddenApps, modelData.id) : false
readonly property bool isFav: modelData ? Strings.testRegexList(GlobalConfig.launcher.favouriteApps, modelData.id) : false
Layout.alignment: Qt.AlignVCenter
asynchronous: true
active: isHidden || isFav
sourceComponent: isHidden ? hiddenIcon : (isFav ? favouriteIcon : null)
}
Component {
id: hiddenIcon
MaterialIcon {
text: "visibility_off"
fill: 1
color: Colours.palette.m3primary
}
}
Component {
id: favouriteIcon
MaterialIcon {
text: "favorite"
fill: 1
color: Colours.palette.m3primary
}
}
}
}
}
}
}
}
rightContent: Component {
Item {
id: rightLauncherPane
property var pane: root.session.launcher.active
property string paneId: pane ? (pane.id || pane.entry?.id || "") : ""
property Component targetComponent: settings
property Component nextComponent: settings
property var displayedApp: null
function getComponentForPane() {
return pane ? appDetails : settings;
}
Component.onCompleted: {
displayedApp = pane;
targetComponent = getComponentForPane();
nextComponent = targetComponent;
}
onPaneChanged: {
nextComponent = getComponentForPane();
paneId = pane ? (pane.id || pane.entry?.id || "") : "";
}
onDisplayedAppChanged: {
if (displayedApp) {
const appId = displayedApp.id || displayedApp.entry?.id;
root.hideFromLauncherChecked = GlobalConfig.launcher.hiddenApps && GlobalConfig.launcher.hiddenApps.length > 0 && Strings.testRegexList(GlobalConfig.launcher.hiddenApps, appId);
root.favouriteChecked = GlobalConfig.launcher.favouriteApps && GlobalConfig.launcher.favouriteApps.length > 0 && Strings.testRegexList(GlobalConfig.launcher.favouriteApps, appId);
} else {
root.hideFromLauncherChecked = false;
root.favouriteChecked = false;
}
}
Loader {
id: rightLauncherLoader
property var displayedApp: rightLauncherPane.displayedApp
anchors.fill: parent
asynchronous: true
opacity: 1
scale: 1
transformOrigin: Item.Center
clip: false
sourceComponent: rightLauncherPane.targetComponent
active: true
onItemChanged: {
if (item && rightLauncherPane.pane && rightLauncherPane.displayedApp !== rightLauncherPane.pane) {
rightLauncherPane.displayedApp = rightLauncherPane.pane;
}
}
}
Behavior on paneId {
PaneTransition {
target: rightLauncherLoader
propertyActions: [
PropertyAction {
target: rightLauncherPane
property: "displayedApp"
value: rightLauncherPane.pane
},
PropertyAction {
target: rightLauncherLoader
property: "active"
value: false
},
PropertyAction {
target: rightLauncherPane
property: "targetComponent"
value: rightLauncherPane.nextComponent
},
PropertyAction {
target: rightLauncherLoader
property: "active"
value: true
}
]
}
}
}
}
}
Component {
id: settings
StyledFlickable {
id: settingsFlickable
flickableDirection: Flickable.VerticalFlick
contentHeight: settingsInner.height
StyledScrollBar.vertical: StyledScrollBar {
flickable: settingsFlickable
}
Settings {
id: settingsInner
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
session: root.session
}
}
}
Component {
id: appDetails
ColumnLayout {
id: appDetailsLayout
readonly property var displayedApp: parent && parent.displayedApp !== undefined ? parent.displayedApp : null
anchors.fill: parent
spacing: Tokens.spacing.medium
SettingsHeader {
Layout.leftMargin: Tokens.padding.extraLargeIncreased
Layout.rightMargin: Tokens.padding.extraLargeIncreased
Layout.topMargin: Tokens.padding.extraLargeIncreased
visible: displayedApp === null
icon: "apps"
title: qsTr("Launcher Applications")
}
Item {
Layout.alignment: Qt.AlignHCenter
Layout.leftMargin: Tokens.padding.extraLargeIncreased
Layout.rightMargin: Tokens.padding.extraLargeIncreased
Layout.topMargin: Tokens.padding.extraLargeIncreased
visible: displayedApp !== null
implicitWidth: Math.max(appIconImage.implicitWidth, appTitleText.implicitWidth)
implicitHeight: appIconImage.implicitHeight + Tokens.spacing.medium + appTitleText.implicitHeight
ColumnLayout {
anchors.centerIn: parent
spacing: Tokens.spacing.medium
IconImage {
id: appIconImage
asynchronous: true
Layout.alignment: Qt.AlignHCenter
implicitSize: Tokens.font.body.large.pointSize * 3 * 2
source: {
const app = appDetailsLayout.displayedApp;
if (!app)
return "image-missing";
const entry = app.entry;
if (entry && entry.icon) {
return Quickshell.iconPath(entry.icon, "image-missing");
}
return "image-missing";
}
}
StyledText {
id: appTitleText
Layout.alignment: Qt.AlignHCenter
text: displayedApp ? (displayedApp.name || displayedApp.entry?.name || qsTr("Application Details")) : ""
font: Tokens.font.title.builders.medium.weight(Font.Bold).build()
}
}
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.topMargin: Tokens.spacing.largeIncreased
Layout.leftMargin: Tokens.padding.extraLargeIncreased
Layout.rightMargin: Tokens.padding.extraLargeIncreased
StyledFlickable {
id: detailsFlickable
anchors.fill: parent
flickableDirection: Flickable.VerticalFlick
contentHeight: debugLayout.height
StyledScrollBar.vertical: StyledScrollBar {
flickable: parent
}
ColumnLayout {
id: debugLayout
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
spacing: Tokens.spacing.medium
SwitchRow {
Layout.topMargin: Tokens.spacing.medium
visible: appDetailsLayout.displayedApp !== null
label: qsTr("Mark as favourite")
checked: root.favouriteChecked
// disabled if:
// * app is hidden
// * app isn't in favouriteApps array but marked as favourite anyway
// ^^^ This means that this app is favourited because of a regex check
// this button can not toggle regexed apps
enabled: appDetailsLayout.displayedApp !== null && !root.hideFromLauncherChecked && (GlobalConfig.launcher.favouriteApps.indexOf(appDetailsLayout.displayedApp.id || appDetailsLayout.displayedApp.entry?.id) !== -1 || !root.favouriteChecked)
opacity: enabled ? 1 : 0.6
onToggled: checked => {
root.favouriteChecked = checked;
const app = appDetailsLayout.displayedApp;
if (app) {
const appId = app.id || app.entry?.id;
const favouriteApps = GlobalConfig.launcher.favouriteApps ? [...GlobalConfig.launcher.favouriteApps] : [];
if (checked) {
if (!favouriteApps.includes(appId)) {
favouriteApps.push(appId);
}
} else {
const index = favouriteApps.indexOf(appId);
if (index !== -1) {
favouriteApps.splice(index, 1);
}
}
GlobalConfig.launcher.favouriteApps = favouriteApps;
}
}
}
SwitchRow {
Layout.topMargin: Tokens.spacing.medium
visible: appDetailsLayout.displayedApp !== null
label: qsTr("Hide from launcher")
checked: root.hideFromLauncherChecked
// disabled if:
// * app is favourited
// * app isn't in hiddenApps array but marked as hidden anyway
// ^^^ This means that this app is hidden because of a regex check
// this button can not toggle regexed apps
enabled: appDetailsLayout.displayedApp !== null && !root.favouriteChecked && (GlobalConfig.launcher.hiddenApps.indexOf(appDetailsLayout.displayedApp.id || appDetailsLayout.displayedApp.entry?.id) !== -1 || !root.hideFromLauncherChecked)
opacity: enabled ? 1 : 0.6
onToggled: checked => {
root.hideFromLauncherChecked = checked;
const app = appDetailsLayout.displayedApp;
if (app) {
const appId = app.id || app.entry?.id;
const hiddenApps = GlobalConfig.launcher.hiddenApps ? [...GlobalConfig.launcher.hiddenApps] : [];
if (checked) {
if (!hiddenApps.includes(appId)) {
hiddenApps.push(appId);
}
} else {
const index = hiddenApps.indexOf(appId);
if (index !== -1) {
hiddenApps.splice(index, 1);
}
}
GlobalConfig.launcher.hiddenApps = hiddenApps;
}
}
}
}
}
}
}
}
}

View file

@ -1,208 +0,0 @@
pragma ComponentBehavior: Bound
import ".."
import "../components"
import QtQuick
import QtQuick.Layouts
import Caelestia.Config
import qs.components
import qs.components.controls
import qs.components.effects
import qs.services
ColumnLayout {
id: root
required property Session session
spacing: Tokens.spacing.medium
SettingsHeader {
icon: "apps"
title: qsTr("Launcher Settings")
}
SectionHeader {
Layout.topMargin: Tokens.spacing.largeIncreased
title: qsTr("General")
description: qsTr("General launcher settings")
}
SectionContainer {
ToggleRow {
label: qsTr("Enabled")
checked: Config.launcher.enabled
toggle.onToggled: {
GlobalConfig.launcher.enabled = checked;
}
}
ToggleRow {
label: qsTr("Show on hover")
checked: Config.launcher.showOnHover
toggle.onToggled: {
GlobalConfig.launcher.showOnHover = checked;
}
}
ToggleRow {
label: qsTr("Vim keybinds")
checked: GlobalConfig.launcher.vimKeybinds
toggle.onToggled: {
GlobalConfig.launcher.vimKeybinds = checked;
}
}
ToggleRow {
label: qsTr("Enable dangerous actions")
checked: GlobalConfig.launcher.enableDangerousActions
toggle.onToggled: {
GlobalConfig.launcher.enableDangerousActions = checked;
}
}
}
SectionHeader {
Layout.topMargin: Tokens.spacing.largeIncreased
title: qsTr("Display")
description: qsTr("Display and appearance settings")
}
SectionContainer {
contentSpacing: Tokens.spacing.extraSmall
PropertyRow {
label: qsTr("Max shown items")
value: qsTr("%1").arg(Config.launcher.maxShown)
}
PropertyRow {
showTopMargin: true
label: qsTr("Max wallpapers")
value: qsTr("%1").arg(Config.launcher.maxWallpapers)
}
PropertyRow {
showTopMargin: true
label: qsTr("Drag threshold")
value: qsTr("%1 px").arg(Config.launcher.dragThreshold)
}
}
SectionHeader {
Layout.topMargin: Tokens.spacing.largeIncreased
title: qsTr("Prefixes")
description: qsTr("Command prefix settings")
}
SectionContainer {
contentSpacing: Tokens.spacing.extraSmall
PropertyRow {
label: qsTr("Special prefix")
value: GlobalConfig.launcher.specialPrefix || qsTr("None")
}
PropertyRow {
showTopMargin: true
label: qsTr("Action prefix")
value: GlobalConfig.launcher.actionPrefix || qsTr("None")
}
}
SectionHeader {
Layout.topMargin: Tokens.spacing.largeIncreased
title: qsTr("Fuzzy search")
description: qsTr("Fuzzy search settings")
}
SectionContainer {
ToggleRow {
label: qsTr("Apps")
checked: GlobalConfig.launcher.useFuzzy.apps
toggle.onToggled: {
GlobalConfig.launcher.useFuzzy.apps = checked;
}
}
ToggleRow {
label: qsTr("Actions")
checked: GlobalConfig.launcher.useFuzzy.actions
toggle.onToggled: {
GlobalConfig.launcher.useFuzzy.actions = checked;
}
}
ToggleRow {
label: qsTr("Schemes")
checked: GlobalConfig.launcher.useFuzzy.schemes
toggle.onToggled: {
GlobalConfig.launcher.useFuzzy.schemes = checked;
}
}
ToggleRow {
label: qsTr("Variants")
checked: GlobalConfig.launcher.useFuzzy.variants
toggle.onToggled: {
GlobalConfig.launcher.useFuzzy.variants = checked;
}
}
ToggleRow {
label: qsTr("Wallpapers")
checked: GlobalConfig.launcher.useFuzzy.wallpapers
toggle.onToggled: {
GlobalConfig.launcher.useFuzzy.wallpapers = checked;
}
}
}
SectionHeader {
Layout.topMargin: Tokens.spacing.largeIncreased
title: qsTr("Sizes")
description: qsTr("Size settings for launcher items")
}
SectionContainer {
contentSpacing: Tokens.spacing.extraSmall
PropertyRow {
label: qsTr("Item width")
value: qsTr("%1 px").arg(Tokens.sizes.launcher.itemWidth)
}
PropertyRow {
showTopMargin: true
label: qsTr("Item height")
value: qsTr("%1 px").arg(Tokens.sizes.launcher.itemHeight)
}
PropertyRow {
showTopMargin: true
label: qsTr("Wallpaper width")
value: qsTr("%1 px").arg(Tokens.sizes.launcher.wallpaperWidth)
}
PropertyRow {
showTopMargin: true
label: qsTr("Wallpaper height")
value: qsTr("%1 px").arg(Tokens.sizes.launcher.wallpaperHeight)
}
}
SectionHeader {
Layout.topMargin: Tokens.spacing.largeIncreased
title: qsTr("Hidden apps")
description: qsTr("Applications hidden from launcher")
}
SectionContainer {
contentSpacing: Tokens.spacing.extraSmall
PropertyRow {
label: qsTr("Total hidden")
value: qsTr("%1").arg(GlobalConfig.launcher.hiddenApps ? GlobalConfig.launcher.hiddenApps.length : 0)
}
}
}

View file

@ -1,118 +0,0 @@
pragma ComponentBehavior: Bound
import ".."
import "../components"
import QtQuick
import QtQuick.Layouts
import Caelestia.Config
import qs.components
import qs.components.containers
import qs.components.controls
import qs.components.effects
import qs.services
DeviceDetails {
id: root
required property Session session
readonly property var ethernetDevice: root.session.ethernet.active
device: ethernetDevice
Component.onCompleted: {
if (ethernetDevice && ethernetDevice.interface) {
Nmcli.getEthernetDeviceDetails(ethernetDevice.interface, () => {});
}
}
onEthernetDeviceChanged: {
if (ethernetDevice && ethernetDevice.interface) {
Nmcli.getEthernetDeviceDetails(ethernetDevice.interface, () => {});
} else {
Nmcli.ethernetDeviceDetails = null;
}
}
headerComponent: Component {
ConnectionHeader {
icon: "cable"
title: root.ethernetDevice?.interface ?? qsTr("Unknown")
}
}
sections: [
Component {
ColumnLayout {
spacing: Tokens.spacing.medium
SectionHeader {
title: qsTr("Connection status")
description: qsTr("Connection settings for this device")
}
SectionContainer {
ToggleRow {
label: qsTr("Connected")
checked: root.ethernetDevice?.connected ?? false
toggle.onToggled: {
if (checked) {
Nmcli.connectEthernet(root.ethernetDevice?.connection || "", root.ethernetDevice?.interface || "", () => {});
} else {
if (root.ethernetDevice?.connection) {
Nmcli.disconnectEthernet(root.ethernetDevice.connection, () => {});
}
}
}
}
}
}
},
Component {
ColumnLayout {
spacing: Tokens.spacing.medium
SectionHeader {
title: qsTr("Device properties")
description: qsTr("Additional information")
}
SectionContainer {
contentSpacing: Tokens.spacing.extraSmall
PropertyRow {
label: qsTr("Interface")
value: root.ethernetDevice?.interface ?? qsTr("Unknown")
}
PropertyRow {
showTopMargin: true
label: qsTr("Connection")
value: root.ethernetDevice?.connection || qsTr("Not connected")
}
PropertyRow {
showTopMargin: true
label: qsTr("State")
value: root.ethernetDevice?.state ?? qsTr("Unknown")
}
}
}
},
Component {
ColumnLayout {
spacing: Tokens.spacing.medium
SectionHeader {
title: qsTr("Connection information")
description: qsTr("Network connection details")
}
SectionContainer {
ConnectionInfoSection {
deviceDetails: Nmcli.ethernetDeviceDetails
}
}
}
}
]
}

View file

@ -1,177 +0,0 @@
pragma ComponentBehavior: Bound
import ".."
import "../components"
import QtQuick
import QtQuick.Layouts
import Caelestia.Config
import qs.components
import qs.components.containers
import qs.components.controls
import qs.services
DeviceList {
id: root
required property Session session
title: qsTr("Devices (%1)").arg(Nmcli.ethernetDevices.length)
description: qsTr("All available ethernet devices")
activeItem: session.ethernet.active
model: Nmcli.ethernetDevices
headerComponent: Component {
RowLayout {
spacing: Tokens.spacing.medium
StyledText {
text: qsTr("Settings")
font: Tokens.font.title.builders.medium.weight(Font.Medium).build()
}
Item {
Layout.fillWidth: true
}
ToggleButton {
toggled: !root.session.ethernet.active
icon: "settings"
accent: "Primary"
iconSize: Tokens.font.body.medium.pointSize
horizontalPadding: Tokens.padding.medium
verticalPadding: Tokens.padding.small
onClicked: {
if (root.session.ethernet.active)
root.session.ethernet.active = null;
else {
root.session.ethernet.active = root.view.model.get(0)?.modelData ?? null;
}
}
}
}
}
delegate: Component {
StyledRect {
id: ethernetItem
required property var modelData
readonly property bool isActive: root.activeItem && modelData && root.activeItem.interface === modelData.interface
width: ListView.view ? ListView.view.width : undefined
implicitHeight: rowLayout.implicitHeight + Tokens.padding.medium * 2
color: Qt.alpha(Colours.tPalette.m3surfaceContainer, ethernetItem.isActive ? Colours.tPalette.m3surfaceContainer.a : 0)
radius: Tokens.rounding.large
StateLayer {
id: stateLayer
onClicked: {
root.session.ethernet.active = modelData;
}
}
RowLayout {
id: rowLayout
anchors.fill: parent
anchors.margins: Tokens.padding.medium
spacing: Tokens.spacing.medium
StyledRect {
implicitWidth: implicitHeight
implicitHeight: icon.implicitHeight + Tokens.padding.medium * 2
radius: Tokens.rounding.large
color: modelData.connected ? Colours.palette.m3primaryContainer : Colours.tPalette.m3surfaceContainerHigh
StyledRect {
anchors.fill: parent
radius: parent.radius
color: Qt.alpha(modelData.connected ? Colours.palette.m3onPrimaryContainer : Colours.palette.m3onSurface, stateLayer.pressed ? 0.1 : stateLayer.containsMouse ? 0.08 : 0)
}
MaterialIcon {
id: icon
anchors.centerIn: parent
text: "cable"
fontStyle: Tokens.font.icon.large
fill: modelData.connected ? 1 : 0
color: modelData.connected ? Colours.palette.m3onPrimaryContainer : Colours.palette.m3onSurface
Behavior on fill {
Anim {
type: Anim.DefaultEffects
}
}
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: 0
StyledText {
Layout.fillWidth: true
text: modelData.interface || qsTr("Unknown")
elide: Text.ElideRight
}
RowLayout {
Layout.fillWidth: true
spacing: Tokens.spacing.medium
StyledText {
Layout.fillWidth: true
text: modelData.connected ? qsTr("Connected") : qsTr("Disconnected")
color: modelData.connected ? Colours.palette.m3primary : Colours.palette.m3outline
font: Tokens.font.body.builders.small.weight(modelData.connected ? Font.Medium : Font.Normal).build()
elide: Text.ElideRight
}
}
}
StyledRect {
id: connectBtn
implicitWidth: implicitHeight
implicitHeight: connectIcon.implicitHeight + Tokens.padding.large
radius: Tokens.rounding.full
color: Qt.alpha(Colours.palette.m3primaryContainer, modelData.connected ? 1 : 0)
StateLayer {
onClicked: {
if (modelData.connected && modelData.connection) {
Nmcli.disconnectEthernet(modelData.connection, () => {});
} else {
Nmcli.connectEthernet(modelData.connection || "", modelData.interface || "", () => {});
}
}
color: modelData.connected ? Colours.palette.m3onPrimaryContainer : Colours.palette.m3onSurface
}
MaterialIcon {
id: connectIcon
anchors.centerIn: parent
animate: true
text: modelData.connected ? "link_off" : "link"
color: modelData.connected ? Colours.palette.m3onPrimaryContainer : Colours.palette.m3onSurface
}
}
}
}
}
onItemSelected: function (item) {
session.ethernet.active = item;
}
}

View file

@ -1,50 +0,0 @@
pragma ComponentBehavior: Bound
import ".."
import "../components"
import QtQuick
import Quickshell.Widgets
import Caelestia.Config
import qs.components
import qs.components.containers
SplitPaneWithDetails {
id: root
required property Session session
anchors.fill: parent
activeItem: session.ethernet.active
paneIdGenerator: function (item) {
return item ? (item.interface || "") : "";
}
leftContent: Component {
EthernetList {
session: root.session
}
}
rightDetailsComponent: Component {
EthernetDetails {
session: root.session
}
}
rightSettingsComponent: Component {
StyledFlickable {
flickableDirection: Flickable.VerticalFlick
contentHeight: settingsInner.height
clip: true
EthernetSettings {
id: settingsInner
anchors.left: parent.left
anchors.right: parent.right
session: root.session
}
}
}
}

View file

@ -1,75 +0,0 @@
pragma ComponentBehavior: Bound
import ".."
import "../components"
import QtQuick
import QtQuick.Layouts
import Caelestia.Config
import qs.components
import qs.components.controls
import qs.components.effects
import qs.services
ColumnLayout {
id: root
required property Session session
spacing: Tokens.spacing.medium
SettingsHeader {
icon: "cable"
title: qsTr("Ethernet settings")
}
StyledText {
Layout.topMargin: Tokens.spacing.largeIncreased
text: qsTr("Ethernet devices")
font: Tokens.font.body.builders.large.weight(Font.Medium).build()
}
StyledText {
text: qsTr("Available ethernet devices")
color: Colours.palette.m3outline
}
StyledRect {
Layout.fillWidth: true
implicitHeight: ethernetInfo.implicitHeight + Tokens.padding.extraLargeIncreased
radius: Tokens.rounding.large
color: Colours.tPalette.m3surfaceContainer
ColumnLayout {
id: ethernetInfo
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Tokens.padding.large
spacing: Tokens.spacing.extraSmall
StyledText {
text: qsTr("Total devices")
}
StyledText {
text: qsTr("%1").arg(Nmcli.ethernetDevices.length)
color: Colours.palette.m3outline
font: Tokens.font.body.small
}
StyledText {
Layout.topMargin: Tokens.spacing.medium
text: qsTr("Connected devices")
}
StyledText {
text: qsTr("%1").arg(Nmcli.ethernetDevices.filter(d => d.connected).length)
color: Colours.palette.m3outline
font: Tokens.font.body.small
}
}
}
}

View file

@ -1,170 +0,0 @@
pragma ComponentBehavior: Bound
import ".."
import "../components"
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Caelestia.Config
import qs.components
import qs.components.containers
import qs.components.controls
import qs.components.effects
import qs.services
ColumnLayout {
id: root
required property Session session
spacing: Tokens.spacing.medium
SettingsHeader {
icon: "router"
title: qsTr("Network Settings")
}
SectionHeader {
Layout.topMargin: Tokens.spacing.largeIncreased
title: qsTr("Ethernet")
description: qsTr("Ethernet device information")
}
SectionContainer {
contentSpacing: Tokens.spacing.extraSmall
PropertyRow {
label: qsTr("Total devices")
value: qsTr("%1").arg(Nmcli.ethernetDevices.length)
}
PropertyRow {
showTopMargin: true
label: qsTr("Connected devices")
value: qsTr("%1").arg(Nmcli.ethernetDevices.filter(d => d.connected).length)
}
}
SectionHeader {
Layout.topMargin: Tokens.spacing.largeIncreased
title: qsTr("Wireless")
description: qsTr("WiFi network settings")
}
SectionContainer {
ToggleRow {
label: qsTr("WiFi enabled")
checked: Nmcli.wifiEnabled
toggle.onToggled: {
Nmcli.enableWifi(checked);
}
}
}
SectionHeader {
Layout.topMargin: Tokens.spacing.largeIncreased
title: qsTr("VPN")
description: qsTr("VPN provider settings")
visible: GlobalConfig.utilities.vpn.enabled || GlobalConfig.utilities.vpn.provider.length > 0
}
SectionContainer {
visible: GlobalConfig.utilities.vpn.enabled || GlobalConfig.utilities.vpn.provider.length > 0
ToggleRow {
label: qsTr("VPN enabled")
checked: GlobalConfig.utilities.vpn.enabled
toggle.onToggled: {
GlobalConfig.utilities.vpn.enabled = checked;
}
}
PropertyRow {
showTopMargin: true
label: qsTr("Providers")
value: qsTr("%1").arg(GlobalConfig.utilities.vpn.provider.length)
}
TextButton {
Layout.fillWidth: true
Layout.topMargin: Tokens.spacing.medium
Layout.minimumHeight: Tokens.font.body.medium.pointSize + Tokens.padding.medium * 2
text: qsTr("⚙ Manage VPN Providers")
inactiveColour: Colours.palette.m3secondaryContainer
inactiveOnColour: Colours.palette.m3onSecondaryContainer
onClicked: {
vpnSettingsDialog.open();
}
}
}
SectionHeader {
Layout.topMargin: Tokens.spacing.largeIncreased
title: qsTr("Current connection")
description: qsTr("Active network connection information")
}
SectionContainer {
contentSpacing: Tokens.spacing.extraSmall
PropertyRow {
label: qsTr("Network")
value: Nmcli.active ? Nmcli.active.ssid : (Nmcli.activeEthernet ? Nmcli.activeEthernet.interface : qsTr("Not connected"))
}
PropertyRow {
showTopMargin: true
visible: Nmcli.active !== null
label: qsTr("Signal strength")
value: Nmcli.active ? qsTr("%1%").arg(Nmcli.active.strength) : qsTr("N/A")
}
PropertyRow {
showTopMargin: true
visible: Nmcli.active !== null
label: qsTr("Security")
value: Nmcli.active ? (Nmcli.active.isSecure ? qsTr("Secured") : qsTr("Open")) : qsTr("N/A")
}
PropertyRow {
showTopMargin: true
visible: Nmcli.active !== null
label: qsTr("Frequency")
value: Nmcli.active ? qsTr("%1 MHz").arg(Nmcli.active.frequency) : qsTr("N/A")
}
}
Popup {
id: vpnSettingsDialog
parent: Overlay.overlay
anchors.centerIn: parent
width: Math.min(600, parent.width - Tokens.padding.extraLargeIncreased)
height: Math.min(700, parent.height - Tokens.padding.extraLargeIncreased)
modal: true
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
background: StyledRect {
color: Colours.palette.m3surface
radius: Tokens.rounding.extraLarge
}
StyledFlickable {
anchors.fill: parent
anchors.margins: Tokens.padding.large * 1.5
flickableDirection: Flickable.VerticalFlick
contentHeight: vpnSettingsContent.height
clip: true
VpnSettings {
id: vpnSettingsContent
anchors.left: parent.left
anchors.right: parent.right
session: root.session
}
}
}
}

View file

@ -1,379 +0,0 @@
pragma ComponentBehavior: Bound
import ".."
import "../components"
import "."
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Widgets
import Caelestia.Config
import qs.components
import qs.components.containers
import qs.components.controls
import qs.components.effects
import qs.services
import qs.utils
Item {
id: root
required property Session session
anchors.fill: parent
SplitPaneLayout {
id: splitLayout
anchors.fill: parent
leftContent: Component {
StyledFlickable {
id: leftFlickable
flickableDirection: Flickable.VerticalFlick
contentHeight: leftContent.height
StyledScrollBar.vertical: StyledScrollBar {
flickable: leftFlickable
}
ColumnLayout {
id: leftContent
anchors.left: parent.left
anchors.right: parent.right
spacing: Tokens.spacing.medium
RowLayout {
Layout.fillWidth: true
spacing: Tokens.spacing.medium
StyledText {
text: qsTr("Network")
font: Tokens.font.body.builders.large.weight(Font.Medium).build()
}
Item {
Layout.fillWidth: true
}
ToggleButton {
toggled: Nmcli.wifiEnabled
icon: "wifi"
accent: "Tertiary"
iconSize: Tokens.font.body.medium.pointSize
horizontalPadding: Tokens.padding.medium
verticalPadding: Tokens.padding.small
tooltip: qsTr("Toggle WiFi")
onClicked: {
Nmcli.toggleWifi(null);
}
}
ToggleButton {
toggled: Nmcli.scanning
icon: "wifi_find"
accent: "Secondary"
iconSize: Tokens.font.body.medium.pointSize
horizontalPadding: Tokens.padding.medium
verticalPadding: Tokens.padding.small
tooltip: qsTr("Scan for networks")
onClicked: {
Nmcli.rescanWifi();
}
}
ToggleButton {
toggled: !root.session.ethernet.active && !root.session.network.active
icon: "settings"
accent: "Primary"
iconSize: Tokens.font.body.medium.pointSize
horizontalPadding: Tokens.padding.medium
verticalPadding: Tokens.padding.small
tooltip: qsTr("Network settings")
onClicked: {
if (root.session.ethernet.active || root.session.network.active) {
root.session.ethernet.active = null;
root.session.network.active = null;
} else {
if (Nmcli.ethernetDevices.length > 0) {
root.session.ethernet.active = Nmcli.ethernetDevices[0];
} else if (Nmcli.networks.length > 0) {
root.session.network.active = Nmcli.networks[0];
}
}
}
}
}
CollapsibleSection {
id: vpnListSection
Layout.fillWidth: true
title: qsTr("VPN")
expanded: true
Loader {
Layout.fillWidth: true
asynchronous: true
sourceComponent: Component {
VpnList {
session: root.session
showHeader: false
}
}
}
}
CollapsibleSection {
id: ethernetListSection
Layout.fillWidth: true
title: qsTr("Ethernet")
expanded: true
Loader {
Layout.fillWidth: true
asynchronous: true
sourceComponent: Component {
EthernetList {
session: root.session
showHeader: false
}
}
}
}
CollapsibleSection {
id: wirelessListSection
Layout.fillWidth: true
title: qsTr("Wireless")
expanded: true
Loader {
Layout.fillWidth: true
asynchronous: true
sourceComponent: Component {
WirelessList {
session: root.session
showHeader: false
}
}
}
}
}
}
}
rightContent: Component {
Item {
id: rightPaneItem
property var vpnPane: root.session && root.session.vpn ? root.session.vpn.active : null
property var ethernetPane: root.session && root.session.ethernet ? root.session.ethernet.active : null
property var wirelessPane: root.session && root.session.network ? root.session.network.active : null
property var pane: vpnPane || ethernetPane || wirelessPane
property string paneId: vpnPane ? ("vpn:" + (vpnPane.name || "")) : (ethernetPane ? ("eth:" + (ethernetPane.interface || "")) : (wirelessPane ? ("wifi:" + (wirelessPane.ssid || wirelessPane.bssid || "")) : "settings"))
property Component targetComponent: settingsComponent
property Component nextComponent: settingsComponent
function getComponentForPane() {
if (vpnPane)
return vpnDetailsComponent;
if (ethernetPane)
return ethernetDetailsComponent;
if (wirelessPane)
return wirelessDetailsComponent;
return settingsComponent;
}
Component.onCompleted: {
targetComponent = getComponentForPane();
nextComponent = targetComponent;
}
Connections {
function onActiveChanged() {
// Clear others when VPN is selected
if (root.session && root.session.vpn && root.session.vpn.active) {
if (root.session.ethernet && root.session.ethernet.active)
root.session.ethernet.active = null;
if (root.session.network && root.session.network.active)
root.session.network.active = null;
}
rightPaneItem.nextComponent = rightPaneItem.getComponentForPane();
}
target: root.session && root.session.vpn ? root.session.vpn : null
enabled: target !== null
}
Connections {
function onActiveChanged() {
// Clear others when ethernet is selected
if (root.session && root.session.ethernet && root.session.ethernet.active) {
if (root.session.vpn && root.session.vpn.active)
root.session.vpn.active = null;
if (root.session.network && root.session.network.active)
root.session.network.active = null;
}
rightPaneItem.nextComponent = rightPaneItem.getComponentForPane();
}
target: root.session && root.session.ethernet ? root.session.ethernet : null
enabled: target !== null
}
Connections {
function onActiveChanged() {
// Clear others when wireless is selected
if (root.session && root.session.network && root.session.network.active) {
if (root.session.vpn && root.session.vpn.active)
root.session.vpn.active = null;
if (root.session.ethernet && root.session.ethernet.active)
root.session.ethernet.active = null;
}
rightPaneItem.nextComponent = rightPaneItem.getComponentForPane();
}
target: root.session && root.session.network ? root.session.network : null
enabled: target !== null
}
Loader {
id: rightLoader
anchors.fill: parent
opacity: 1
scale: 1
transformOrigin: Item.Center
clip: false
asynchronous: true
sourceComponent: rightPaneItem.targetComponent
}
Behavior on paneId {
PaneTransition {
target: rightLoader
propertyActions: [
PropertyAction {
target: rightPaneItem
property: "targetComponent"
value: rightPaneItem.nextComponent
}
]
}
}
}
}
}
Component {
id: settingsComponent
StyledFlickable {
id: settingsFlickable
flickableDirection: Flickable.VerticalFlick
contentHeight: settingsInner.height
StyledScrollBar.vertical: StyledScrollBar {
flickable: settingsFlickable
}
NetworkSettings {
id: settingsInner
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
session: root.session
}
}
}
Component {
id: ethernetDetailsComponent
StyledFlickable {
id: ethernetFlickable
flickableDirection: Flickable.VerticalFlick
contentHeight: ethernetDetailsInner.height
StyledScrollBar.vertical: StyledScrollBar {
flickable: ethernetFlickable
}
EthernetDetails {
id: ethernetDetailsInner
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
session: root.session
}
}
}
Component {
id: wirelessDetailsComponent
StyledFlickable {
id: wirelessFlickable
flickableDirection: Flickable.VerticalFlick
contentHeight: wirelessDetailsInner.height
StyledScrollBar.vertical: StyledScrollBar {
flickable: wirelessFlickable
}
WirelessDetails {
id: wirelessDetailsInner
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
session: root.session
}
}
}
Component {
id: vpnDetailsComponent
StyledFlickable {
id: vpnFlickable
flickableDirection: Flickable.VerticalFlick
contentHeight: vpnDetailsInner.height
StyledScrollBar.vertical: StyledScrollBar {
flickable: vpnFlickable
}
VpnDetails {
id: vpnDetailsInner
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
session: root.session
}
}
}
WirelessPasswordDialog {
anchors.fill: parent
session: root.session
z: 1000
}
}

View file

@ -1,542 +0,0 @@
pragma ComponentBehavior: Bound
import ".."
import "../components"
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Caelestia.Config
import qs.components
import qs.components.containers
import qs.components.controls
import qs.components.effects
import qs.services
import qs.utils
DeviceDetails {
id: root
required property Session session
readonly property var vpnProvider: root.session.vpn.active
readonly property bool providerEnabled: {
if (!vpnProvider || vpnProvider.index === undefined)
return false;
const provider = GlobalConfig.utilities.vpn.provider[vpnProvider.index];
return provider && typeof provider === "object" && provider.enabled === true;
}
device: vpnProvider
headerComponent: Component {
ConnectionHeader {
icon: "vpn_key"
title: root.vpnProvider?.displayName ?? qsTr("Unknown")
}
}
sections: [
Component {
ColumnLayout {
spacing: Tokens.spacing.medium
SectionHeader {
title: qsTr("Connection status")
description: qsTr("VPN connection settings")
}
SectionContainer {
ToggleRow {
label: qsTr("Enable this provider")
checked: root.providerEnabled
toggle.onToggled: {
if (!root.vpnProvider)
return;
const providers = [];
const index = root.vpnProvider.index;
// Copy providers and update enabled state
for (let i = 0; i < GlobalConfig.utilities.vpn.provider.length; i++) {
const p = GlobalConfig.utilities.vpn.provider[i];
if (typeof p === "object") {
const newProvider = {
name: p.name,
displayName: p.displayName,
interface: p.interface
};
if (checked) {
// Enable this one, disable others
newProvider.enabled = (i === index);
} else {
// Just disable this one
newProvider.enabled = (i === index) ? false : (p.enabled !== false);
}
if (p.connectCmd && p.connectCmd.length > 0) {
newProvider.connectCmd = p.connectCmd;
}
if (p.disconnectCmd && p.disconnectCmd.length > 0) {
newProvider.disconnectCmd = p.disconnectCmd;
}
providers.push(newProvider);
} else {
providers.push(p);
}
}
GlobalConfig.utilities.vpn.provider = providers;
}
}
RowLayout {
Layout.fillWidth: true
Layout.topMargin: Tokens.spacing.medium
spacing: Tokens.spacing.medium
TextButton {
Layout.fillWidth: true
Layout.minimumHeight: Tokens.font.body.medium.pointSize + Tokens.padding.medium * 2
visible: root.providerEnabled
enabled: !VPN.connecting
inactiveColour: Colours.palette.m3primaryContainer
inactiveOnColour: Colours.palette.m3onPrimaryContainer
text: VPN.connected ? qsTr("Disconnect") : qsTr("Connect")
onClicked: {
VPN.toggle();
}
}
TextButton {
Layout.fillWidth: true
text: qsTr("Edit Provider")
inactiveColour: Colours.palette.m3secondaryContainer
inactiveOnColour: Colours.palette.m3onSecondaryContainer
onClicked: {
const provider = GlobalConfig.utilities.vpn.provider[root.vpnProvider.index];
editVpnDialog.editIndex = root.vpnProvider.index;
editVpnDialog.providerName = root.vpnProvider.name;
editVpnDialog.displayName = root.vpnProvider.displayName;
editVpnDialog.interfaceName = root.vpnProvider.interface;
editVpnDialog.connectCmd = (provider && provider.connectCmd) ? provider.connectCmd.join(" ") : "";
editVpnDialog.disconnectCmd = (provider && provider.disconnectCmd) ? provider.disconnectCmd.join(" ") : "";
editVpnDialog.open();
}
}
TextButton {
Layout.fillWidth: true
text: qsTr("Delete Provider")
inactiveColour: Colours.palette.m3errorContainer
inactiveOnColour: Colours.palette.m3onErrorContainer
onClicked: {
const providers = [];
for (let i = 0; i < GlobalConfig.utilities.vpn.provider.length; i++) {
if (i !== root.vpnProvider.index) {
providers.push(GlobalConfig.utilities.vpn.provider[i]);
}
}
GlobalConfig.utilities.vpn.provider = providers;
root.session.vpn.active = null;
}
}
}
TextButton {
Layout.fillWidth: true
Layout.topMargin: Tokens.spacing.medium
visible: root.providerEnabled && VPN.status.state === "needs-auth" && VPN.status.authUrl !== ""
text: qsTr("Open Login Page")
inactiveColour: Colours.palette.m3tertiaryContainer
inactiveOnColour: Colours.palette.m3onTertiaryContainer
onClicked: {
Qt.openUrlExternally(VPN.status.authUrl);
}
}
StyledText {
Layout.fillWidth: true
Layout.topMargin: Tokens.spacing.medium
visible: root.providerEnabled && VPN.status.state === "needs-auth" && VPN.status.authUrl === ""
text: qsTr("Click 'Connect' to generate authentication URL")
font: Tokens.font.body.small
color: Colours.palette.m3onSurfaceVariant
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
}
}
}
},
Component {
ColumnLayout {
spacing: Tokens.spacing.medium
SectionHeader {
title: qsTr("Provider details")
description: qsTr("VPN provider information")
}
SectionContainer {
contentSpacing: Tokens.spacing.extraSmall
PropertyRow {
label: qsTr("Provider")
value: root.vpnProvider?.name ?? qsTr("Unknown")
}
PropertyRow {
showTopMargin: true
label: qsTr("Display name")
value: root.vpnProvider?.displayName ?? qsTr("Unknown")
}
PropertyRow {
showTopMargin: true
label: qsTr("Interface")
value: root.vpnProvider?.interface || qsTr("N/A")
}
PropertyRow {
showTopMargin: true
label: qsTr("Status")
value: {
if (!root.providerEnabled)
return qsTr("Disabled");
if (VPN.connecting)
return qsTr("Connecting...");
switch (VPN.status.state) {
case "connected":
return qsTr("Connected");
case "disconnected":
return qsTr("Disconnected");
case "connecting":
return qsTr("Connecting...");
case "needs-auth":
return qsTr("Authentication required");
case "error":
return qsTr("Error");
default:
return qsTr("Unknown");
}
}
}
PropertyRow {
visible: VPN.status.reason !== ""
showTopMargin: true
label: qsTr("Details")
value: VPN.status.reason
}
PropertyRow {
showTopMargin: true
label: qsTr("Enabled")
value: root.providerEnabled ? qsTr("Yes") : qsTr("No")
}
}
}
}
]
// Edit VPN Dialog
Popup {
id: editVpnDialog
property int editIndex: -1
property string providerName: ""
property string displayName: ""
property string interfaceName: ""
property string connectCmd: ""
property string disconnectCmd: ""
function closeWithAnimation(): void {
close();
}
parent: Overlay.overlay
anchors.centerIn: parent
width: Math.min(400, parent.width - Tokens.padding.extraLargeIncreased)
padding: Tokens.padding.large * 1.5
modal: true
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
opacity: 0
scale: 0.7
enter: Transition {
Anim {
property: "opacity"
from: 0
to: 1
type: Anim.FastSpatial
}
Anim {
property: "scale"
from: 0.7
to: 1
type: Anim.FastSpatial
}
}
exit: Transition {
Anim {
property: "opacity"
from: 1
to: 0
type: Anim.FastSpatial
}
Anim {
property: "scale"
from: 1
to: 0.7
type: Anim.FastSpatial
}
}
Overlay.modal: Rectangle {
color: Qt.rgba(0, 0, 0, 0.4 * editVpnDialog.opacity)
}
background: StyledRect {
color: Colours.palette.m3surfaceContainerHigh
radius: Tokens.rounding.extraLarge
Elevation {
anchors.fill: parent
radius: parent.radius
level: 3
z: -1
}
}
contentItem: ColumnLayout {
spacing: Tokens.spacing.medium
StyledText {
text: qsTr("Edit VPN Provider")
font: Tokens.font.body.builders.large.weight(Font.Medium).build()
}
ColumnLayout {
Layout.fillWidth: true
spacing: Tokens.spacing.medium / 2
StyledText {
text: qsTr("Display Name")
font: Tokens.font.body.small
color: Colours.palette.m3onSurfaceVariant
}
StyledRect {
Layout.fillWidth: true
implicitHeight: 40
color: displayNameField.activeFocus ? Colours.layer(Colours.palette.m3surfaceContainer, 3) : Colours.layer(Colours.palette.m3surfaceContainer, 2)
radius: Tokens.rounding.medium
border.width: 1
border.color: displayNameField.activeFocus ? Colours.palette.m3primary : Qt.alpha(Colours.palette.m3outline, 0.3)
Behavior on color {
CAnim {}
}
Behavior on border.color {
CAnim {}
}
StyledTextField {
id: displayNameField
anchors.centerIn: parent
width: parent.width - Tokens.padding.medium
horizontalAlignment: TextInput.AlignLeft
text: editVpnDialog.displayName
onTextChanged: editVpnDialog.displayName = text
}
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: Tokens.spacing.medium / 2
StyledText {
text: qsTr("Interface (e.g., wg0, torguard)")
font: Tokens.font.body.small
color: Colours.palette.m3onSurfaceVariant
}
StyledRect {
Layout.fillWidth: true
implicitHeight: 40
color: interfaceNameField.activeFocus ? Colours.layer(Colours.palette.m3surfaceContainer, 3) : Colours.layer(Colours.palette.m3surfaceContainer, 2)
radius: Tokens.rounding.medium
border.width: 1
border.color: interfaceNameField.activeFocus ? Colours.palette.m3primary : Qt.alpha(Colours.palette.m3outline, 0.3)
Behavior on color {
CAnim {}
}
Behavior on border.color {
CAnim {}
}
StyledTextField {
id: interfaceNameField
anchors.centerIn: parent
width: parent.width - Tokens.padding.medium
horizontalAlignment: TextInput.AlignLeft
text: editVpnDialog.interfaceName
onTextChanged: editVpnDialog.interfaceName = text
}
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: Tokens.spacing.medium / 2
visible: editVpnDialog.connectCmd.length > 0
StyledText {
text: qsTr("Connect Command")
font: Tokens.font.body.small
color: Colours.palette.m3onSurfaceVariant
}
StyledRect {
Layout.fillWidth: true
implicitHeight: 40
color: connectCmdFieldEdit.activeFocus ? Colours.layer(Colours.palette.m3surfaceContainer, 3) : Colours.layer(Colours.palette.m3surfaceContainer, 2)
radius: Tokens.rounding.medium
border.width: 1
border.color: connectCmdFieldEdit.activeFocus ? Colours.palette.m3primary : Qt.alpha(Colours.palette.m3outline, 0.3)
Behavior on color {
CAnim {}
}
Behavior on border.color {
CAnim {}
}
StyledTextField {
id: connectCmdFieldEdit
anchors.centerIn: parent
width: parent.width - Tokens.padding.medium
horizontalAlignment: TextInput.AlignLeft
text: editVpnDialog.connectCmd
onTextChanged: editVpnDialog.connectCmd = text
}
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: Tokens.spacing.medium / 2
visible: editVpnDialog.disconnectCmd.length > 0
StyledText {
text: qsTr("Disconnect Command")
font: Tokens.font.body.small
color: Colours.palette.m3onSurfaceVariant
}
StyledRect {
Layout.fillWidth: true
implicitHeight: 40
color: disconnectCmdFieldEdit.activeFocus ? Colours.layer(Colours.palette.m3surfaceContainer, 3) : Colours.layer(Colours.palette.m3surfaceContainer, 2)
radius: Tokens.rounding.medium
border.width: 1
border.color: disconnectCmdFieldEdit.activeFocus ? Colours.palette.m3primary : Qt.alpha(Colours.palette.m3outline, 0.3)
Behavior on color {
CAnim {}
}
Behavior on border.color {
CAnim {}
}
StyledTextField {
id: disconnectCmdFieldEdit
anchors.centerIn: parent
width: parent.width - Tokens.padding.medium
horizontalAlignment: TextInput.AlignLeft
text: editVpnDialog.disconnectCmd
onTextChanged: editVpnDialog.disconnectCmd = text
}
}
}
RowLayout {
Layout.topMargin: Tokens.spacing.medium
Layout.fillWidth: true
spacing: Tokens.spacing.medium
TextButton {
Layout.fillWidth: true
text: qsTr("Cancel")
inactiveColour: Colours.tPalette.m3surfaceContainerHigh
inactiveOnColour: Colours.palette.m3onSurface
onClicked: editVpnDialog.closeWithAnimation()
}
TextButton {
Layout.fillWidth: true
text: qsTr("Save")
enabled: editVpnDialog.interfaceName.length > 0
inactiveColour: Colours.palette.m3primaryContainer
inactiveOnColour: Colours.palette.m3onPrimaryContainer
onClicked: {
const providers = [];
const oldProvider = GlobalConfig.utilities.vpn.provider[editVpnDialog.editIndex];
const wasEnabled = typeof oldProvider === "object" ? (oldProvider.enabled !== false) : true;
for (let i = 0; i < GlobalConfig.utilities.vpn.provider.length; i++) {
if (i === editVpnDialog.editIndex) {
const hasCommands = editVpnDialog.connectCmd.length > 0 && editVpnDialog.disconnectCmd.length > 0;
const newProvider = {
displayName: editVpnDialog.displayName || editVpnDialog.interfaceName,
enabled: wasEnabled,
interface: editVpnDialog.interfaceName,
name: editVpnDialog.providerName
};
if (hasCommands) {
newProvider.connectCmd = editVpnDialog.connectCmd.split(" ").filter(s => s.length > 0);
newProvider.disconnectCmd = editVpnDialog.disconnectCmd.split(" ").filter(s => s.length > 0);
}
providers.push(newProvider);
} else {
const p = GlobalConfig.utilities.vpn.provider[i];
const reconstructed = {
displayName: p.displayName,
enabled: p.enabled,
interface: p.interface,
name: p.name
};
if (p.connectCmd && p.connectCmd.length > 0) {
reconstructed.connectCmd = p.connectCmd;
}
if (p.disconnectCmd && p.disconnectCmd.length > 0) {
reconstructed.disconnectCmd = p.disconnectCmd;
}
providers.push(reconstructed);
}
}
GlobalConfig.utilities.vpn.provider = providers;
editVpnDialog.closeWithAnimation();
}
}
}
}
}
}

View file

@ -1,827 +0,0 @@
pragma ComponentBehavior: Bound
import ".."
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Caelestia.Config
import qs.components
import qs.components.controls
import qs.components.effects
import qs.services
ColumnLayout {
id: root
required property Session session
property bool showHeader: true
property int pendingSwitchIndex: -1
spacing: Tokens.spacing.medium
Connections {
function onConnectedChanged() {
if (!VPN.connected && root.pendingSwitchIndex >= 0) {
const targetIndex = root.pendingSwitchIndex;
root.pendingSwitchIndex = -1;
const providers = [];
for (let i = 0; i < GlobalConfig.utilities.vpn.provider.length; i++) {
const p = GlobalConfig.utilities.vpn.provider[i];
if (typeof p === "object") {
const newProvider = {
name: p.name,
displayName: p.displayName,
interface: p.interface,
enabled: (i === targetIndex)
};
if (p.connectCmd && p.connectCmd.length > 0) {
newProvider.connectCmd = p.connectCmd;
}
if (p.disconnectCmd && p.disconnectCmd.length > 0) {
newProvider.disconnectCmd = p.disconnectCmd;
}
providers.push(newProvider);
} else {
providers.push(p);
}
}
GlobalConfig.utilities.vpn.provider = providers;
Qt.callLater(function () {
VPN.toggle();
});
}
}
target: VPN
}
TextButton {
Layout.fillWidth: true
text: qsTr("+ Add VPN Provider")
inactiveColour: Colours.palette.m3primaryContainer
inactiveOnColour: Colours.palette.m3onPrimaryContainer
onClicked: {
vpnDialog.showProviderSelection();
}
}
ListView {
id: listView
Layout.fillWidth: true
Layout.preferredHeight: contentHeight
interactive: false
spacing: Tokens.spacing.medium
model: ScriptModel {
values: GlobalConfig.utilities.vpn.provider.map((provider, index) => {
const isObject = typeof provider === "object";
const name = isObject ? (provider.name || "custom") : String(provider);
const displayName = isObject ? (provider.displayName || name) : name;
const iface = isObject ? (provider.interface || "") : "";
const enabled = isObject ? (provider.enabled === true) : false;
return {
index: index,
name: name,
displayName: displayName,
interface: iface,
provider: provider,
enabled: enabled
};
})
}
delegate: Component {
StyledRect {
required property var modelData
required property int index
width: ListView.view ? ListView.view.width : undefined
implicitHeight: rowLayout.implicitHeight + Tokens.padding.medium * 2
color: Qt.alpha(Colours.tPalette.m3surfaceContainer, (root.session && root.session.vpn && root.session.vpn.active === modelData) ? Colours.tPalette.m3surfaceContainer.a : 0)
radius: Tokens.rounding.large
StateLayer {
onClicked: {
if (root.session && root.session.vpn) {
root.session.vpn.active = modelData;
}
}
}
RowLayout {
id: rowLayout
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Tokens.padding.medium
spacing: Tokens.spacing.medium
StyledRect {
implicitWidth: implicitHeight
implicitHeight: icon.implicitHeight + Tokens.padding.medium * 2
radius: Tokens.rounding.large
color: modelData.enabled && VPN.connected ? Colours.palette.m3primaryContainer : Colours.tPalette.m3surfaceContainerHigh
MaterialIcon {
id: icon
anchors.centerIn: parent
text: modelData.enabled && VPN.connected ? "vpn_key" : "vpn_key_off"
fontStyle: Tokens.font.icon.large
fill: modelData.enabled && VPN.connected ? 1 : 0
color: modelData.enabled && VPN.connected ? Colours.palette.m3onPrimaryContainer : Colours.palette.m3onSurface
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: 0
StyledText {
Layout.fillWidth: true
elide: Text.ElideRight
maximumLineCount: 1
text: modelData.displayName || qsTr("Unknown")
}
RowLayout {
Layout.fillWidth: true
spacing: Tokens.spacing.medium
StyledText {
Layout.fillWidth: true
text: {
if (!modelData.enabled)
return qsTr("Disabled");
if (VPN.connecting)
return qsTr("Connecting...");
switch (VPN.status.state) {
case "connected":
return qsTr("Connected");
case "disconnected":
return qsTr("Enabled");
case "connecting":
return qsTr("Connecting...");
case "needs-auth":
return qsTr("Auth required");
case "error":
return qsTr("Error");
default:
return qsTr("Enabled");
}
}
color: {
if (!modelData.enabled)
return Colours.palette.m3outline;
if (VPN.status.state === "connected")
return Colours.palette.m3primary;
if (VPN.status.state === "error")
return Colours.palette.m3error;
if (VPN.status.state === "needs-auth")
return Colours.palette.m3tertiary;
return Colours.palette.m3onSurface;
}
font: Tokens.font.body.builders.small.weight(modelData.enabled && VPN.connected ? Font.Medium : Font.Normal).build()
elide: Text.ElideRight
}
}
}
StyledRect {
implicitWidth: implicitHeight
implicitHeight: connectIcon.implicitHeight + Tokens.padding.large
radius: Tokens.rounding.full
color: Qt.alpha(Colours.palette.m3primaryContainer, VPN.connected && modelData.enabled ? 1 : 0)
StateLayer {
onClicked: {
const clickedIndex = modelData.index;
if (modelData.enabled) {
VPN.toggle();
} else {
if (VPN.connected) {
root.pendingSwitchIndex = clickedIndex;
VPN.toggle();
} else {
const providers = [];
for (let i = 0; i < GlobalConfig.utilities.vpn.provider.length; i++) {
const p = GlobalConfig.utilities.vpn.provider[i];
if (typeof p === "object") {
const newProvider = {
name: p.name,
displayName: p.displayName,
interface: p.interface,
enabled: (i === clickedIndex)
};
if (p.connectCmd && p.connectCmd.length > 0) {
newProvider.connectCmd = p.connectCmd;
}
if (p.disconnectCmd && p.disconnectCmd.length > 0) {
newProvider.disconnectCmd = p.disconnectCmd;
}
providers.push(newProvider);
} else {
providers.push(p);
}
}
GlobalConfig.utilities.vpn.provider = providers;
Qt.callLater(function () {
VPN.toggle();
});
}
}
}
enabled: !VPN.connecting
}
MaterialIcon {
id: connectIcon
anchors.centerIn: parent
text: VPN.connected && modelData.enabled ? "link_off" : "link"
color: VPN.connected && modelData.enabled ? Colours.palette.m3onPrimaryContainer : Colours.palette.m3onSurface
}
}
StyledRect {
implicitWidth: implicitHeight
implicitHeight: deleteIcon.implicitHeight + Tokens.padding.large
radius: Tokens.rounding.full
color: "transparent"
StateLayer {
onClicked: {
const providers = [];
for (let i = 0; i < GlobalConfig.utilities.vpn.provider.length; i++) {
if (i !== modelData.index) {
const p = GlobalConfig.utilities.vpn.provider[i];
const reconstructed = {
name: p.name,
displayName: p.displayName,
interface: p.interface,
enabled: p.enabled
};
if (p.connectCmd && p.connectCmd.length > 0) {
reconstructed.connectCmd = p.connectCmd;
}
if (p.disconnectCmd && p.disconnectCmd.length > 0) {
reconstructed.disconnectCmd = p.disconnectCmd;
}
providers.push(reconstructed);
}
}
GlobalConfig.utilities.vpn.provider = providers;
}
}
MaterialIcon {
id: deleteIcon
anchors.centerIn: parent
text: "delete"
color: Colours.palette.m3onSurface
}
}
}
}
}
}
Popup {
id: vpnDialog
property string currentState: "selection"
property int editIndex: -1
property string providerName: ""
property string displayName: ""
property string interfaceName: ""
property string connectCmd: ""
property string disconnectCmd: ""
function showProviderSelection(): void {
currentState = "selection";
open();
}
function closeWithAnimation(): void {
close();
}
function showAddForm(providerType: string, defaultDisplayName: string): void {
editIndex = -1;
providerName = providerType;
displayName = defaultDisplayName;
interfaceName = "";
connectCmd = "";
disconnectCmd = "";
if (currentState === "selection") {
transitionToForm.start();
} else {
currentState = "form";
isClosing = false;
open();
}
}
function showEditForm(index: int): void {
const provider = GlobalConfig.utilities.vpn.provider[index];
const isObject = typeof provider === "object";
editIndex = index;
providerName = isObject ? (provider.name || "custom") : String(provider);
displayName = isObject ? (provider.displayName || providerName) : providerName;
interfaceName = isObject ? (provider.interface || "") : "";
connectCmd = isObject && provider.connectCmd ? provider.connectCmd.join(" ") : "";
disconnectCmd = isObject && provider.disconnectCmd ? provider.disconnectCmd.join(" ") : "";
currentState = "form";
open();
}
parent: Overlay.overlay
x: Math.round((parent.width - width) / 2)
y: Math.round((parent.height - height) / 2)
implicitWidth: Math.min(400, parent.width - Tokens.padding.extraLargeIncreased)
padding: Tokens.padding.large * 1.5
modal: true
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
opacity: 0
scale: 0.7
enter: Transition {
ParallelAnimation {
Anim {
property: "opacity"
from: 0
to: 1
type: Anim.Emphasized
}
Anim {
property: "scale"
from: 0.7
to: 1
type: Anim.Emphasized
}
}
}
exit: Transition {
ParallelAnimation {
Anim {
property: "opacity"
from: 1
to: 0
type: Anim.EmphasizedSmall
}
Anim {
property: "scale"
from: 1
to: 0.7
type: Anim.EmphasizedSmall
}
}
}
Overlay.modal: Rectangle {
color: Qt.rgba(0, 0, 0, 0.4 * vpnDialog.opacity)
}
onClosed: {
currentState = "selection";
}
background: StyledRect {
color: Colours.palette.m3surfaceContainerHigh
radius: Tokens.rounding.extraLarge
Elevation {
anchors.fill: parent
radius: parent.radius
level: 3
z: -1
}
Behavior on implicitHeight {
Anim {
type: Anim.Emphasized
}
}
}
contentItem: Item {
implicitHeight: vpnDialog.currentState === "selection" ? selectionContent.implicitHeight : formContent.implicitHeight
Behavior on implicitHeight {
Anim {
type: Anim.Emphasized
}
}
ColumnLayout {
id: selectionContent
anchors.fill: parent
spacing: Tokens.spacing.medium
visible: vpnDialog.currentState === "selection"
opacity: vpnDialog.currentState === "selection" ? 1 : 0
Behavior on opacity {
Anim {
type: Anim.EmphasizedSmall
}
}
StyledText {
text: qsTr("Add VPN Provider")
font: Tokens.font.body.builders.large.weight(Font.Medium).build()
}
StyledText {
Layout.fillWidth: true
text: qsTr("Choose a provider to add")
wrapMode: Text.WordWrap
color: Colours.palette.m3outline
font: Tokens.font.body.small
}
TextButton {
Layout.topMargin: Tokens.spacing.medium
Layout.fillWidth: true
text: qsTr("NetBird")
inactiveColour: Colours.tPalette.m3surfaceContainerHigh
inactiveOnColour: Colours.palette.m3onSurface
onClicked: {
const providers = [];
for (let i = 0; i < GlobalConfig.utilities.vpn.provider.length; i++) {
providers.push(GlobalConfig.utilities.vpn.provider[i]);
}
providers.push({
name: "netbird",
displayName: "NetBird",
interface: "wt0"
});
GlobalConfig.utilities.vpn.provider = providers;
vpnDialog.closeWithAnimation();
}
}
TextButton {
Layout.fillWidth: true
text: qsTr("Tailscale")
inactiveColour: Colours.tPalette.m3surfaceContainerHigh
inactiveOnColour: Colours.palette.m3onSurface
onClicked: {
const providers = [];
for (let i = 0; i < GlobalConfig.utilities.vpn.provider.length; i++) {
providers.push(GlobalConfig.utilities.vpn.provider[i]);
}
providers.push({
name: "tailscale",
displayName: "Tailscale",
interface: "tailscale0"
});
GlobalConfig.utilities.vpn.provider = providers;
vpnDialog.closeWithAnimation();
}
}
TextButton {
Layout.fillWidth: true
text: qsTr("Cloudflare WARP")
inactiveColour: Colours.tPalette.m3surfaceContainerHigh
inactiveOnColour: Colours.palette.m3onSurface
onClicked: {
const providers = [];
for (let i = 0; i < GlobalConfig.utilities.vpn.provider.length; i++) {
providers.push(GlobalConfig.utilities.vpn.provider[i]);
}
providers.push({
name: "warp",
displayName: "Cloudflare WARP",
interface: "CloudflareWARP"
});
GlobalConfig.utilities.vpn.provider = providers;
vpnDialog.closeWithAnimation();
}
}
TextButton {
Layout.fillWidth: true
text: qsTr("WireGuard")
inactiveColour: Colours.tPalette.m3surfaceContainerHigh
inactiveOnColour: Colours.palette.m3onSurface
onClicked: {
vpnDialog.showAddForm("wireguard", "WireGuard");
}
}
TextButton {
Layout.fillWidth: true
text: qsTr("Custom")
inactiveColour: Colours.tPalette.m3surfaceContainerHigh
inactiveOnColour: Colours.palette.m3onSurface
onClicked: {
vpnDialog.showAddForm("custom", "Custom VPN");
}
}
TextButton {
Layout.topMargin: Tokens.spacing.medium
Layout.fillWidth: true
text: qsTr("Cancel")
inactiveColour: Colours.palette.m3secondaryContainer
inactiveOnColour: Colours.palette.m3onSecondaryContainer
onClicked: vpnDialog.closeWithAnimation()
}
}
ColumnLayout {
id: formContent
anchors.fill: parent
spacing: Tokens.spacing.medium
visible: vpnDialog.currentState === "form"
opacity: vpnDialog.currentState === "form" ? 1 : 0
Behavior on opacity {
Anim {
type: Anim.EmphasizedSmall
}
}
StyledText {
text: vpnDialog.editIndex >= 0 ? qsTr("Edit VPN Provider") : qsTr("Add %1 VPN").arg(vpnDialog.displayName)
font: Tokens.font.body.builders.large.weight(Font.Medium).build()
}
ColumnLayout {
Layout.fillWidth: true
spacing: Tokens.spacing.medium / 2
StyledText {
text: qsTr("Display Name")
font: Tokens.font.body.small
color: Colours.palette.m3onSurfaceVariant
}
StyledRect {
Layout.fillWidth: true
implicitHeight: 40
color: displayNameField.activeFocus ? Colours.layer(Colours.palette.m3surfaceContainer, 3) : Colours.layer(Colours.palette.m3surfaceContainer, 2)
radius: Tokens.rounding.medium
border.width: 1
border.color: displayNameField.activeFocus ? Colours.palette.m3primary : Qt.alpha(Colours.palette.m3outline, 0.3)
Behavior on color {
CAnim {}
}
Behavior on border.color {
CAnim {}
}
StyledTextField {
id: displayNameField
anchors.centerIn: parent
width: parent.width - Tokens.padding.medium
horizontalAlignment: TextInput.AlignLeft
text: vpnDialog.displayName
onTextChanged: vpnDialog.displayName = text
}
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: Tokens.spacing.medium / 2
StyledText {
text: qsTr("Interface (e.g., wg0, torguard)")
font: Tokens.font.body.small
color: Colours.palette.m3onSurfaceVariant
}
StyledRect {
Layout.fillWidth: true
implicitHeight: 40
color: interfaceNameField.activeFocus ? Colours.layer(Colours.palette.m3surfaceContainer, 3) : Colours.layer(Colours.palette.m3surfaceContainer, 2)
radius: Tokens.rounding.medium
border.width: 1
border.color: interfaceNameField.activeFocus ? Colours.palette.m3primary : Qt.alpha(Colours.palette.m3outline, 0.3)
Behavior on color {
CAnim {}
}
Behavior on border.color {
CAnim {}
}
StyledTextField {
id: interfaceNameField
anchors.centerIn: parent
width: parent.width - Tokens.padding.medium
horizontalAlignment: TextInput.AlignLeft
text: vpnDialog.interfaceName
onTextChanged: vpnDialog.interfaceName = text
}
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: Tokens.spacing.medium / 2
visible: vpnDialog.editIndex >= 0 ? (vpnDialog.connectCmd.length > 0) : (vpnDialog.providerName === "custom")
StyledText {
text: qsTr("Connect Command (e.g., wg-quick up wg0)")
font: Tokens.font.body.small
color: Colours.palette.m3onSurfaceVariant
}
StyledRect {
Layout.fillWidth: true
implicitHeight: 40
color: connectCmdField.activeFocus ? Colours.layer(Colours.palette.m3surfaceContainer, 3) : Colours.layer(Colours.palette.m3surfaceContainer, 2)
radius: Tokens.rounding.medium
border.width: 1
border.color: connectCmdField.activeFocus ? Colours.palette.m3primary : Qt.alpha(Colours.palette.m3outline, 0.3)
Behavior on color {
CAnim {}
}
Behavior on border.color {
CAnim {}
}
StyledTextField {
id: connectCmdField
anchors.centerIn: parent
width: parent.width - Tokens.padding.medium
horizontalAlignment: TextInput.AlignLeft
text: vpnDialog.connectCmd
onTextChanged: vpnDialog.connectCmd = text
}
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: Tokens.spacing.medium / 2
visible: vpnDialog.editIndex >= 0 ? (vpnDialog.connectCmd.length > 0) : (vpnDialog.providerName === "custom")
StyledText {
text: qsTr("Disconnect Command (e.g., wg-quick down wg0)")
font: Tokens.font.body.small
color: Colours.palette.m3onSurfaceVariant
}
StyledRect {
Layout.fillWidth: true
implicitHeight: 40
color: disconnectCmdField.activeFocus ? Colours.layer(Colours.palette.m3surfaceContainer, 3) : Colours.layer(Colours.palette.m3surfaceContainer, 2)
radius: Tokens.rounding.medium
border.width: 1
border.color: disconnectCmdField.activeFocus ? Colours.palette.m3primary : Qt.alpha(Colours.palette.m3outline, 0.3)
Behavior on color {
CAnim {}
}
Behavior on border.color {
CAnim {}
}
StyledTextField {
id: disconnectCmdField
anchors.centerIn: parent
width: parent.width - Tokens.padding.medium
horizontalAlignment: TextInput.AlignLeft
text: vpnDialog.disconnectCmd
onTextChanged: vpnDialog.disconnectCmd = text
}
}
}
RowLayout {
Layout.topMargin: Tokens.spacing.medium
Layout.fillWidth: true
spacing: Tokens.spacing.medium
TextButton {
Layout.fillWidth: true
text: qsTr("Cancel")
inactiveColour: Colours.tPalette.m3surfaceContainerHigh
inactiveOnColour: Colours.palette.m3onSurface
onClicked: vpnDialog.closeWithAnimation()
}
TextButton {
Layout.fillWidth: true
text: qsTr("Save")
enabled: {
const hasCommands = vpnDialog.connectCmd.length > 0 || vpnDialog.disconnectCmd.length > 0;
if (hasCommands) {
return vpnDialog.interfaceName.length > 0 && vpnDialog.connectCmd.length > 0 && vpnDialog.disconnectCmd.length > 0;
}
return vpnDialog.interfaceName.length > 0;
}
inactiveColour: Colours.palette.m3primaryContainer
inactiveOnColour: Colours.palette.m3onPrimaryContainer
onClicked: {
const providers = [];
const hasCommands = vpnDialog.connectCmd.length > 0 && vpnDialog.disconnectCmd.length > 0;
const newProvider = {
displayName: vpnDialog.displayName || vpnDialog.interfaceName,
enabled: false,
interface: vpnDialog.interfaceName,
name: vpnDialog.providerName
};
if (hasCommands) {
newProvider.connectCmd = vpnDialog.connectCmd.split(" ").filter(s => s.length > 0);
newProvider.disconnectCmd = vpnDialog.disconnectCmd.split(" ").filter(s => s.length > 0);
}
if (vpnDialog.editIndex >= 0) {
const oldProvider = GlobalConfig.utilities.vpn.provider[vpnDialog.editIndex];
if (typeof oldProvider === "object" && oldProvider.enabled !== undefined) {
newProvider.enabled = oldProvider.enabled;
}
for (let i = 0; i < GlobalConfig.utilities.vpn.provider.length; i++) {
if (i === vpnDialog.editIndex) {
providers.push(newProvider);
} else {
providers.push(GlobalConfig.utilities.vpn.provider[i]);
}
}
} else {
for (let i = 0; i < GlobalConfig.utilities.vpn.provider.length; i++) {
providers.push(GlobalConfig.utilities.vpn.provider[i]);
}
providers.push(newProvider);
}
GlobalConfig.utilities.vpn.provider = providers;
vpnDialog.closeWithAnimation();
}
}
}
}
}
SequentialAnimation {
id: transitionToForm
ParallelAnimation {
Anim {
target: selectionContent
property: "opacity"
to: 0
type: Anim.EmphasizedSmall
}
}
ScriptAction {
script: {
vpnDialog.currentState = "form";
}
}
ParallelAnimation {
Anim {
target: formContent
property: "opacity"
to: 1
type: Anim.EmphasizedSmall
}
}
}
}
}

View file

@ -1,257 +0,0 @@
pragma ComponentBehavior: Bound
import ".."
import "../components"
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Caelestia.Config
import qs.components
import qs.components.containers
import qs.components.controls
import qs.components.effects
import qs.services
ColumnLayout {
id: root
required property Session session
spacing: Tokens.spacing.medium
SettingsHeader {
icon: "vpn_key"
title: qsTr("VPN Settings")
}
SectionHeader {
Layout.topMargin: Tokens.spacing.largeIncreased
title: qsTr("General")
description: qsTr("VPN configuration")
}
SectionContainer {
ToggleRow {
label: qsTr("VPN enabled")
checked: GlobalConfig.utilities.vpn.enabled
toggle.onToggled: {
GlobalConfig.utilities.vpn.enabled = checked;
}
}
}
SectionHeader {
Layout.topMargin: Tokens.spacing.largeIncreased
title: qsTr("Providers")
description: qsTr("Manage VPN providers")
}
SectionContainer {
contentSpacing: Tokens.spacing.medium
ListView {
Layout.fillWidth: true
Layout.preferredHeight: contentHeight
interactive: false
spacing: Tokens.spacing.medium
model: ScriptModel {
values: GlobalConfig.utilities.vpn.provider.map((provider, index) => {
const isObject = typeof provider === "object";
const name = isObject ? (provider.name || "custom") : String(provider);
const displayName = isObject ? (provider.displayName || name) : name;
const iface = isObject ? (provider.interface || "") : "";
return {
index: index,
name: name,
displayName: displayName,
interface: iface,
provider: provider,
isActive: index === 0
};
})
}
delegate: Component {
StyledRect {
required property var modelData
required property int index
width: ListView.view ? ListView.view.width : undefined
implicitHeight: 60
color: Colours.tPalette.m3surfaceContainerHigh
radius: Tokens.rounding.large
RowLayout {
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Tokens.padding.medium
spacing: Tokens.spacing.medium
MaterialIcon {
text: modelData.isActive ? "vpn_key" : "vpn_key_off"
fontStyle: Tokens.font.icon.large
color: modelData.isActive ? Colours.palette.m3primary : Colours.palette.m3outline
}
ColumnLayout {
Layout.fillWidth: true
spacing: 0
StyledText {
text: modelData.displayName
font: Tokens.font.body.builders.medium.weight(modelData.isActive ? Font.Medium : Font.Normal).build()
}
StyledText {
text: qsTr("%1 • %2").arg(modelData.name).arg(modelData.interface || qsTr("No interface"))
font: Tokens.font.body.small
color: Colours.palette.m3outline
}
}
IconButton {
icon: modelData.isActive ? "arrow_downward" : "arrow_upward"
visible: !modelData.isActive || GlobalConfig.utilities.vpn.provider.length > 1
onClicked: {
const providers = [];
for (let i = 0; i < GlobalConfig.utilities.vpn.provider.length; i++) {
const p = GlobalConfig.utilities.vpn.provider[i];
const reconstructed = {
name: p.name,
displayName: p.displayName,
interface: p.interface,
enabled: p.enabled
};
if (p.connectCmd && p.connectCmd.length > 0) {
reconstructed.connectCmd = p.connectCmd;
}
if (p.disconnectCmd && p.disconnectCmd.length > 0) {
reconstructed.disconnectCmd = p.disconnectCmd;
}
providers.push(reconstructed);
}
if (modelData.isActive && index < providers.length - 1) {
// Move down
const temp = providers[index];
providers[index] = providers[index + 1];
providers[index + 1] = temp;
} else if (!modelData.isActive) {
// Make active (move to top)
const provider = providers.splice(index, 1)[0];
providers.unshift(provider);
}
GlobalConfig.utilities.vpn.provider = providers;
}
}
IconButton {
icon: "delete"
onClicked: {
const providers = [];
for (let i = 0; i < GlobalConfig.utilities.vpn.provider.length; i++) {
if (i !== index) {
const p = GlobalConfig.utilities.vpn.provider[i];
const reconstructed = {
name: p.name,
displayName: p.displayName,
interface: p.interface,
enabled: p.enabled
};
if (p.connectCmd && p.connectCmd.length > 0) {
reconstructed.connectCmd = p.connectCmd;
}
if (p.disconnectCmd && p.disconnectCmd.length > 0) {
reconstructed.disconnectCmd = p.disconnectCmd;
}
providers.push(reconstructed);
}
}
GlobalConfig.utilities.vpn.provider = providers;
}
}
}
}
}
}
TextButton {
Layout.fillWidth: true
Layout.topMargin: Tokens.spacing.medium
text: qsTr("+ Add Provider")
inactiveColour: Colours.palette.m3primaryContainer
inactiveOnColour: Colours.palette.m3onPrimaryContainer
onClicked: {
addProviderDialog.open();
}
}
}
SectionHeader {
Layout.topMargin: Tokens.spacing.largeIncreased
title: qsTr("Quick Add")
description: qsTr("Add common VPN providers")
}
SectionContainer {
contentSpacing: Tokens.spacing.medium
TextButton {
Layout.fillWidth: true
text: qsTr("+ Add NetBird")
inactiveColour: Colours.tPalette.m3surfaceContainerHigh
inactiveOnColour: Colours.palette.m3onSurface
onClicked: {
const providers = [...GlobalConfig.utilities.vpn.provider];
providers.push({
name: "netbird",
displayName: "NetBird",
interface: "wt0"
});
GlobalConfig.utilities.vpn.provider = providers;
}
}
TextButton {
Layout.fillWidth: true
text: qsTr("+ Add Tailscale")
inactiveColour: Colours.tPalette.m3surfaceContainerHigh
inactiveOnColour: Colours.palette.m3onSurface
onClicked: {
const providers = [...GlobalConfig.utilities.vpn.provider];
providers.push({
name: "tailscale",
displayName: "Tailscale",
interface: "tailscale0"
});
GlobalConfig.utilities.vpn.provider = providers;
}
}
TextButton {
Layout.fillWidth: true
text: qsTr("+ Add Cloudflare WARP")
inactiveColour: Colours.tPalette.m3surfaceContainerHigh
inactiveOnColour: Colours.palette.m3onSurface
onClicked: {
const providers = [...GlobalConfig.utilities.vpn.provider];
providers.push({
name: "warp",
displayName: "Cloudflare WARP",
interface: "CloudflareWARP"
});
GlobalConfig.utilities.vpn.provider = providers;
}
}
}
}

View file

@ -1,213 +0,0 @@
pragma ComponentBehavior: Bound
import ".."
import "../components"
import "."
import QtQuick
import QtQuick.Layouts
import Caelestia.Config
import qs.components
import qs.components.containers
import qs.components.controls
import qs.components.effects
import qs.services
import qs.utils
DeviceDetails {
id: root
required property Session session
readonly property var network: root.session.network.active
function checkSavedProfile(): void {
if (network && network.ssid) {
Nmcli.loadSavedConnections(() => {});
}
}
function updateDeviceDetails(): void {
if (network && network.ssid) {
const isActive = network.active || (Nmcli.active && Nmcli.active.ssid === network.ssid);
if (isActive) {
Nmcli.getWirelessDeviceDetails("");
} else {
Nmcli.wirelessDeviceDetails = null;
}
} else {
Nmcli.wirelessDeviceDetails = null;
}
}
device: network
Component.onCompleted: {
updateDeviceDetails();
checkSavedProfile();
}
onNetworkChanged: {
connectionUpdateTimer.stop();
if (network && network.ssid) {
connectionUpdateTimer.start();
}
updateDeviceDetails();
checkSavedProfile();
}
headerComponent: Component {
ConnectionHeader {
icon: root.network?.isSecure ? "lock" : "wifi"
title: root.network?.ssid ?? qsTr("Unknown")
}
}
sections: [
Component {
ColumnLayout {
spacing: Tokens.spacing.medium
SectionHeader {
title: qsTr("Connection status")
description: qsTr("Connection settings for this network")
}
SectionContainer {
ToggleRow {
label: qsTr("Connected")
checked: root.network?.active ?? false
toggle.onToggled: {
if (checked) {
NetworkConnection.handleConnect(root.network, root.session, null);
} else {
Nmcli.disconnectFromNetwork();
}
}
}
TextButton {
Layout.fillWidth: true
Layout.topMargin: Tokens.spacing.medium
Layout.minimumHeight: Tokens.font.body.medium.pointSize + Tokens.padding.medium * 2
visible: {
if (!root.network || !root.network.ssid) {
return false;
}
return Nmcli.hasSavedProfile(root.network.ssid);
}
inactiveColour: Colours.palette.m3secondaryContainer
inactiveOnColour: Colours.palette.m3onSecondaryContainer
text: qsTr("Forget Network")
onClicked: {
if (root.network && root.network.ssid) {
if (root.network.active) {
Nmcli.disconnectFromNetwork();
}
Nmcli.forgetNetwork(root.network.ssid);
}
}
}
}
}
},
Component {
ColumnLayout {
spacing: Tokens.spacing.medium
SectionHeader {
title: qsTr("Network properties")
description: qsTr("Additional information")
}
SectionContainer {
contentSpacing: Tokens.spacing.extraSmall
PropertyRow {
label: qsTr("SSID")
value: root.network?.ssid ?? qsTr("Unknown")
}
PropertyRow {
showTopMargin: true
label: qsTr("BSSID")
value: root.network?.bssid ?? qsTr("Unknown")
}
PropertyRow {
showTopMargin: true
label: qsTr("Signal strength")
value: root.network ? qsTr("%1%").arg(root.network.strength) : qsTr("N/A")
}
PropertyRow {
showTopMargin: true
label: qsTr("Frequency")
value: root.network ? qsTr("%1 MHz").arg(root.network.frequency) : qsTr("N/A")
}
PropertyRow {
showTopMargin: true
label: qsTr("Security")
value: root.network ? (root.network.isSecure ? root.network.security : qsTr("Open")) : qsTr("N/A")
}
}
}
},
Component {
ColumnLayout {
spacing: Tokens.spacing.medium
SectionHeader {
title: qsTr("Connection information")
description: qsTr("Network connection details")
}
SectionContainer {
ConnectionInfoSection {
deviceDetails: Nmcli.wirelessDeviceDetails
}
}
}
}
]
Connections {
function onActiveChanged() {
updateDeviceDetails();
}
function onWirelessDeviceDetailsChanged() {
if (network && network.ssid) {
const isActive = network.active || (Nmcli.active && Nmcli.active.ssid === network.ssid);
if (isActive && Nmcli.wirelessDeviceDetails && Nmcli.wirelessDeviceDetails !== null) {
connectionUpdateTimer.stop();
}
}
}
target: Nmcli
}
Timer {
id: connectionUpdateTimer
interval: 500
repeat: true
running: network && network.ssid
onTriggered: {
if (network) {
const isActive = network.active || (Nmcli.active && Nmcli.active.ssid === network.ssid);
if (isActive) {
if (!Nmcli.wirelessDeviceDetails || Nmcli.wirelessDeviceDetails === null) {
Nmcli.getWirelessDeviceDetails("", () => {});
} else {
connectionUpdateTimer.stop();
}
} else {
if (Nmcli.wirelessDeviceDetails !== null) {
Nmcli.wirelessDeviceDetails = null;
}
}
}
}
}
}

View file

@ -1,225 +0,0 @@
pragma ComponentBehavior: Bound
import ".."
import "../components"
import "."
import QtQuick
import QtQuick.Layouts
import Quickshell
import Caelestia.Config
import qs.components
import qs.components.containers
import qs.components.controls
import qs.components.effects
import qs.services
import qs.utils
DeviceList {
id: root
required property Session session
function checkSavedProfileForNetwork(ssid: string): void {
if (ssid && ssid.length > 0) {
Nmcli.loadSavedConnections(() => {});
}
}
title: qsTr("Networks (%1)").arg(Nmcli.networks.length)
description: qsTr("All available WiFi networks")
activeItem: session.network.active
titleSuffix: Component {
StyledText {
visible: Nmcli.scanning
text: qsTr("Scanning...")
color: Colours.palette.m3primary
font: Tokens.font.title.small
}
}
model: ScriptModel {
values: [...Nmcli.networks].sort((a, b) => {
if (a.active !== b.active)
return b.active - a.active;
return b.strength - a.strength;
})
}
headerComponent: Component {
RowLayout {
spacing: Tokens.spacing.medium
StyledText {
text: qsTr("Settings")
font: Tokens.font.title.builders.medium.weight(Font.Medium).build()
}
Item {
Layout.fillWidth: true
}
ToggleButton {
toggled: Nmcli.wifiEnabled
icon: "wifi"
accent: "Tertiary"
iconSize: Tokens.font.body.medium.pointSize
horizontalPadding: Tokens.padding.medium
verticalPadding: Tokens.padding.small
onClicked: {
Nmcli.toggleWifi(null);
}
}
ToggleButton {
toggled: Nmcli.scanning
icon: "wifi_find"
accent: "Secondary"
iconSize: Tokens.font.body.medium.pointSize
horizontalPadding: Tokens.padding.medium
verticalPadding: Tokens.padding.small
onClicked: {
Nmcli.rescanWifi();
}
}
ToggleButton {
toggled: !root.session.network.active
icon: "settings"
accent: "Primary"
iconSize: Tokens.font.body.medium.pointSize
horizontalPadding: Tokens.padding.medium
verticalPadding: Tokens.padding.small
onClicked: {
if (root.session.network.active)
root.session.network.active = null;
else {
root.session.network.active = root.view.model.get(0)?.modelData ?? null;
}
}
}
}
}
delegate: Component {
StyledRect {
required property var modelData
width: ListView.view ? ListView.view.width : undefined
implicitHeight: rowLayout.implicitHeight + Tokens.padding.medium * 2
color: Qt.alpha(Colours.tPalette.m3surfaceContainer, root.activeItem === modelData ? Colours.tPalette.m3surfaceContainer.a : 0)
radius: Tokens.rounding.large
StateLayer {
onClicked: {
root.session.network.active = modelData;
if (modelData && modelData.ssid) {
root.checkSavedProfileForNetwork(modelData.ssid);
}
}
}
RowLayout {
id: rowLayout
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Tokens.padding.medium
spacing: Tokens.spacing.medium
StyledRect {
implicitWidth: implicitHeight
implicitHeight: icon.implicitHeight + Tokens.padding.medium * 2
radius: Tokens.rounding.large
color: modelData.active ? Colours.palette.m3primaryContainer : Colours.tPalette.m3surfaceContainerHigh
MaterialIcon {
id: icon
anchors.centerIn: parent
text: Icons.getNetworkIcon(modelData.strength, modelData.isSecure)
fontStyle: Tokens.font.icon.large
fill: modelData.active ? 1 : 0
color: modelData.active ? Colours.palette.m3onPrimaryContainer : Colours.palette.m3onSurface
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: 0
StyledText {
Layout.fillWidth: true
elide: Text.ElideRight
maximumLineCount: 1
text: modelData.ssid || qsTr("Unknown")
}
RowLayout {
Layout.fillWidth: true
spacing: Tokens.spacing.medium
StyledText {
Layout.fillWidth: true
text: {
if (modelData.active)
return qsTr("Connected");
if (modelData.isSecure && modelData.security && modelData.security.length > 0) {
return modelData.security;
}
if (modelData.isSecure)
return qsTr("Secured");
return qsTr("Open");
}
color: modelData.active ? Colours.palette.m3primary : Colours.palette.m3outline
font: Tokens.font.body.builders.small.weight(modelData.active ? Font.Medium : Font.Normal).build()
elide: Text.ElideRight
}
}
}
StyledRect {
implicitWidth: implicitHeight
implicitHeight: connectIcon.implicitHeight + Tokens.padding.large
radius: Tokens.rounding.full
color: Qt.alpha(Colours.palette.m3primaryContainer, modelData.active ? 1 : 0)
StateLayer {
onClicked: {
if (modelData.active) {
Nmcli.disconnectFromNetwork();
} else {
NetworkConnection.handleConnect(modelData, root.session, null);
}
}
}
MaterialIcon {
id: connectIcon
anchors.centerIn: parent
text: modelData.active ? "link_off" : "link"
color: modelData.active ? Colours.palette.m3onPrimaryContainer : Colours.palette.m3onSurface
}
}
}
}
}
onItemSelected: function (item) {
session.network.active = item;
if (item && item.ssid) {
checkSavedProfileForNetwork(item.ssid);
}
}
}

View file

@ -1,57 +0,0 @@
pragma ComponentBehavior: Bound
import ".."
import "../components"
import QtQuick
import Quickshell.Widgets
import Caelestia.Config
import qs.components
import qs.components.containers
SplitPaneWithDetails {
id: root
required property Session session
anchors.fill: parent
activeItem: session.network.active
paneIdGenerator: function (item) {
return item ? (item.ssid || item.bssid || "") : "";
}
leftContent: Component {
WirelessList {
session: root.session
}
}
rightDetailsComponent: Component {
WirelessDetails {
session: root.session
}
}
rightSettingsComponent: Component {
StyledFlickable {
flickableDirection: Flickable.VerticalFlick
contentHeight: settingsInner.height
clip: true
WirelessSettings {
id: settingsInner
anchors.left: parent.left
anchors.right: parent.right
session: root.session
}
}
}
overlayComponent: Component {
WirelessPasswordDialog {
anchors.fill: parent
session: root.session
}
}
}

View file

@ -1,522 +0,0 @@
pragma ComponentBehavior: Bound
import ".."
import "."
import QtQuick
import QtQuick.Layouts
import Quickshell
import Caelestia.Config
import qs.components
import qs.components.containers
import qs.components.controls
import qs.components.effects
import qs.services
import qs.utils
Item {
id: root
required property Session session
readonly property var network: {
if (session.network.pendingNetwork) {
return session.network.pendingNetwork;
}
if (session.network.active) {
return session.network.active;
}
return null;
}
property bool isClosing: false
function checkConnectionStatus(): void {
if (!root.visible || !connectButton.connecting) {
return;
}
const isConnected = root.network && Nmcli.active && Nmcli.active.ssid && Nmcli.active.ssid.toLowerCase().trim() === root.network.ssid.toLowerCase().trim();
if (isConnected) {
connectionSuccessTimer.start();
return;
}
if (Nmcli.pendingConnection === null && connectButton.connecting) {
if (connectionMonitor.repeatCount > 10) {
connectionMonitor.stop();
connectButton.connecting = false;
connectButton.hasError = true;
connectButton.enabled = true;
connectButton.text = qsTr("Connect");
passwordContainer.passwordBuffer = "";
if (root.network && root.network.ssid) {
Nmcli.forgetNetwork(root.network.ssid);
}
}
}
}
function closeDialog(): void {
if (isClosing) {
return;
}
isClosing = true;
passwordContainer.passwordBuffer = "";
connectButton.connecting = false;
connectButton.hasError = false;
connectButton.text = qsTr("Connect");
connectionMonitor.stop();
}
visible: session.network.showPasswordDialog || isClosing
enabled: session.network.showPasswordDialog && !isClosing
focus: enabled
Keys.onEscapePressed: {
closeDialog();
}
Rectangle {
anchors.fill: parent
color: Qt.rgba(0, 0, 0, 0.5)
opacity: root.session.network.showPasswordDialog && !root.isClosing ? 1 : 0
Behavior on opacity {
Anim {}
}
MouseArea {
anchors.fill: parent
onClicked: closeDialog()
}
}
StyledRect {
id: dialog
anchors.centerIn: parent
implicitWidth: 400
implicitHeight: content.implicitHeight + Tokens.padding.extraLargeIncreased
radius: Tokens.rounding.large
color: Colours.tPalette.m3surface
opacity: root.session.network.showPasswordDialog && !root.isClosing ? 1 : 0
scale: root.session.network.showPasswordDialog && !root.isClosing ? 1 : 0.7
Keys.onEscapePressed: closeDialog()
Behavior on opacity {
Anim {}
}
Behavior on scale {
Anim {}
}
ParallelAnimation {
running: root.isClosing
onFinished: {
if (root.isClosing) {
root.session.network.showPasswordDialog = false;
root.isClosing = false;
}
}
Anim {
target: dialog
property: "opacity"
to: 0
}
Anim {
target: dialog
property: "scale"
to: 0.7
}
}
ColumnLayout {
id: content
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Tokens.padding.large
spacing: Tokens.spacing.medium
MaterialIcon {
Layout.alignment: Qt.AlignHCenter
text: "lock"
fontStyle: Tokens.font.icon.builders.extraLarge.scale(2).build()
}
StyledText {
Layout.alignment: Qt.AlignHCenter
text: qsTr("Enter password")
font: Tokens.font.body.builders.large.weight(Font.Medium).build()
}
StyledText {
Layout.alignment: Qt.AlignHCenter
text: root.network ? qsTr("Network: %1").arg(root.network.ssid) : ""
color: Colours.palette.m3outline
font: Tokens.font.body.small
}
StyledText {
id: statusText
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: Tokens.spacing.small
visible: connectButton.connecting || connectButton.hasError
text: {
if (connectButton.hasError) {
return qsTr("Connection failed. Please check your password and try again.");
}
if (connectButton.connecting) {
return qsTr("Connecting...");
}
return "";
}
color: connectButton.hasError ? Colours.palette.m3error : Colours.palette.m3onSurfaceVariant
font: Tokens.font.body.builders.small.weight(Font.Normal).build()
wrapMode: Text.WordWrap
Layout.maximumWidth: parent.width - Tokens.padding.extraLargeIncreased
}
Item {
id: passwordContainer
property string passwordBuffer: ""
Layout.topMargin: Tokens.spacing.largeIncreased
Layout.fillWidth: true
implicitHeight: Math.max(48, charList.implicitHeight + Tokens.padding.medium * 2)
focus: true
Keys.onPressed: event => {
if (!activeFocus) {
forceActiveFocus();
}
if (event.key === Qt.Key_Escape) {
event.accepted = false;
closeDialog();
}
if (connectButton.hasError && event.text && event.text.length > 0) {
connectButton.hasError = false;
}
if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) {
if (connectButton.enabled) {
connectButton.clicked();
}
event.accepted = true;
} else if (event.key === Qt.Key_Backspace) {
if (event.modifiers & Qt.ControlModifier) {
passwordBuffer = "";
} else {
passwordBuffer = passwordBuffer.slice(0, -1);
}
event.accepted = true;
} else if (event.text && event.text.length > 0) {
if (event.key === Qt.Key_Tab) {
event.accepted = false;
return;
}
passwordBuffer += event.text;
event.accepted = true;
}
}
Connections {
function onShowPasswordDialogChanged(): void {
if (root.session.network.showPasswordDialog) {
Qt.callLater(() => {
passwordContainer.forceActiveFocus();
passwordContainer.passwordBuffer = "";
connectButton.hasError = false;
});
}
}
target: root.session.network
}
Connections {
function onVisibleChanged(): void {
if (root.visible) {
Qt.callLater(() => {
passwordContainer.forceActiveFocus();
});
}
}
target: root
}
StyledRect {
anchors.fill: parent
radius: Tokens.rounding.large
color: passwordContainer.activeFocus ? Qt.lighter(Colours.tPalette.m3surfaceContainer, 1.05) : Colours.tPalette.m3surfaceContainer
border.width: passwordContainer.activeFocus || connectButton.hasError ? 4 : (root.visible ? 1 : 0)
border.color: {
if (connectButton.hasError) {
return Colours.palette.m3error;
}
if (passwordContainer.activeFocus) {
return Colours.palette.m3primary;
}
return root.visible ? Colours.palette.m3outline : "transparent";
}
Behavior on border.color {
CAnim {}
}
Behavior on border.width {
CAnim {}
}
Behavior on color {
CAnim {}
}
}
StateLayer {
onClicked: {
passwordContainer.forceActiveFocus();
}
hoverEnabled: false
cursorShape: Qt.IBeamCursor
}
StyledText {
id: placeholder
anchors.centerIn: parent
text: qsTr("Password")
color: Colours.palette.m3outline
font: Tokens.font.mono.medium
opacity: passwordContainer.passwordBuffer ? 0 : 1
Behavior on opacity {
Anim {}
}
}
ListView {
id: charList
readonly property int fullWidth: count * (implicitHeight + spacing) - spacing
anchors.centerIn: parent
implicitWidth: fullWidth
implicitHeight: Tokens.font.body.medium.pointSize
orientation: Qt.Horizontal
spacing: Tokens.spacing.extraSmall
interactive: false
model: ScriptModel {
values: passwordContainer.passwordBuffer.split("")
}
delegate: StyledRect {
id: ch
implicitWidth: implicitHeight
implicitHeight: charList.implicitHeight
color: Colours.palette.m3onSurface
radius: Tokens.rounding.medium / 2
opacity: 0
scale: 0
Component.onCompleted: {
opacity = 1;
scale = 1;
}
ListView.onRemove: removeAnim.start()
SequentialAnimation {
id: removeAnim
PropertyAction {
target: ch
property: "ListView.delayRemove"
value: true
}
ParallelAnimation {
Anim {
target: ch
property: "opacity"
to: 0
}
Anim {
target: ch
property: "scale"
to: 0.5
}
}
PropertyAction {
target: ch
property: "ListView.delayRemove"
value: false
}
}
Behavior on opacity {
Anim {}
}
Behavior on scale {
Anim {
type: Anim.FastSpatial
}
}
}
Behavior on implicitWidth {
Anim {}
}
}
}
RowLayout {
Layout.topMargin: Tokens.spacing.medium
Layout.fillWidth: true
spacing: Tokens.spacing.medium
TextButton {
id: cancelButton
Layout.fillWidth: true
Layout.minimumHeight: Tokens.font.body.medium.pointSize + Tokens.padding.medium * 2
inactiveColour: Colours.palette.m3secondaryContainer
inactiveOnColour: Colours.palette.m3onSecondaryContainer
text: qsTr("Cancel")
onClicked: root.closeDialog()
}
TextButton {
id: connectButton
property bool connecting: false
property bool hasError: false
Layout.fillWidth: true
Layout.minimumHeight: Tokens.font.body.medium.pointSize + Tokens.padding.medium * 2
inactiveColour: Colours.palette.m3primary
inactiveOnColour: Colours.palette.m3onPrimary
text: qsTr("Connect")
enabled: passwordContainer.passwordBuffer.length > 0 && !connecting
onClicked: {
if (!root.network || connecting) {
return;
}
const password = passwordContainer.passwordBuffer;
if (!password || password.length === 0) {
return;
}
hasError = false;
connecting = true;
enabled = false;
text = qsTr("Connecting...");
NetworkConnection.connectWithPassword(root.network, password, result => {
if (result && result.success) {} else if (result && result.needsPassword) {
connectionMonitor.stop();
connecting = false;
hasError = true;
enabled = true;
text = qsTr("Connect");
passwordContainer.passwordBuffer = "";
if (root.network && root.network.ssid) {
Nmcli.forgetNetwork(root.network.ssid);
}
} else {
connectionMonitor.stop();
connecting = false;
hasError = true;
enabled = true;
text = qsTr("Connect");
passwordContainer.passwordBuffer = "";
if (root.network && root.network.ssid) {
Nmcli.forgetNetwork(root.network.ssid);
}
}
});
connectionMonitor.start();
}
}
}
}
}
Timer {
id: connectionMonitor
property int repeatCount: 0
interval: 1000
repeat: true
triggeredOnStart: false
onTriggered: {
repeatCount++;
checkConnectionStatus();
}
onRunningChanged: {
if (!running) {
repeatCount = 0;
}
}
}
Timer {
id: connectionSuccessTimer
interval: 500
onTriggered: {
if (root.visible && Nmcli.active && Nmcli.active.ssid) {
const stillConnected = Nmcli.active.ssid.toLowerCase().trim() === root.network.ssid.toLowerCase().trim();
if (stillConnected) {
connectionMonitor.stop();
connectButton.connecting = false;
connectButton.text = qsTr("Connect");
closeDialog();
}
}
}
}
Connections {
function onActiveChanged() {
if (root.visible) {
checkConnectionStatus();
}
}
function onConnectionFailed(ssid: string) {
if (root.visible && root.network && root.network.ssid === ssid && connectButton.connecting) {
connectionMonitor.stop();
connectButton.connecting = false;
connectButton.hasError = true;
connectButton.enabled = true;
connectButton.text = qsTr("Connect");
passwordContainer.passwordBuffer = "";
Nmcli.forgetNetwork(ssid);
}
}
target: Nmcli
}
}

View file

@ -1,73 +0,0 @@
pragma ComponentBehavior: Bound
import ".."
import "../components"
import QtQuick
import QtQuick.Layouts
import Caelestia.Config
import qs.components
import qs.components.controls
import qs.components.effects
import qs.services
ColumnLayout {
id: root
required property Session session
spacing: Tokens.spacing.medium
SettingsHeader {
icon: "wifi"
title: qsTr("Network settings")
}
SectionHeader {
Layout.topMargin: Tokens.spacing.largeIncreased
title: qsTr("WiFi status")
description: qsTr("General WiFi settings")
}
SectionContainer {
ToggleRow {
label: qsTr("WiFi enabled")
checked: Nmcli.wifiEnabled
toggle.onToggled: {
Nmcli.enableWifi(checked);
}
}
}
SectionHeader {
Layout.topMargin: Tokens.spacing.largeIncreased
title: qsTr("Network information")
description: qsTr("Current network connection")
}
SectionContainer {
contentSpacing: Tokens.spacing.extraSmall
PropertyRow {
label: qsTr("Connected network")
value: Nmcli.active ? Nmcli.active.ssid : qsTr("Not connected")
}
PropertyRow {
showTopMargin: true
label: qsTr("Signal strength")
value: Nmcli.active ? qsTr("%1%").arg(Nmcli.active.strength) : qsTr("N/A")
}
PropertyRow {
showTopMargin: true
label: qsTr("Security")
value: Nmcli.active ? (Nmcli.active.isSecure ? qsTr("Secured") : qsTr("Open")) : qsTr("N/A")
}
PropertyRow {
showTopMargin: true
label: qsTr("Frequency")
value: Nmcli.active ? qsTr("%1 MHz").arg(Nmcli.active.frequency) : qsTr("N/A")
}
}
}

View file

@ -1,425 +0,0 @@
pragma ComponentBehavior: Bound
import ".."
import "../components"
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Widgets
import Caelestia.Config
import qs.components
import qs.components.containers
import qs.components.controls
import qs.components.effects
import qs.services
Item {
id: root
required property Session session
property bool notificationsExpire: GlobalConfig.notifs.expire ?? true
property string notificationsFullscreen: GlobalConfig.notifs.fullscreen ?? "on"
property bool notificationsOpenExpanded: Config.notifs.openExpanded ?? false
property int notificationsDefaultExpireTimeout: GlobalConfig.notifs.defaultExpireTimeout ?? 5000
property int notificationsGroupPreviewNum: Config.notifs.groupPreviewNum ?? 3
property int maxToasts: Config.utilities.maxToasts ?? 4
property string toastsFullscreen: Config.utilities.toasts.fullscreen ?? "off"
property bool chargingChanged: GlobalConfig.utilities.toasts.chargingChanged ?? true
property bool gameModeChanged: GlobalConfig.utilities.toasts.gameModeChanged ?? true
property bool dndChanged: GlobalConfig.utilities.toasts.dndChanged ?? true
property bool audioOutputChanged: GlobalConfig.utilities.toasts.audioOutputChanged ?? true
property bool audioInputChanged: GlobalConfig.utilities.toasts.audioInputChanged ?? true
property bool capsLockChanged: GlobalConfig.utilities.toasts.capsLockChanged ?? true
property bool numLockChanged: GlobalConfig.utilities.toasts.numLockChanged ?? true
property bool kbLayoutChanged: GlobalConfig.utilities.toasts.kbLayoutChanged ?? true
property bool vpnChanged: GlobalConfig.utilities.toasts.vpnChanged ?? true
property bool nowPlaying: GlobalConfig.utilities.toasts.nowPlaying ?? false
function saveConfig(): void {
GlobalConfig.notifs.expire = root.notificationsExpire;
GlobalConfig.notifs.fullscreen = root.notificationsFullscreen;
GlobalConfig.notifs.openExpanded = root.notificationsOpenExpanded;
GlobalConfig.notifs.defaultExpireTimeout = root.notificationsDefaultExpireTimeout;
GlobalConfig.notifs.groupPreviewNum = root.notificationsGroupPreviewNum;
GlobalConfig.utilities.maxToasts = root.maxToasts;
GlobalConfig.utilities.toasts.fullscreen = root.toastsFullscreen;
GlobalConfig.utilities.toasts.chargingChanged = root.chargingChanged;
GlobalConfig.utilities.toasts.gameModeChanged = root.gameModeChanged;
GlobalConfig.utilities.toasts.dndChanged = root.dndChanged;
GlobalConfig.utilities.toasts.audioOutputChanged = root.audioOutputChanged;
GlobalConfig.utilities.toasts.audioInputChanged = root.audioInputChanged;
GlobalConfig.utilities.toasts.capsLockChanged = root.capsLockChanged;
GlobalConfig.utilities.toasts.numLockChanged = root.numLockChanged;
GlobalConfig.utilities.toasts.kbLayoutChanged = root.kbLayoutChanged;
GlobalConfig.utilities.toasts.vpnChanged = root.vpnChanged;
GlobalConfig.utilities.toasts.nowPlaying = root.nowPlaying;
}
anchors.fill: parent
ClippingRectangle {
id: notificationsClippingRect
anchors.fill: parent
anchors.margins: Tokens.padding.medium
anchors.leftMargin: 0
anchors.rightMargin: Tokens.padding.medium
color: "transparent"
radius: notificationsBorder.innerRadius
Loader {
id: notificationsLoader
anchors.fill: parent
anchors.margins: Tokens.padding.large + Tokens.padding.medium
anchors.leftMargin: Tokens.padding.large
anchors.rightMargin: Tokens.padding.large
sourceComponent: notificationsContentComponent
}
}
InnerBorder {
id: notificationsBorder
leftThickness: 0
rightThickness: Tokens.padding.medium
}
Component {
id: notificationsContentComponent
StyledFlickable {
id: notificationsFlickable
flickableDirection: Flickable.VerticalFlick
contentHeight: notificationsLayout.height
StyledScrollBar.vertical: StyledScrollBar {
flickable: notificationsFlickable
}
RowLayout {
id: notificationsLayout
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
spacing: Tokens.spacing.medium
ColumnLayout {
Layout.fillWidth: true
Layout.maximumWidth: 500
Layout.alignment: Qt.AlignTop
spacing: Tokens.spacing.medium
SectionContainer {
Layout.fillWidth: true
alignTop: true
StyledText {
text: qsTr("Notifications")
font: Tokens.font.title.small
}
SplitButtonRow {
id: notificationsFullscreenSelector
function syncActiveItem(): void {
active = root.notificationsFullscreen === "off" ? notificationsFullscreenOffItem : notificationsFullscreenOnItem;
}
label: qsTr("Show in fullscreen")
menuItems: [notificationsFullscreenOffItem, notificationsFullscreenOnItem]
Component.onCompleted: syncActiveItem()
Connections {
function onNotificationsFullscreenChanged(): void {
notificationsFullscreenSelector.syncActiveItem();
}
target: root
}
MenuItem {
id: notificationsFullscreenOffItem
text: qsTr("Off")
icon: "notifications_off"
activeText: qsTr("Off")
onClicked: {
root.notificationsFullscreen = "off";
root.saveConfig();
}
}
MenuItem {
id: notificationsFullscreenOnItem
text: qsTr("On")
icon: "notifications"
activeText: qsTr("On")
onClicked: {
root.notificationsFullscreen = "on";
root.saveConfig();
}
}
}
SwitchRow {
label: qsTr("Expire automatically")
checked: root.notificationsExpire
onToggled: checked => {
root.notificationsExpire = checked;
root.saveConfig();
}
}
SwitchRow {
label: qsTr("Open expanded")
checked: root.notificationsOpenExpanded
onToggled: checked => {
root.notificationsOpenExpanded = checked;
root.saveConfig();
}
}
SpinBoxRow {
label: qsTr("Default timeout")
value: root.notificationsDefaultExpireTimeout
min: 1000
max: 60000
step: 500
onValueModified: value => {
root.notificationsDefaultExpireTimeout = value;
root.saveConfig();
}
}
SpinBoxRow {
label: qsTr("Group preview count")
value: root.notificationsGroupPreviewNum
min: 1
max: 10
step: 1
onValueModified: value => {
root.notificationsGroupPreviewNum = value;
root.saveConfig();
}
}
}
}
ColumnLayout {
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop
spacing: Tokens.spacing.medium
SectionContainer {
Layout.fillWidth: true
alignTop: true
StyledText {
text: qsTr("Toast settings")
font: Tokens.font.title.small
}
SplitButtonRow {
id: toastFullscreenSelector
function syncActiveItem(): void {
if (root.toastsFullscreen === "all") {
active = toastFullscreenAllItem;
return;
}
if (root.toastsFullscreen === "important") {
active = toastFullscreenImportantItem;
return;
}
active = toastFullscreenOffItem;
}
Layout.fillWidth: true
z: expanded ? 100 : 0
label: qsTr("Show in fullscreen")
menuItems: [toastFullscreenOffItem, toastFullscreenImportantItem, toastFullscreenAllItem]
Component.onCompleted: syncActiveItem()
Connections {
function onToastsFullscreenChanged(): void {
toastFullscreenSelector.syncActiveItem();
}
target: root
}
MenuItem {
id: toastFullscreenOffItem
text: qsTr("Off")
icon: "notifications_off"
activeText: qsTr("Off")
onClicked: {
root.toastsFullscreen = "off";
root.saveConfig();
}
}
MenuItem {
id: toastFullscreenImportantItem
text: qsTr("Important")
icon: "priority_high"
activeText: qsTr("Important")
onClicked: {
root.toastsFullscreen = "important";
root.saveConfig();
}
}
MenuItem {
id: toastFullscreenAllItem
text: qsTr("On")
icon: "notifications"
activeText: qsTr("On")
onClicked: {
root.toastsFullscreen = "all";
root.saveConfig();
}
}
}
SpinBoxRow {
Layout.fillWidth: true
label: qsTr("Visible toasts")
value: root.maxToasts
min: 1
max: 10
step: 1
onValueModified: value => {
root.maxToasts = value;
root.saveConfig();
}
}
GridLayout {
Layout.fillWidth: true
columns: 2
columnSpacing: Tokens.spacing.medium
rowSpacing: Tokens.spacing.medium
SwitchRow {
Layout.fillWidth: true
label: qsTr("Charging changes")
checked: root.chargingChanged
onToggled: checked => {
root.chargingChanged = checked;
root.saveConfig();
}
}
SwitchRow {
Layout.fillWidth: true
label: qsTr("Game mode changes")
checked: root.gameModeChanged
onToggled: checked => {
root.gameModeChanged = checked;
root.saveConfig();
}
}
SwitchRow {
Layout.fillWidth: true
label: qsTr("Do not disturb")
checked: root.dndChanged
onToggled: checked => {
root.dndChanged = checked;
root.saveConfig();
}
}
SwitchRow {
Layout.fillWidth: true
label: qsTr("Audio output changes")
checked: root.audioOutputChanged
onToggled: checked => {
root.audioOutputChanged = checked;
root.saveConfig();
}
}
SwitchRow {
Layout.fillWidth: true
label: qsTr("Audio input changes")
checked: root.audioInputChanged
onToggled: checked => {
root.audioInputChanged = checked;
root.saveConfig();
}
}
SwitchRow {
Layout.fillWidth: true
label: qsTr("Caps lock changes")
checked: root.capsLockChanged
onToggled: checked => {
root.capsLockChanged = checked;
root.saveConfig();
}
}
SwitchRow {
Layout.fillWidth: true
label: qsTr("Num lock changes")
checked: root.numLockChanged
onToggled: checked => {
root.numLockChanged = checked;
root.saveConfig();
}
}
SwitchRow {
Layout.fillWidth: true
label: qsTr("Keyboard layout changes")
checked: root.kbLayoutChanged
onToggled: checked => {
root.kbLayoutChanged = checked;
root.saveConfig();
}
}
SwitchRow {
Layout.fillWidth: true
label: qsTr("VPN changes")
checked: root.vpnChanged
onToggled: checked => {
root.vpnChanged = checked;
root.saveConfig();
}
}
SwitchRow {
Layout.fillWidth: true
label: qsTr("Now playing")
checked: root.nowPlaying
onToggled: checked => {
root.nowPlaying = checked;
root.saveConfig();
}
}
}
}
}
}
}
}
}

View file

@ -1,12 +0,0 @@
import QtQuick
import Quickshell.Bluetooth
QtObject {
id: root
property BluetoothDevice active: null
property BluetoothAdapter currentAdapter: Bluetooth.defaultAdapter
property bool editingAdapterName: false
property bool fabMenuOpen: false
property bool editingDeviceName: false
}

View file

@ -1,7 +0,0 @@
import QtQuick
QtObject {
id: root
property var active: null
}

View file

@ -1,7 +0,0 @@
import QtQuick
QtObject {
id: root
property var active: null
}

View file

@ -1,9 +0,0 @@
import QtQuick
QtObject {
id: root
property var active: null
property bool showPasswordDialog: false
property var pendingNetwork: null
}

View file

@ -1,5 +0,0 @@
import QtQuick
QtObject {
property var active: null
}

View file

@ -1,792 +0,0 @@
pragma ComponentBehavior: Bound
import ".."
import "../components"
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Widgets
import Caelestia.Config
import qs.components
import qs.components.containers
import qs.components.controls
import qs.components.effects
import qs.services
import qs.utils
Item {
id: root
required property Session session
property bool activeWindowCompact: Config.bar.activeWindow.compact ?? false
property bool activeWindowInverted: Config.bar.activeWindow.inverted ?? false
property bool clockShowIcon: Config.bar.clock.showIcon ?? true
property bool clockBackground: Config.bar.clock.background ?? false
property bool clockShowDate: Config.bar.clock.showDate ?? false
property bool persistent: Config.bar.persistent ?? true
property bool showOnHover: Config.bar.showOnHover ?? true
property int dragThreshold: Config.bar.dragThreshold ?? 20
property bool showAudio: Config.bar.status.showAudio ?? true
property bool showMicrophone: Config.bar.status.showMicrophone ?? true
property bool showKbLayout: Config.bar.status.showKbLayout ?? false
property bool showNetwork: Config.bar.status.showNetwork ?? true
property bool showWifi: Config.bar.status.showWifi ?? true
property bool showBluetooth: Config.bar.status.showBluetooth ?? true
property bool showBattery: Config.bar.status.showBattery ?? true
property bool showLockStatus: Config.bar.status.showLockStatus ?? true
property bool trayBackground: Config.bar.tray.background ?? false
property bool trayCompact: Config.bar.tray.compact ?? false
property bool trayRecolour: Config.bar.tray.recolour ?? false
property int workspacesShown: Config.bar.workspaces.shown ?? 5
property bool workspacesActiveIndicator: Config.bar.workspaces.activeIndicator ?? true
property bool workspacesOccupiedBg: Config.bar.workspaces.occupiedBg ?? false
property bool workspacesShowWindows: Config.bar.workspaces.showWindows ?? false
property int workspacesMaxWindowIcons: Config.bar.workspaces.maxWindowIcons ?? 0
property bool workspacesPerMonitor: GlobalConfig.bar.workspaces.perMonitorWorkspaces ?? true
property bool scrollWorkspaces: Config.bar.scrollActions.workspaces ?? true
property bool scrollVolume: Config.bar.scrollActions.volume ?? true
property bool scrollBrightness: Config.bar.scrollActions.brightness ?? true
property bool popoutActiveWindow: Config.bar.popouts.activeWindow ?? true
property bool popoutTray: Config.bar.popouts.tray ?? true
property bool popoutStatusIcons: Config.bar.popouts.statusIcons ?? true
property list<string> monitorNames: Hypr.monitorNames()
property list<string> excludedScreens: Config.bar.excludedScreens ?? []
function saveConfig(entryIndex, entryEnabled) {
GlobalConfig.bar.activeWindow.compact = root.activeWindowCompact;
GlobalConfig.bar.activeWindow.inverted = root.activeWindowInverted;
GlobalConfig.bar.clock.background = root.clockBackground;
GlobalConfig.bar.clock.showDate = root.clockShowDate;
GlobalConfig.bar.clock.showIcon = root.clockShowIcon;
GlobalConfig.bar.persistent = root.persistent;
GlobalConfig.bar.showOnHover = root.showOnHover;
GlobalConfig.bar.dragThreshold = root.dragThreshold;
GlobalConfig.bar.status.showAudio = root.showAudio;
GlobalConfig.bar.status.showMicrophone = root.showMicrophone;
GlobalConfig.bar.status.showKbLayout = root.showKbLayout;
GlobalConfig.bar.status.showNetwork = root.showNetwork;
GlobalConfig.bar.status.showWifi = root.showWifi;
GlobalConfig.bar.status.showBluetooth = root.showBluetooth;
GlobalConfig.bar.status.showBattery = root.showBattery;
GlobalConfig.bar.status.showLockStatus = root.showLockStatus;
GlobalConfig.bar.tray.background = root.trayBackground;
GlobalConfig.bar.tray.compact = root.trayCompact;
GlobalConfig.bar.tray.recolour = root.trayRecolour;
GlobalConfig.bar.workspaces.shown = root.workspacesShown;
GlobalConfig.bar.workspaces.activeIndicator = root.workspacesActiveIndicator;
GlobalConfig.bar.workspaces.occupiedBg = root.workspacesOccupiedBg;
GlobalConfig.bar.workspaces.showWindows = root.workspacesShowWindows;
GlobalConfig.bar.workspaces.maxWindowIcons = root.workspacesMaxWindowIcons;
GlobalConfig.bar.workspaces.perMonitorWorkspaces = root.workspacesPerMonitor;
GlobalConfig.bar.scrollActions.workspaces = root.scrollWorkspaces;
GlobalConfig.bar.scrollActions.volume = root.scrollVolume;
GlobalConfig.bar.scrollActions.brightness = root.scrollBrightness;
GlobalConfig.bar.popouts.activeWindow = root.popoutActiveWindow;
GlobalConfig.bar.popouts.tray = root.popoutTray;
GlobalConfig.bar.popouts.statusIcons = root.popoutStatusIcons;
GlobalConfig.bar.excludedScreens = root.excludedScreens;
const entries = [];
for (let i = 0; i < entriesModel.count; i++) {
const entry = entriesModel.get(i);
let enabled = entry.enabled;
if (entryIndex !== undefined && i === entryIndex) {
enabled = entryEnabled;
}
entries.push({
id: entry.id,
enabled: enabled
});
}
GlobalConfig.bar.entries = entries;
}
anchors.fill: parent
Component.onCompleted: {
if (Config.bar.entries) {
entriesModel.clear();
for (let i = 0; i < Config.bar.entries.length; i++) {
const entry = Config.bar.entries[i];
entriesModel.append({
id: entry.id,
enabled: entry.enabled !== false
});
}
}
}
ListModel {
id: entriesModel
}
ClippingRectangle {
id: taskbarClippingRect
anchors.fill: parent
anchors.margins: Tokens.padding.medium
anchors.leftMargin: 0
anchors.rightMargin: Tokens.padding.medium
radius: taskbarBorder.innerRadius
color: "transparent"
Loader {
id: taskbarLoader
anchors.fill: parent
anchors.margins: Tokens.padding.large + Tokens.padding.medium
anchors.leftMargin: Tokens.padding.large
anchors.rightMargin: Tokens.padding.large
asynchronous: true
sourceComponent: taskbarContentComponent
}
}
InnerBorder {
id: taskbarBorder
leftThickness: 0
rightThickness: Tokens.padding.medium
}
Component {
id: taskbarContentComponent
StyledFlickable {
id: sidebarFlickable
flickableDirection: Flickable.VerticalFlick
contentHeight: sidebarLayout.height
StyledScrollBar.vertical: StyledScrollBar {
flickable: sidebarFlickable
}
ColumnLayout {
id: sidebarLayout
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
spacing: Tokens.spacing.medium
RowLayout {
spacing: Tokens.spacing.medium
StyledText {
text: qsTr("Taskbar")
font: Tokens.font.title.builders.medium.weight(Font.Medium).build()
}
}
SectionContainer {
Layout.fillWidth: true
alignTop: true
StyledText {
text: qsTr("Status Icons")
font: Tokens.font.title.small
}
ConnectedButtonGroup {
rootItem: root
options: [
{
label: qsTr("Speakers"),
propertyName: "showAudio",
onToggled: function (checked) {
root.showAudio = checked;
root.saveConfig();
}
},
{
label: qsTr("Microphone"),
propertyName: "showMicrophone",
onToggled: function (checked) {
root.showMicrophone = checked;
root.saveConfig();
}
},
{
label: qsTr("Keyboard"),
propertyName: "showKbLayout",
onToggled: function (checked) {
root.showKbLayout = checked;
root.saveConfig();
}
},
{
label: qsTr("Network"),
propertyName: "showNetwork",
onToggled: function (checked) {
root.showNetwork = checked;
root.saveConfig();
}
},
{
label: qsTr("Wifi"),
propertyName: "showWifi",
onToggled: function (checked) {
root.showWifi = checked;
root.saveConfig();
}
},
{
label: qsTr("Bluetooth"),
propertyName: "showBluetooth",
onToggled: function (checked) {
root.showBluetooth = checked;
root.saveConfig();
}
},
{
label: qsTr("Battery"),
propertyName: "showBattery",
onToggled: function (checked) {
root.showBattery = checked;
root.saveConfig();
}
},
{
label: qsTr("Capslock"),
propertyName: "showLockStatus",
onToggled: function (checked) {
root.showLockStatus = checked;
root.saveConfig();
}
}
]
}
}
RowLayout {
id: mainRowLayout
Layout.fillWidth: true
spacing: Tokens.spacing.medium
ColumnLayout {
id: leftColumnLayout
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop
spacing: Tokens.spacing.medium
SectionContainer {
Layout.fillWidth: true
alignTop: true
StyledText {
text: qsTr("Workspaces")
font: Tokens.font.title.small
}
StyledRect {
Layout.fillWidth: true
implicitHeight: workspacesShownRow.implicitHeight + Tokens.padding.extraLargeIncreased
radius: Tokens.rounding.large
color: Colours.layer(Colours.palette.m3surfaceContainer, 2)
Behavior on implicitHeight {
Anim {}
}
RowLayout {
id: workspacesShownRow
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("Shown")
}
CustomSpinBox {
min: 1
max: 20
value: root.workspacesShown
onValueModified: value => {
root.workspacesShown = value;
root.saveConfig();
}
}
}
}
StyledRect {
Layout.fillWidth: true
implicitHeight: workspacesActiveIndicatorRow.implicitHeight + Tokens.padding.extraLargeIncreased
radius: Tokens.rounding.large
color: Colours.layer(Colours.palette.m3surfaceContainer, 2)
Behavior on implicitHeight {
Anim {}
}
RowLayout {
id: workspacesActiveIndicatorRow
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("Active indicator")
}
StyledSwitch {
checked: root.workspacesActiveIndicator
onToggled: {
root.workspacesActiveIndicator = checked;
root.saveConfig();
}
}
}
}
StyledRect {
Layout.fillWidth: true
implicitHeight: workspacesOccupiedBgRow.implicitHeight + Tokens.padding.extraLargeIncreased
radius: Tokens.rounding.large
color: Colours.layer(Colours.palette.m3surfaceContainer, 2)
Behavior on implicitHeight {
Anim {}
}
RowLayout {
id: workspacesOccupiedBgRow
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("Occupied background")
}
StyledSwitch {
checked: root.workspacesOccupiedBg
onToggled: {
root.workspacesOccupiedBg = checked;
root.saveConfig();
}
}
}
}
StyledRect {
Layout.fillWidth: true
implicitHeight: workspacesShowWindowsRow.implicitHeight + Tokens.padding.extraLargeIncreased
radius: Tokens.rounding.large
color: Colours.layer(Colours.palette.m3surfaceContainer, 2)
Behavior on implicitHeight {
Anim {}
}
RowLayout {
id: workspacesShowWindowsRow
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("Show windows")
}
StyledSwitch {
checked: root.workspacesShowWindows
onToggled: {
root.workspacesShowWindows = checked;
root.saveConfig();
}
}
}
}
StyledRect {
Layout.fillWidth: true
implicitHeight: workspacesMaxWindowIconsRow.implicitHeight + Tokens.padding.extraLargeIncreased
radius: Tokens.rounding.large
color: Colours.layer(Colours.palette.m3surfaceContainer, 2)
Behavior on implicitHeight {
Anim {}
}
RowLayout {
id: workspacesMaxWindowIconsRow
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("Max window icons")
}
CustomSpinBox {
min: 0
max: 20
value: root.workspacesMaxWindowIcons
onValueModified: value => {
root.workspacesMaxWindowIcons = value;
root.saveConfig();
}
}
}
}
StyledRect {
Layout.fillWidth: true
implicitHeight: workspacesPerMonitorRow.implicitHeight + Tokens.padding.extraLargeIncreased
radius: Tokens.rounding.large
color: Colours.layer(Colours.palette.m3surfaceContainer, 2)
Behavior on implicitHeight {
Anim {}
}
RowLayout {
id: workspacesPerMonitorRow
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("Per monitor workspaces")
}
StyledSwitch {
checked: root.workspacesPerMonitor
onToggled: {
root.workspacesPerMonitor = checked;
root.saveConfig();
}
}
}
}
}
SectionContainer {
Layout.fillWidth: true
alignTop: true
StyledText {
text: qsTr("Scroll Actions")
font: Tokens.font.title.small
}
ConnectedButtonGroup {
rootItem: root
options: [
{
label: qsTr("Workspaces"),
propertyName: "scrollWorkspaces",
onToggled: function (checked) {
root.scrollWorkspaces = checked;
root.saveConfig();
}
},
{
label: qsTr("Volume"),
propertyName: "scrollVolume",
onToggled: function (checked) {
root.scrollVolume = checked;
root.saveConfig();
}
},
{
label: qsTr("Brightness"),
propertyName: "scrollBrightness",
onToggled: function (checked) {
root.scrollBrightness = checked;
root.saveConfig();
}
}
]
}
}
}
ColumnLayout {
id: middleColumnLayout
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop
spacing: Tokens.spacing.medium
SectionContainer {
Layout.fillWidth: true
alignTop: true
StyledText {
text: qsTr("Clock")
font: Tokens.font.title.small
}
SwitchRow {
label: qsTr("Background")
checked: root.clockBackground
onToggled: checked => {
root.clockBackground = checked;
root.saveConfig();
}
}
SwitchRow {
label: qsTr("Show date")
checked: root.clockShowDate
onToggled: checked => {
root.clockShowDate = checked;
root.saveConfig();
}
}
SwitchRow {
label: qsTr("Show clock icon")
checked: root.clockShowIcon
onToggled: checked => {
root.clockShowIcon = checked;
root.saveConfig();
}
}
}
SectionContainer {
Layout.fillWidth: true
alignTop: true
StyledText {
text: qsTr("Bar Behavior")
font: Tokens.font.title.small
}
SwitchRow {
label: qsTr("Persistent")
checked: root.persistent
onToggled: checked => {
root.persistent = checked;
root.saveConfig();
}
}
SwitchRow {
label: qsTr("Show on hover")
checked: root.showOnHover
onToggled: checked => {
root.showOnHover = checked;
root.saveConfig();
}
}
SectionContainer {
contentSpacing: Tokens.spacing.medium
SliderInput {
Layout.fillWidth: true
label: qsTr("Drag threshold")
value: root.dragThreshold
from: 0
to: 100
suffix: "px"
validator: IntValidator {
bottom: 0
top: 100
}
formatValueFunction: val => Math.round(val).toString()
parseValueFunction: text => parseInt(text)
onValueModified: newValue => {
root.dragThreshold = Math.round(newValue);
root.saveConfig();
}
}
}
}
SectionContainer {
Layout.fillWidth: true
alignTop: true
StyledText {
text: qsTr("Active window")
font: Tokens.font.title.small
}
SwitchRow {
label: qsTr("Compact")
checked: root.activeWindowCompact
onToggled: checked => {
root.activeWindowCompact = checked;
root.saveConfig();
}
}
SwitchRow {
label: qsTr("Inverted")
checked: root.activeWindowInverted
onToggled: checked => {
root.activeWindowInverted = checked;
root.saveConfig();
}
}
}
}
ColumnLayout {
id: rightColumnLayout
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop
spacing: Tokens.spacing.medium
SectionContainer {
Layout.fillWidth: true
alignTop: true
StyledText {
text: qsTr("Popouts")
font: Tokens.font.title.small
}
SwitchRow {
label: qsTr("Active window")
checked: root.popoutActiveWindow
onToggled: checked => {
root.popoutActiveWindow = checked;
root.saveConfig();
}
}
SwitchRow {
label: qsTr("Tray")
checked: root.popoutTray
onToggled: checked => {
root.popoutTray = checked;
root.saveConfig();
}
}
SwitchRow {
label: qsTr("Status icons")
checked: root.popoutStatusIcons
onToggled: checked => {
root.popoutStatusIcons = checked;
root.saveConfig();
}
}
}
SectionContainer {
Layout.fillWidth: true
alignTop: true
StyledText {
text: qsTr("Tray Settings")
font: Tokens.font.title.small
}
ConnectedButtonGroup {
rootItem: root
options: [
{
label: qsTr("Background"),
propertyName: "trayBackground",
onToggled: function (checked) {
root.trayBackground = checked;
root.saveConfig();
}
},
{
label: qsTr("Compact"),
propertyName: "trayCompact",
onToggled: function (checked) {
root.trayCompact = checked;
root.saveConfig();
}
},
{
label: qsTr("Recolour"),
propertyName: "trayRecolour",
onToggled: function (checked) {
root.trayRecolour = checked;
root.saveConfig();
}
}
]
}
}
SectionContainer {
Layout.fillWidth: true
alignTop: true
StyledText {
text: qsTr("Monitors")
font: Tokens.font.title.small
}
ConnectedButtonGroup {
rootItem: root
// max 3 options per line
rows: Math.ceil(root.monitorNames.length / 3)
options: root.monitorNames.map(e => ({
label: qsTr(e),
propertyName: `monitor${e}`,
onToggled: function (_) {
// if the given monitor is in the excluded list, it should be added back
let addedBack = excludedScreens.includes(e);
if (addedBack) {
const index = excludedScreens.indexOf(e);
if (index !== -1) {
excludedScreens.splice(index, 1);
}
} else {
if (!excludedScreens.includes(e)) {
excludedScreens.push(e);
}
}
root.saveConfig();
},
state: !Strings.testRegexList(root.excludedScreens, e)
}))
}
}
}
}
}
}
}
}

View file

@ -15,7 +15,6 @@ qml_module(caelestia-config
backgroundconfig.hpp backgroundconfig.hpp
barconfig.hpp barconfig.hpp
borderconfig.hpp borderconfig.hpp
controlcenterconfig.hpp
dashboardconfig.hpp dashboardconfig.hpp
generalconfig.hpp generalconfig.hpp
launcherconfig.hpp launcherconfig.hpp

View file

@ -3,7 +3,6 @@
#include "backgroundconfig.hpp" #include "backgroundconfig.hpp"
#include "barconfig.hpp" #include "barconfig.hpp"
#include "borderconfig.hpp" #include "borderconfig.hpp"
#include "controlcenterconfig.hpp"
#include "dashboardconfig.hpp" #include "dashboardconfig.hpp"
#include "generalconfig.hpp" #include "generalconfig.hpp"
#include "launcherconfig.hpp" #include "launcherconfig.hpp"
@ -41,7 +40,6 @@ GlobalConfig::GlobalConfig(QObject* parent)
, m_bar(new BarConfig(this)) , m_bar(new BarConfig(this))
, m_border(new BorderConfig(this)) , m_border(new BorderConfig(this))
, m_dashboard(new DashboardConfig(this)) , m_dashboard(new DashboardConfig(this))
, m_controlCenter(new ControlCenterConfig(this))
, m_launcher(new LauncherConfig(this)) , m_launcher(new LauncherConfig(this))
, m_lock(new LockConfig(this)) , m_lock(new LockConfig(this))
, m_nexus(new NexusConfig(this)) , m_nexus(new NexusConfig(this))
@ -64,7 +62,6 @@ GlobalConfig::GlobalConfig(GlobalConfig* fallback, const QString& filePath, cons
, m_bar(new BarConfig(this)) , m_bar(new BarConfig(this))
, m_border(new BorderConfig(this)) , m_border(new BorderConfig(this))
, m_dashboard(new DashboardConfig(this)) , m_dashboard(new DashboardConfig(this))
, m_controlCenter(new ControlCenterConfig(this))
, m_launcher(new LauncherConfig(this)) , m_launcher(new LauncherConfig(this))
, m_lock(new LockConfig(this)) , m_lock(new LockConfig(this))
, m_nexus(new NexusConfig(this)) , m_nexus(new NexusConfig(this))

View file

@ -10,7 +10,6 @@ class AppearanceConfig;
class BackgroundConfig; class BackgroundConfig;
class BarConfig; class BarConfig;
class BorderConfig; class BorderConfig;
class ControlCenterConfig;
class DashboardConfig; class DashboardConfig;
class GeneralConfig; class GeneralConfig;
class LauncherConfig; class LauncherConfig;
@ -33,7 +32,6 @@ class GlobalConfig : public RootConfig {
Q_MOC_INCLUDE("backgroundconfig.hpp") Q_MOC_INCLUDE("backgroundconfig.hpp")
Q_MOC_INCLUDE("barconfig.hpp") Q_MOC_INCLUDE("barconfig.hpp")
Q_MOC_INCLUDE("borderconfig.hpp") Q_MOC_INCLUDE("borderconfig.hpp")
Q_MOC_INCLUDE("controlcenterconfig.hpp")
Q_MOC_INCLUDE("dashboardconfig.hpp") Q_MOC_INCLUDE("dashboardconfig.hpp")
Q_MOC_INCLUDE("generalconfig.hpp") Q_MOC_INCLUDE("generalconfig.hpp")
Q_MOC_INCLUDE("launcherconfig.hpp") Q_MOC_INCLUDE("launcherconfig.hpp")
@ -55,7 +53,6 @@ class GlobalConfig : public RootConfig {
CONFIG_SUBOBJECT(BarConfig, bar) CONFIG_SUBOBJECT(BarConfig, bar)
CONFIG_SUBOBJECT(BorderConfig, border) CONFIG_SUBOBJECT(BorderConfig, border)
CONFIG_SUBOBJECT(DashboardConfig, dashboard) CONFIG_SUBOBJECT(DashboardConfig, dashboard)
CONFIG_SUBOBJECT(ControlCenterConfig, controlCenter)
CONFIG_SUBOBJECT(LauncherConfig, launcher) CONFIG_SUBOBJECT(LauncherConfig, launcher)
CONFIG_SUBOBJECT(LockConfig, lock) CONFIG_SUBOBJECT(LockConfig, lock)
CONFIG_SUBOBJECT(NexusConfig, nexus) CONFIG_SUBOBJECT(NexusConfig, nexus)

View file

@ -71,7 +71,6 @@ CONFIG_ATTACHED_GETTER(BackgroundConfig, background)
CONFIG_ATTACHED_GETTER(BarConfig, bar) CONFIG_ATTACHED_GETTER(BarConfig, bar)
CONFIG_ATTACHED_GETTER(BorderConfig, border) CONFIG_ATTACHED_GETTER(BorderConfig, border)
CONFIG_ATTACHED_GETTER(DashboardConfig, dashboard) CONFIG_ATTACHED_GETTER(DashboardConfig, dashboard)
CONFIG_ATTACHED_GETTER(ControlCenterConfig, controlCenter)
CONFIG_ATTACHED_GETTER(LauncherConfig, launcher) CONFIG_ATTACHED_GETTER(LauncherConfig, launcher)
CONFIG_ATTACHED_GETTER(LockConfig, lock) CONFIG_ATTACHED_GETTER(LockConfig, lock)
CONFIG_ATTACHED_GETTER(NexusConfig, nexus) CONFIG_ATTACHED_GETTER(NexusConfig, nexus)

View file

@ -16,7 +16,6 @@ class Config : public QQuickAttachedPropertyPropagator, public QQmlParserStatus
Q_MOC_INCLUDE("backgroundconfig.hpp") Q_MOC_INCLUDE("backgroundconfig.hpp")
Q_MOC_INCLUDE("barconfig.hpp") Q_MOC_INCLUDE("barconfig.hpp")
Q_MOC_INCLUDE("borderconfig.hpp") Q_MOC_INCLUDE("borderconfig.hpp")
Q_MOC_INCLUDE("controlcenterconfig.hpp")
Q_MOC_INCLUDE("dashboardconfig.hpp") Q_MOC_INCLUDE("dashboardconfig.hpp")
Q_MOC_INCLUDE("generalconfig.hpp") Q_MOC_INCLUDE("generalconfig.hpp")
Q_MOC_INCLUDE("launcherconfig.hpp") Q_MOC_INCLUDE("launcherconfig.hpp")
@ -38,7 +37,6 @@ class Config : public QQuickAttachedPropertyPropagator, public QQmlParserStatus
Q_PROPERTY(const caelestia::config::BarConfig* bar READ bar NOTIFY sourceChanged) Q_PROPERTY(const caelestia::config::BarConfig* bar READ bar NOTIFY sourceChanged)
Q_PROPERTY(const caelestia::config::BorderConfig* border READ border NOTIFY sourceChanged) Q_PROPERTY(const caelestia::config::BorderConfig* border READ border NOTIFY sourceChanged)
Q_PROPERTY(const caelestia::config::DashboardConfig* dashboard READ dashboard NOTIFY sourceChanged) 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::LauncherConfig* launcher READ launcher NOTIFY sourceChanged)
Q_PROPERTY(const caelestia::config::LockConfig* lock READ lock 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::NexusConfig* nexus READ nexus NOTIFY sourceChanged)
@ -63,7 +61,6 @@ public:
[[nodiscard]] const BarConfig* bar() const; [[nodiscard]] const BarConfig* bar() const;
[[nodiscard]] const BorderConfig* border() const; [[nodiscard]] const BorderConfig* border() const;
[[nodiscard]] const DashboardConfig* dashboard() const; [[nodiscard]] const DashboardConfig* dashboard() const;
[[nodiscard]] const ControlCenterConfig* controlCenter() const;
[[nodiscard]] const LauncherConfig* launcher() const; [[nodiscard]] const LauncherConfig* launcher() const;
[[nodiscard]] const LockConfig* lock() const; [[nodiscard]] const LockConfig* lock() const;
[[nodiscard]] const NexusConfig* nexus() const; [[nodiscard]] const NexusConfig* nexus() const;

View file

@ -122,7 +122,7 @@ class LauncherConfig : public ConfigObject {
{ u"name"_s, u"Settings"_s }, { u"name"_s, u"Settings"_s },
{ u"icon"_s, u"settings"_s }, { u"icon"_s, u"settings"_s },
{ u"description"_s, u"Configure the shell"_s }, { u"description"_s, u"Configure the shell"_s },
{ u"command"_s, QStringList{ u"caelestia"_s, u"shell"_s, u"controlCenter"_s, u"open"_s } }, { u"command"_s, QStringList{ u"caelestia"_s, u"shell"_s, u"nexus"_s, u"open"_s } },
}), }),
}) })

View file

@ -311,18 +311,6 @@ public:
: ConfigObject(parent) {} : ConfigObject(parent) {}
}; };
class ControlCenterTokens : public ConfigObject {
Q_OBJECT
QML_ANONYMOUS
CONFIG_PROPERTY(qreal, heightMult, 0.7)
CONFIG_PROPERTY(qreal, ratio, 16.0 / 9.0)
public:
explicit ControlCenterTokens(QObject* parent = nullptr)
: ConfigObject(parent) {}
};
class NexusTokens : public ConfigObject { class NexusTokens : public ConfigObject {
Q_OBJECT Q_OBJECT
QML_ANONYMOUS QML_ANONYMOUS
@ -352,7 +340,6 @@ class SizeTokens : public ConfigObject {
CONFIG_SUBOBJECT(UtilitiesTokens, utilities) CONFIG_SUBOBJECT(UtilitiesTokens, utilities)
CONFIG_SUBOBJECT(LockTokens, lock) CONFIG_SUBOBJECT(LockTokens, lock)
CONFIG_SUBOBJECT(WInfoTokens, winfo) CONFIG_SUBOBJECT(WInfoTokens, winfo)
CONFIG_SUBOBJECT(ControlCenterTokens, controlCenter)
CONFIG_SUBOBJECT(NexusTokens, nexus) CONFIG_SUBOBJECT(NexusTokens, nexus)
public: public:
@ -368,7 +355,6 @@ public:
, m_utilities(new UtilitiesTokens(this)) , m_utilities(new UtilitiesTokens(this))
, m_lock(new LockTokens(this)) , m_lock(new LockTokens(this))
, m_winfo(new WInfoTokens(this)) , m_winfo(new WInfoTokens(this))
, m_controlCenter(new ControlCenterTokens(this))
, m_nexus(new NexusTokens(this)) {} , m_nexus(new NexusTokens(this)) {}
}; };