1 Commits
Author SHA1 Message Date
zach 8a83181f11 modular quick toggles 2026-08-19 00:34:49 +02:00
26 changed files with 281 additions and 1450 deletions
+1 -1
View File
@@ -15,7 +15,7 @@ CustomRect {
property color activeOnColor
property bool checked
property real checkedRadius: Tokens.rounding.medium
property real defaultRadius: Math.min(width, height) / 2
property real defaultRadius: Tokens.rounding.large
property color disabledColor: Qt.alpha(Colors.palette.m3onSurface, 0.1)
property color disabledOnColor: Qt.alpha(Colors.palette.m3onSurface, 0.38)
property bool fillWidth
-2
View File
@@ -34,7 +34,6 @@ BusyIndicator {
anchors.fill: parent
bgColor: root.bgColor
fgColor: root.fgColor
hasEndIndicator: false
padding: root.padding
rotation: manager.rotation
startAngle: manager.startFraction * 360
@@ -54,7 +53,6 @@ BusyIndicator {
Anim {
duration: manager.completeEndDuration * Tokens.anim.durations.scale
properties: "opacity,internalStrokeWidth"
type: Anim.DefaultEffects
}
}
-2
View File
@@ -23,7 +23,6 @@ TextFieldBase {
property string leadingIcon
readonly property int leadingOffset: leadingIcon ? leadingIconLoader.width + leadingIconLoader.anchors.leftMargin : 0
property int radius: Tokens.rounding.small
property alias sLayer: stateLayer
readonly property real smallFontScale: smallFontSize / font.pointSize
property int smallFontSize: Tokens.font.size.small
property string supportingText
@@ -146,7 +145,6 @@ TextFieldBase {
sourceComponent: MaterialIcon {
color: Colors.palette.m3onSurfaceVariant
font.pointSize: Tokens.font.size.larger
text: root.leadingIcon
}
}
+1 -1
View File
@@ -236,7 +236,7 @@ Item {
}
}
if (Config.dock.enabled && Config.dock.hoverToReveal && !root.visibilities.dock && !root.visibilities.launcher && root.inBottomPanel(root.panels.dock, x, y))
if (Config.dock.enable && Config.dock.hoverToReveal && !root.visibilities.dock && !root.visibilities.launcher && root.inBottomPanel(root.panels.dock, x, y))
root.visibilities.dock = true;
if (root.geometry.barContains(x, y))
+1 -12
View File
@@ -11,7 +11,6 @@ import ZShell.Blobs
import qs.Daemons
import qs.Components
import qs.Modules.Bar
import qs.Modules.Bar.Popouts.Network as N
import ZShell.Config
import qs.Helpers
import qs.Drawers
@@ -145,17 +144,7 @@ CustomWindow {
HyprlandFocusGrab {
id: focusGrab
active: {
const v = visibilities;
const conf = Config;
if (!N.PopupManager.closed && panels.popouts.hasCurrent)
return true;
if ((v.launcher && conf.launcher.enabled) || (v.sidebar && conf.sidebar.enabled) || (v.resources && conf.dashboard.performance.enabled) || (v.dashboard && conf.dashboard.enabled) || v.settings || (v.clipboard && conf.clipboard.enabled) || (v.dock && conf.dock.enabled))
return true;
if (panels.popouts.hasCurrent && panels.popouts.currentName.startsWith("traymenu") && (!conf.bar.tray.showOnHover || (panels.popouts.current as StackView)?.depth > 1))
return true;
return false;
}
active: visibilities.dock || visibilities.resources || visibilities.launcher || visibilities.sidebar || visibilities.dashboard || visibilities.settings || visibilities.clipboard || (panels.popouts.hasCurrent && panels.popouts.currentName.startsWith("traymenu") && (!Config.bar.tray.showOnHover || (panels.popouts.current as StackView)?.depth > 1))
windows: [root]
onCleared: {
+10
View File
@@ -77,6 +77,16 @@ Singleton {
"99": "thunderstorm"
})
function getAppCategoryIcon(name: string, fallback: string): string {
const categories = DesktopEntries.heuristicLookup(name)?.categories;
if (categories)
for (const [key, value] of Object.entries(categoryIcons))
if (categories.includes(key))
return value;
return fallback;
}
function getAppIcon(name: string, fallback: string): string {
const icon = DesktopEntries.heuristicLookup(name)?.icon;
if (fallback !== "undefined")
-114
View File
@@ -1,114 +0,0 @@
pragma Singleton
import Quickshell
import Quickshell.Networking
import QtQuick
Singleton {
id: root
property bool active: false
readonly property list<Network> connectedNetworks: networks.filter(n => n.connected)
property Network connectingNetwork
readonly property list<NetworkDevice> devices: Networking.devices.values
property Network failedNetwork
readonly property list<Network> knownNetworks: networks.filter(n => n.known && !n.connected)
readonly property list<Network> networks: {
const list = [];
for (const d of wifiDevices) {
for (const n of d.networks.values) {
if (!list.includes(n))
list.push(n);
}
}
return list;
}
property bool scanning: false
readonly property list<Network> unknownNetworks: networks.filter(n => !n.known)
readonly property list<WifiDevice> wifiDevices: devices.filter(d => wifiDevice(d))
readonly property bool wifiEnabled: Networking.wifiEnabled
signal networkConnected
function isSecure(security): bool {
return (security === WifiSecurityType.WpaPsk || security === WifiSecurityType.Wpa2Psk || security === WifiSecurityType.Sae);
}
function requestConnect(network: Network, secret = undefined): void {
connectingNetwork = network;
if (secret === undefined || secret === "") {
network.connect();
} else {
(network as WifiNetwork).connectWithPsk(secret);
}
}
function setScan(value: bool): void {
for (const d of wifiDevices) {
d.scannerEnabled = value;
}
}
function setWifi(value: bool): void {
Networking.wifiEnabled = value;
}
function wifiDevice(dev): bool {
return dev.type === DeviceType.Wifi;
}
onActiveChanged: {
for (const d of wifiDevices)
d.scannerEnabled = active;
}
Timer {
id: flushFailed
interval: 5000
running: root.failedNetwork
onTriggered: {
root.failedNetwork = null;
}
}
Timer {
id: checkConnection
interval: 500
repeat: true
running: root.connectingNetwork
onTriggered: {
var completedNetwork;
switch (root.connectingNetwork.state) {
case ConnectionState.Connecting:
break;
case ConnectionState.Connected:
completedNetwork = root.connectingNetwork;
root.networkConnected();
break;
case ConnectionState.Disconnected:
root.failedNetwork = root.connectingNetwork;
completedNetwork = root.connectingNetwork;
break;
}
if (completedNetwork) {
root.connectingNetwork = null;
}
}
}
Timer {
interval: 10000
running: checkConnection.running
onTriggered: {
root.failedNetwork = root.connectingNetwork;
root.connectingNetwork = null;
}
}
}
+9
View File
@@ -205,6 +205,15 @@ GridLayout {
}
}
DelegateChoice {
roleValue: "network"
delegate: EntryWrapper {
NetworkWidget {
}
}
}
DelegateChoice {
roleValue: "media"
-22
View File
@@ -105,28 +105,6 @@ WidgetBase {
}
}
DelegateChoice {
roleValue: "bluetooth"
delegate: EntryWrapper {
name: "bluetooth"
BluetoothWidget {
}
}
}
DelegateChoice {
roleValue: "network"
delegate: EntryWrapper {
name: "network"
NetworkWidget {
}
}
}
DelegateChoice {
roleValue: "power"
@@ -1,14 +0,0 @@
import QtQuick
import QtQuick.Layouts
import ZShell.Config
import qs.Services
import qs.Components
MaterialIcon {
Layout.alignment: Qt.AlignVCenter
animate: true
color: Colors.palette.m3onSurface // Network.connected ? root.textColor : Colors.palette.m3error
fill: 1
font.pointSize: Tokens.font.size.larger
text: "bluetooth"
}
@@ -1,14 +0,0 @@
import QtQuick
import QtQuick.Layouts
import ZShell.Config
import qs.Services
import qs.Components
MaterialIcon {
Layout.alignment: Qt.AlignVCenter
animate: true
color: Colors.palette.m3onSurface // Network.connected ? root.textColor : Colors.palette.m3error
fill: 1
font.pointSize: Tokens.font.size.larger
text: "android_wifi_4_bar"
}
-385
View File
@@ -1,385 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import QtQml.Models
import Quickshell
import Quickshell.Bluetooth
import ZShell.Components
import ZShell.Config
import qs.Services
import qs.Components
import qs.Helpers
CustomClippingRect {
id: root
readonly property list<var> btConnected: Bluetooth.devices.values.filter(d => d.connected)
implicitHeight: layout.implicitHeight + layout.anchors.margins * 2
implicitWidth: 500
ColumnLayout {
id: layout
anchors.fill: parent
anchors.margins: Tokens.padding.larger
spacing: Tokens.spacing.extraSmall / 2
CustomText {
Layout.rightMargin: Tokens.padding.extraSmall
Layout.topMargin: Tokens.padding.normal
font.pointSize: Tokens.font.size.large
text: qsTr("Bluetooth")
}
Toggle {
checked: Bluetooth.defaultAdapter?.enabled ?? false // qmllint disable unresolved-type
first: true
subtext: qsTr("Toggle bluetooth device state")
text: checked ? qsTr("Enabled") : qsTr("Disabled")
onToggled: {
const adapter = Bluetooth.defaultAdapter; // qmllint disable unresolved-type
if (adapter)
adapter.enabled = checked;
}
}
Toggle {
checked: Bluetooth.defaultAdapter?.discovering ?? false // qmllint disable unresolved-type
last: root.btConnected.length === 0
subtext: qsTr("Enable scanning for new devices")
text: qsTr("Discover")
onToggled: {
const adapter = Bluetooth.defaultAdapter; // qmllint disable unresolved-type
if (adapter)
adapter.discovering = checked;
}
}
ListView {
id: rows
Layout.fillWidth: true
Layout.preferredHeight: contentHeight
interactive: false
section.property: "connected"
spacing: Tokens.spacing.extraSmall / 2
add: Transition {
Anim {
from: 0
property: "opacity"
to: 1
}
}
delegate: DelegateChooser {
role: "isDivider"
DelegateChoice {
roleValue: true
delegate: StatusDivider {
}
}
DelegateChoice {
delegate: BluetoothItem {
list: rows
width: rows.width
}
}
}
displaced: Transition {
Anim {
easing.type: Easing.OutCubic
property: "y"
}
}
model: ScriptModel {
objectProp: "address"
values: {
const byName = (a, b) => a.name.localeCompare(b.name);
return [...[...Bluetooth.devices.values].filter(d => d.connected).sort(byName).slice(0, 5),
{
isDivider: true
},
...[...Bluetooth.devices.values].filter(d => !d.connected).sort(byName).slice(0, 5)];
}
}
move: Transition {
Anim {
easing.type: Easing.OutCubic
property: "y"
}
}
remove: Transition {
Anim {
property: "opacity"
to: 0
}
}
section.delegate: Rectangle {
required property string section
color: "transparent"
height: childrenRect.height
width: ListView.view.width
CustomText {
text: parent.section
}
}
}
}
component BluetoothItem: ConnectedRect {
id: btItem
readonly property int connectedCount: root.btConnected.length
readonly property bool connecting: Network.connectingNetwork === modelData
property int horizontalPadding: Tokens.padding.largeIncreased
readonly property bool inConnectedGroup: index < connectedCount
required property int index
required property ListView list
readonly property bool loading: modelData.state === BluetoothDeviceState.Connecting || modelData.state === BluetoothDeviceState.Disconnecting // qmllint disable unresolved-type
required property BluetoothDevice modelData
property int verticalPadding: Tokens.padding.smaller
first: !inConnectedGroup && index === connectedCount + 1
implicitHeight: rowLayout.implicitHeight + verticalPadding * 2
last: inConnectedGroup ? index === connectedCount - 1 : index === list.count - 1
radius: Tokens.rounding.small
Timer {
interval: 50
running: btItem.ListView.delayRemove
onTriggered: btItem.ListView.delayRemove = false
}
Connections {
function onConnectedChanged(): void {
if (!btItem.modelData.connected)
btItem.ListView.delayRemove = true;
}
function onPairedChanged(): void {
if (btItem.modelData.paired)
btItem.modelData.connect();
}
target: btItem.modelData
}
RowLayout {
id: rowLayout
anchors.fill: parent
anchors.leftMargin: btItem.horizontalPadding
anchors.rightMargin: btItem.horizontalPadding
Column {
id: column
Layout.fillWidth: true
CustomText {
Layout.fillWidth: true
Layout.leftMargin: Tokens.spacing.extraSmall
Layout.rightMargin: Tokens.spacing.extraSmall
elide: Text.ElideRight
text: btItem.modelData.name
}
CustomText {
id: subtext
anchors.left: parent.left
anchors.right: parent.right
color: Colors.palette.m3outline
elide: Text.ElideRight
font.pointSize: Tokens.font.size.small
text: btItem.modelData.connected ? (btItem.modelData.batteryAvailable ? qsTr("%1 battery left").arg(Math.round(btItem.modelData.battery * 100) + "%") : qsTr("Battery status unknown")) : btItem.modelData.address
}
}
Item {
Layout.alignment: Qt.AlignRight
implicitHeight: icon.height
implicitWidth: icon.width
MaterialIcon {
id: icon
font.pointSize: Tokens.font.size.large
opacity: btItem.connecting ? 0 : 1
text: btItem.modelData.batteryAvailable ? Icons.getBatteryIcon(btItem.modelData.battery) : "battery_unknown"
visible: btItem.modelData.state === BluetoothDeviceState.Connected // qmllint disable unresolved-type
Behavior on opacity {
Anim {
}
}
}
Loader {
active: opacity > 0
anchors.fill: parent
opacity: btItem.connecting ? 1 : 0
Behavior on opacity {
Anim {
}
}
sourceComponent: CircularIndicator {
bgColor: "transparent"
running: true
}
}
}
ButtonRow {
spacing: Tokens.spacing.small
IconTextButton {
font.pointSize: Tokens.font.size.small
icon: btItem.modelData.connected ? "link_off" : btItem.modelData.paired ? "link" : "add_link"
text: btItem.modelData.connected ? qsTr("Disconnect") : btItem.modelData.paired ? qsTr("Connect") : qsTr("Pair")
onClicked: {
if (btItem.modelData.connected)
btItem.modelData.disconnect();
else if (btItem.modelData.paired)
btItem.modelData.connect();
else if (!btItem.modelData.paired)
btItem.modelData.pair();
else if (btItem.modelData.pairing)
btItem.modelData.cancelPair();
}
}
}
}
}
component ConnectedRect: CustomRect {
id: bg
property bool first
property bool last
bottomLeftRadius: last ? Tokens.rounding.large : Tokens.rounding.extraSmall
bottomRightRadius: last ? Tokens.rounding.large : Tokens.rounding.extraSmall
color: Colors.tPalette.m3surfaceContainer
topLeftRadius: first ? Tokens.rounding.large : Tokens.rounding.extraSmall
topRightRadius: first ? Tokens.rounding.large : Tokens.rounding.extraSmall
Behavior on bottomLeftRadius {
Anim {
}
}
Behavior on bottomRightRadius {
Anim {
}
}
Behavior on topLeftRadius {
Anim {
}
}
Behavior on topRightRadius {
Anim {
}
}
}
component StatusDivider: CustomText {
bottomPadding: Tokens.spacing.extraSmall
color: Colors.palette.m3onSurfaceVariant
font.pointSize: Tokens.font.size.smaller
text: {
const devices = Bluetooth.devices.values; // qmllint disable unresolved-type
const connectedCount = devices.filter(d => d.connected).length;
let available = qsTr("%1 device%2 available").arg(devices.length - connectedCount).arg(devices.length === 1 ? "" : "s");
return available;
}
topPadding: Tokens.spacing.small
}
component Toggle: CustomSwitch {
id: toggleItem
readonly property alias bg: bg
property alias first: bg.first
property alias last: bg.last
property string subtext
Layout.fillWidth: true
cLayer: 2
horizontalPadding: Tokens.padding.largeIncreased
implicitHeight: Math.max(implicitContentHeight, implicitIndicatorHeight) + verticalPadding * 2
implicitWidth: implicitContentWidth + implicitIndicatorWidth + horizontalPadding * 2
indicator.anchors.right: right
indicator.anchors.rightMargin: toggleItem.horizontalPadding
indicator.anchors.verticalCenter: verticalCenter
verticalPadding: Tokens.padding.normal
background: ConnectedRect {
id: bg
StateLayer {
id: stateLayer
manualPressOverride: toggleItem.pressed
}
}
contentItem: Item {
anchors.left: parent.left
anchors.leftMargin: toggleItem.horizontalPadding
anchors.right: toggleItem.indicator.left
anchors.rightMargin: Tokens.spacing.normal
implicitHeight: column.implicitHeight
implicitWidth: column.implicitWidth
Column {
id: column
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: 0
CustomText {
id: label
anchors.left: parent.left
anchors.right: parent.right
elide: Text.ElideRight
font: {
const f = Qt.font(label.font);
f.pointSize = Tokens.font.size.smaller;
return f;
}
text: toggleItem.text
}
CustomText {
id: subtext
anchors.left: parent.left
anchors.right: parent.right
color: Colors.palette.m3outline
elide: Text.ElideRight
font: {
const f = Qt.font(subtext.font);
f.pointSize = Tokens.font.size.small;
return f;
}
text: toggleItem.subtext
visible: toggleItem.subtext
}
}
}
onPressed: stateLayer.press(stateLayer.mouseX, stateLayer.mouseY)
}
}
-7
View File
@@ -80,13 +80,6 @@ Item {
}
}
Popout {
name: "bluetooth"
sourceComponent: BluetoothPopout {
}
}
Popout {
name: "updates"
@@ -1,71 +0,0 @@
import QtQuick
import ZShell.Config
import qs.Services
import qs.Helpers
import qs.Components
CustomRect {
id: root
readonly property bool closing: PopupManager.closed
readonly property url currentPopup: PopupManager.currentPopup
property bool shouldBeVisible: loader.status === Loader.Ready
color: Qt.alpha(Colors.palette.m3shadow, 0.3)
opacity: shouldBeVisible && !closing ? 1 : 0
visible: opacity > 0
Behavior on opacity {
Anim {
}
}
onVisibleChanged: {
if (!visible) {
PopupManager.requestClose();
PopupManager.currentPopup = "";
}
}
CustomMouseArea {
anchors.fill: parent
hoverEnabled: true
preventStealing: true
propagateComposedEvents: false
onClicked: {
const insideItemWidth = mouseX < loader.x + loader.item.width && mouseX > loader.x;
const insideItemHeight = mouseY < loader.y + loader.item.height && mouseY > loader.y;
if (insideItemHeight && insideItemWidth)
return;
PopupManager.requestClose();
}
}
Loader {
id: loader
anchors.centerIn: parent
Connections {
function onCurrentPopupChanged(): void {
if (PopupManager.currentPopup) {
loader.setSource(PopupManager.currentPopup, PopupManager.currentPopupProps);
} else {
loader.source = null;
}
}
target: PopupManager
}
}
Connections {
function onNetworkConnected(): void {
PopupManager.requestClose();
}
target: Network
}
}
-103
View File
@@ -1,103 +0,0 @@
import Quickshell
import QtQuick
import QtQuick.Layouts
import ZShell.Components
import ZShell.Config
import qs.Services
import qs.Components
import qs.Helpers
CustomClippingRect {
id: root
required property var network
color: Colors.palette.m3surfaceContainer
implicitHeight: layout.implicitHeight + layout.anchors.margins * 2
implicitWidth: layout.implicitWidth + layout.anchors.margins * 2
radius: Tokens.rounding.large
ColumnLayout {
id: layout
anchors.fill: parent
anchors.margins: Tokens.padding.extraLarge
spacing: Tokens.spacing.normal
MaterialIcon {
Layout.alignment: Qt.AlignHCenter
font.pointSize: Tokens.font.size.extraLarge * 2
text: "lock"
}
CustomText {
Layout.alignment: Qt.AlignHCenter
color: Colors.palette.m3onSurfaceVariant
text: qsTr("Connect to %1").arg(root.network.name)
}
CustomTextField {
id: passwordField
Layout.fillWidth: true
Layout.preferredWidth: 400
echoMode: CustomTextField.Password
errorText: qsTr("Failed to connect with given password")
isError: Network.failedNetwork
leadingIcon: "lock"
passwordMaskDelay: 5000
placeholderText: qsTr("Input password")
}
ButtonRow {
id: buttonRow
Layout.fillWidth: true
spacing: Tokens.spacing.smaller
IconTextButton {
fillWidth: true
font.pointSize: Tokens.font.size.normal
icon: "close"
inactiveColor: Colors.layer(Colors.palette.m3surfaceContainerHighest, 2)
inactiveOnColor: Colors.palette.m3onSurfaceVariant
isRound: true
isToggle: false
shapeMorph: true
text: "Cancel"
onClicked: PopupManager.requestClose()
}
IconTextButton {
fillWidth: true
font.pointSize: Tokens.font.size.normal
icon: Network.connectingNetwork ? "" : "check"
inactiveColor: Network.connectingNetwork ? Colors.palette.m3primaryContainer : Network.failedNetwork ? Colors.palette.m3error : Colors.palette.m3primary
inactiveOnColor: Network.connectingNetwork ? Colors.palette.m3onPrimaryContainer : Network.failedNetwork ? Colors.palette.m3onError : Colors.palette.m3onPrimary
isRound: true
isToggle: false
shapeMorph: true
text: Network.connectingNetwork ? "" : "Apply"
onClicked: Network.requestConnect(root.network, passwordField.text)
Loader {
active: opacity > 0
anchors.centerIn: parent
opacity: Network.connectingNetwork ? 1 : 0
Behavior on opacity {
Anim {
}
}
sourceComponent: CircularIndicator {
bgColor: "transparent"
implicitSize: buttonRow.height - Tokens.padding.normal
running: true
}
}
}
}
}
}
@@ -1,22 +0,0 @@
pragma Singleton
import Quickshell
import QtQuick
Singleton {
id: root
property bool closed: true
property url currentPopup: ""
property var currentPopupProps: ({})
function requestClose(): void {
closed = true;
}
function requestOpen(source: url, properties = {}): void {
currentPopupProps = properties;
currentPopup = source;
closed = false;
}
}
+16 -342
View File
@@ -1,362 +1,36 @@
pragma ComponentBehavior: Bound
import Quickshell
import Quickshell.Networking
import QtQuick
import QtQuick.Layouts
import Quickshell
import ZShell.Components
import ZShell.Config
import qs.Services
import qs.Modules.Bar.Popouts.Network
import qs.Components
import qs.Helpers
import ZShell.Config
import qs.Modules
import qs.Helpers as Helpers
CustomClippingRect {
Item {
id: root
required property var wrapper
anchors.horizontalCenter: parent.horizontalCenter
implicitHeight: networkPopContent.height + networkPopContent.anchors.margins * 2
implicitWidth: 500 + 8 * 2
radius: (20 - Tokens.padding.small) * Tokens.rounding.scale
Component.onCompleted: Network.active = true
Component.onDestruction: Network.active = false
ColumnLayout {
id: networkPopContent
id: layout
anchors.left: parent.left
anchors.margins: Tokens.padding.large
anchors.right: parent.right
anchors.top: parent.top
spacing: Tokens.spacing.extraSmall / 2
spacing: 8
CustomText {
Layout.preferredHeight: visible ? implicitHeight : 0
Layout.rightMargin: Tokens.padding.extraSmall
font.pointSize: Tokens.font.size.large
text: qsTr("Wifi")
}
Repeater {
model: Helpers.Network.devices
Toggle {
Layout.preferredHeight: visible ? implicitHeight : 0
checked: Network.wifiEnabled
first: true
last: Network.connectedNetworks.length === 0
subtext: qsTr("Toggle WiFi device state")
text: checked ? qsTr("Enabled") : qsTr("Disabled")
CustomRadioButton {
id: network
onToggled: Network.setWifi(checked)
}
required property NetworkDevice modelData
ListView {
id: rows
Layout.fillWidth: true
Layout.preferredHeight: contentHeight
interactive: false
section.property: "connected"
spacing: Tokens.spacing.extraSmall / 2
add: Transition {
Anim {
from: 0
property: "opacity"
to: 1
}
}
delegate: DelegateChooser {
role: "isDivider"
DelegateChoice {
roleValue: true
delegate: StatusDivider {
}
}
DelegateChoice {
delegate: NetworkItem {
list: rows
width: rows.width
}
}
}
displaced: Transition {
Anim {
easing.type: Easing.OutCubic
property: "y"
}
}
model: ScriptModel {
values: {
const byStrengthName = (a, b) => b.signalStrength - a.signalStrength || a.name.localeCompare(b.name);
const cNetworks = [...Network.connectedNetworks];
const kNetworks = [...Network.knownNetworks];
const uNetworks = [...Network.unknownNetworks];
return [...cNetworks.sort(byStrengthName), ...(kNetworks.length > 0 ? [
{
isDivider: true,
type: "known"
},
...kNetworks.sort(byStrengthName)] : []), ...(uNetworks.length > 0 ? [
{
isDivider: true,
type: "unknown"
},
...uNetworks.sort(byStrengthName)] : [])];
}
}
move: Transition {
Anim {
easing.type: Easing.OutCubic
property: "y"
}
}
remove: Transition {
Anim {
property: "opacity"
to: 0
}
}
section.delegate: Rectangle {
required property string section
color: "transparent"
height: childrenRect.height
width: ListView.view.width
CustomText {
text: parent.section
}
checked: Helpers.Network.activeDevice?.name === modelData.name
text: modelData.description
visible: modelData.name !== "lo"
}
}
}
NetworkOverlay {
anchors.fill: parent
}
component ConnectedRect: CustomRect {
id: bg
property bool first
property bool last
bottomLeftRadius: last ? Tokens.rounding.large : Tokens.rounding.extraSmall
bottomRightRadius: last ? Tokens.rounding.large : Tokens.rounding.extraSmall
color: Colors.tPalette.m3surfaceContainer
topLeftRadius: first ? Tokens.rounding.large : Tokens.rounding.extraSmall
topRightRadius: first ? Tokens.rounding.large : Tokens.rounding.extraSmall
}
component NetworkItem: ConnectedRect {
id: networkItem
readonly property int connectedCount: Network.connectedNetworks.length
readonly property bool connecting: Network.connectingNetwork === modelData
property int horizontalPadding: Tokens.padding.largeIncreased
readonly property bool inConnectedGroup: index < connectedCount
// sectioning first / last item in list reminder
// if index === network.connectedNetworks.length - 1
// if index === network.connectedNetworks.length - 1 + network.knownNetworks.length - 2
// if index === network.connectedNetworks.length - 1 + network.knownNetworks.length
// list.model.values.length - 1
required property int index
readonly property bool isSecure: Network.isSecure(modelData.security)
required property ListView list
required property var modelData
property int verticalPadding: Tokens.padding.smaller
Layout.fillWidth: true
first: !inConnectedGroup && index === connectedCount + 1
implicitHeight: rowLayout.implicitHeight + verticalPadding * 2
last: inConnectedGroup ? index === connectedCount - 1 : index === list.count - 1
radius: Tokens.rounding.small
RowLayout {
id: rowLayout
Layout.preferredHeight: visible ? rowLayout.implicitHeight + Tokens.padding.smaller * 2 : 0
anchors.fill: parent
anchors.leftMargin: networkItem.horizontalPadding
anchors.rightMargin: networkItem.horizontalPadding
Column {
id: column
Layout.fillWidth: true
CustomText {
Layout.fillWidth: true
Layout.leftMargin: Tokens.spacing.extraSmall
Layout.rightMargin: Tokens.spacing.extraSmall
color: networkItem.modelData.active ? Colors.palette.m3primary : Colors.palette.m3onSurface
elide: Text.ElideRight
text: networkItem.modelData.name
}
CustomText {
id: subtext
anchors.left: parent.left
anchors.right: parent.right
color: Colors.palette.m3outline
elide: Text.ElideRight
font.pointSize: Tokens.font.size.small
text: networkItem.modelData.connected ? qsTr("Connected: %1 strength").arg(Math.round(networkItem.modelData.signalStrength * 100) + "%") : qsTr("%1 strength").arg(Math.round(networkItem.modelData.signalStrength * 100) + "%")
}
}
Item {
Layout.alignment: Qt.AlignRight
implicitHeight: icon.height
implicitWidth: icon.width
MaterialIcon {
id: icon
color: networkItem.modelData.active ? Colors.palette.m3primary : Colors.palette.m3onSurfaceVariant
font.pointSize: Tokens.font.size.large
opacity: networkItem.connecting ? 0 : 1
text: Icons.getNetworkIcon(networkItem.modelData.signalStrength * 100, networkItem.isSecure)
Behavior on opacity {
Anim {
}
}
}
Loader {
active: opacity > 0
anchors.fill: parent
opacity: networkItem.connecting ? 1 : 0
Behavior on opacity {
Anim {
}
}
sourceComponent: CircularIndicator {
bgColor: "transparent"
running: true
}
}
}
}
StateLayer {
color: networkItem.modelData.active ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
onClicked: {
if (networkItem.isSecure && !networkItem.known) {
PopupManager.requestOpen(Qt.resolvedUrl("Network/PSKOverlay.qml"), {
network: networkItem.modelData
});
} else {
Network.requestConnect(networkItem.modelData);
}
}
}
}
component StatusDivider: CustomText {
required property var modelData
bottomPadding: Tokens.spacing.extraSmall
color: Colors.palette.m3onSurfaceVariant
font.pointSize: Tokens.font.size.smaller
text: {
if (modelData.type === "known") {
const count = Network.knownNetworks.filter(n => !n.connected).length;
return qsTr("%1 known network%2").arg(count).arg(count === 1 ? "" : "s");
}
if (modelData.type === "unknown") {
const count = Network.unknownNetworks.filter(n => !n.connected).length;
return qsTr("%1 network%2 available").arg(count).arg(count === 1 ? "" : "s");
}
return qsTr("object unknown");
}
topPadding: Tokens.spacing.small
}
component Toggle: CustomSwitch {
id: toggleItem
readonly property alias bg: bg
property alias first: bg.first
property alias last: bg.last
property string subtext
Layout.fillWidth: true
cLayer: 2
horizontalPadding: Tokens.padding.largeIncreased
implicitHeight: Math.max(implicitContentHeight, implicitIndicatorHeight) + verticalPadding * 2
implicitWidth: implicitContentWidth + implicitIndicatorWidth + horizontalPadding * 2
indicator.anchors.right: right
indicator.anchors.rightMargin: toggleItem.horizontalPadding
indicator.anchors.verticalCenter: verticalCenter
verticalPadding: Tokens.padding.normal
background: ConnectedRect {
id: bg
StateLayer {
id: stateLayer
manualPressOverride: toggleItem.pressed
}
}
contentItem: Item {
anchors.left: parent.left
anchors.leftMargin: toggleItem.horizontalPadding
anchors.right: toggleItem.indicator.left
anchors.rightMargin: Tokens.spacing.normal
implicitHeight: column.implicitHeight
implicitWidth: column.implicitWidth
Column {
id: column
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: 0
CustomText {
id: label
anchors.left: parent.left
anchors.right: parent.right
elide: Text.ElideRight
font: {
const f = Qt.font(label.font);
f.pointSize = Tokens.font.size.smaller;
return f;
}
text: toggleItem.text
}
CustomText {
id: subtext
anchors.left: parent.left
anchors.right: parent.right
color: Colors.palette.m3outline
elide: Text.ElideRight
font: {
const f = Qt.font(subtext.font);
f.pointSize = Tokens.font.size.small;
return f;
}
text: toggleItem.subtext
visible: toggleItem.subtext
}
}
}
onPressed: stateLayer.press(stateLayer.mouseX, stateLayer.mouseY)
}
}
+1 -1
View File
@@ -12,7 +12,7 @@ Item {
property real offsetScale: shouldBeActive ? 0 : 1
required property var panels
required property ShellScreen screen
readonly property bool shouldBeActive: visibilities.dock && Config.dock.enabled
readonly property bool shouldBeActive: visibilities.dock && Config.dock.enable
required property PersistentProperties visibilities
anchors.bottomMargin: (-implicitHeight - 5) * offsetScale
@@ -1,101 +1,184 @@
import Quickshell.Bluetooth
import Quickshell.Networking as QSNetwork
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Bluetooth
import Quickshell.Networking as QSNetwork
import ZShell.Components
import qs.Components
import ZShell.Config
import qs.Components
import qs.Services
import qs.Helpers
import qs.Daemons
import qs.Services
import qs.Modules.Settings
import qs.Modules.Bar.Popouts as BarPopouts
CustomRect {
id: root
required property Item popouts
required property var visibilities
readonly property bool needExtraRow: quickToggles.length > 6
required property BarPopouts.Wrapper popouts
readonly property var quickToggles: {
const seenIds = new Set();
return Config.utilities.quickToggles.values.filter(item => {
if (!item.enabled)
return false;
if (seenIds.has(item.id)) {
return false;
}
if (item.id === "bluetooth" && !Bluetooth.defaultAdapter)
return false;
if (item.id === "wifi" && !QSNetwork.Networking.devices.values.some(n => n.type === QSNetwork.DeviceType.Wifi))
return false;
seenIds.add(item.id);
return true;
});
}
readonly property int splitIndex: Math.ceil(quickToggles.length / 2)
required property PersistentProperties visibilities
Layout.fillWidth: true
color: Colors.tPalette.m3surfaceContainer
implicitHeight: layout.implicitHeight + 18 * 2
implicitHeight: layout.implicitHeight + Tokens.padding.extraLargeIncreased
radius: Tokens.rounding.smallest
ButtonRow {
ColumnLayout {
id: layout
anchors.fill: parent
anchors.margins: 18
spacing: Tokens.spacing.extraSmall
anchors.margins: Tokens.padding.large
spacing: Tokens.spacing.normal
Toggle {
checked: Network.wifiEnabled
icon: Network.wifiEnabled ? "wifi" : "wifi_off"
visible: QSNetwork.Networking.devices.values.some(n => n.type === QSNetwork.DeviceType.Wifi)
onClicked: Network.toggleWifi()
CustomText {
font.pointSize: Tokens.font.size.normal
text: qsTr("Quick Toggles")
}
Toggle {
id: toggle
checked: !NotifServer.dnd
icon: NotifServer.dnd ? "notifications_off" : "notifications"
onClicked: NotifServer.dnd = !NotifServer.dnd
QuickToggleRow {
model: root.needExtraRow ? root.quickToggles.slice(0, root.splitIndex) : root.quickToggles
}
Toggle {
checked: !Audio.sourceMuted
icon: Audio.sourceMuted ? "mic_off" : "mic"
onClicked: {
const audio = Audio.source?.audio;
if (audio)
audio.muted = !audio.muted;
}
}
Toggle {
checked: !Audio.muted
icon: Audio.muted ? "volume_off" : "volume_up"
onClicked: {
const audio = Audio.sink?.audio;
if (audio)
audio.muted = !audio.muted;
}
}
Toggle {
checked: Bluetooth.defaultAdapter?.enabled ?? false
icon: Bluetooth.defaultAdapter?.enabled ? "bluetooth" : "bluetooth_disabled"
visible: Bluetooth.defaultAdapter ?? false
onClicked: {
const adapter = Bluetooth.defaultAdapter;
if (adapter)
adapter.enabled = !adapter.enabled;
}
}
Toggle {
checked: GameMode.enabled
icon: GameMode.enabled ? "videogame_asset" : "videogame_asset_off"
onClicked: GameMode.enabled = !GameMode.enabled
QuickToggleRow {
model: root.needExtraRow ? root.quickToggles.slice(root.splitIndex) : []
visible: root.needExtraRow
}
}
CustomShortcut {
name: "toggle-dnd"
component QuickToggleRow: ButtonRow {
property alias model: repeater.model
onPressed: {
toggle.clicked();
Layout.fillWidth: true
spacing: Tokens.spacing.small
Repeater {
id: repeater
delegate: DelegateChooser {
role: "id"
DelegateChoice {
roleValue: "wifi"
delegate: Toggle {
checked: Network.wifiEnabled
icon: checked ? "wifi" : "wifi_off"
onClicked: Network.toggleWifi()
}
}
DelegateChoice {
roleValue: "bluetooth"
delegate: Toggle {
checked: Bluetooth.defaultAdapter?.enabled ?? false // qmllint disable unresolved-type
icon: "bluetooth"
onClicked: {
const adapter = Bluetooth.defaultAdapter; // qmllint disable unresolved-type
if (adapter)
adapter.enabled = !adapter.enabled;
}
}
}
DelegateChoice {
roleValue: "mic"
delegate: Toggle {
checked: !Audio.sourceMuted
icon: checked ? "mic" : "mic_off"
onClicked: {
const audio = Audio.source?.audio;
if (audio)
audio.muted = !audio.muted;
}
}
}
DelegateChoice {
roleValue: "audio"
delegate: Toggle {
checked: !Audio.muted
icon: checked ? "volume_up" : "volume_off"
onClicked: {
const audio = Audio.sink?.audio;
if (audio)
audio.muted = !audio.muted;
}
}
}
DelegateChoice {
roleValue: "gameMode"
delegate: Toggle {
checked: GameMode.enabled
icon: "gamepad"
onClicked: GameMode.enabled = !GameMode.enabled
}
}
DelegateChoice {
roleValue: "dnd"
delegate: Toggle {
checked: NotifServer.dnd
icon: "notifications_off"
onClicked: NotifServer.dnd = !NotifServer.dnd
}
}
DelegateChoice {
roleValue: "settings"
delegate: Toggle {
icon: "settings"
inactiveOnColor: Colors.palette.m3onSurfaceVariant
isToggle: false
onClicked: {
root.visibilities.sidebar = false;
root.visibilities.settings = true;
}
}
}
}
}
}
component Toggle: IconButton {
fillWidth: true
inactiveColor: Colors.layer(Colors.palette.m3surfaceContainerHighest, 2)
isRound: true
isToggle: true
shapeMorph: true
@@ -1,13 +1,14 @@
import Quickshell
import QtQuick
import QtQuick.Layouts
import qs.Modules.Bar.Popouts as BarPopouts
import qs.Modules.Notifications.Sidebar.Utils.Cards
import ZShell.Config
Item {
id: root
required property Item popouts
required property BarPopouts.Wrapper popouts
required property PersistentProperties props
required property var visibilities
@@ -30,6 +31,7 @@ Item {
}
Toggles {
Layout.fillWidth: true
popouts: root.popouts
visibilities: root.visibilities
}
@@ -1,14 +1,16 @@
pragma ComponentBehavior: Bound
import qs.Components
import ZShell.Config
import Quickshell
import QtQuick
import Quickshell
import ZShell.Config
import qs.Components
import qs.Modules.Bar.Popouts as BarPopouts
Item {
id: root
required property Item popouts
property real offsetScale: shouldBeActive ? 0 : 1
required property BarPopouts.Wrapper popouts
readonly property PersistentProperties props: PersistentProperties {
property string recordingConfirmDelete
property bool recordingListExpanded: false
@@ -20,13 +22,11 @@ Item {
required property Item sidebar
required property var visibilities
property real offsetScale: shouldBeActive ? 0 : 1
visible: offsetScale < 1
anchors.bottomMargin: (-implicitHeight - 5) * offsetScale
implicitHeight: content.implicitHeight + 8 * 2
implicitWidth: sidebar.width * (1 - sidebar.offsetScale)
opacity: 1 - offsetScale
visible: offsetScale < 1
Behavior on offsetScale {
Anim {
@@ -38,12 +38,11 @@ Item {
Loader {
id: content
active: root.shouldBeActive || root.visible
anchors.left: parent.left
anchors.margins: 8
anchors.top: parent.top
active: root.shouldBeActive || root.visible
sourceComponent: Content {
implicitWidth: root.implicitWidth - 8 * 2
popouts: root.popouts
+2 -6
View File
@@ -2,8 +2,8 @@ pragma Singleton
import QtQuick
import QtQuick.Layouts
import ZShell.Config
import qs.Components
import ZShell.Config
import qs.Modules.Settings.Common
import qs.Modules.Settings.Pages
import qs.Modules.Settings.Pages.Wallpaper
@@ -45,11 +45,7 @@ QtObject {
// Connectivity
Component {
StackPage {
Component {
NetworkPage {
}
}
PlaceholderComp {
}
},
Component {
-240
View File
@@ -1,240 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import ZShell.Config
import qs.Modules.Settings.Common
import qs.Modules.Bar.Popouts.Network
import qs.Components
import qs.Helpers
import qs.Services
PageBase {
id: root
title: qsTr("Network")
ColumnLayout {
id: networkContent
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
spacing: Tokens.spacing.extraSmall / 2
width: root.cappedWidth
Toggle {
Layout.preferredHeight: visible ? implicitHeight : 0
checked: Network.wifiEnabled
first: true
last: connected.count === 0
subtext: qsTr("Toggle WiFi device state")
text: checked ? qsTr("Enabled") : qsTr("Disabled")
onToggled: Network.setWifi(checked)
}
Repeater {
id: connected
visible: Network.connectedNetworks.length > 0
model: ScriptModel {
values: [...Network.connectedNetworks].sort((a, b) => {
return b.signalStrength - a.signalStrength;
})
}
NetworkItem {
first: false
known: true
repeater: connected
}
}
}
component NetworkItem: ConnectedRect {
id: networkItem
readonly property bool connecting: Network.connectingNetwork === modelData
property int horizontalPadding: Tokens.padding.largeIncreased
required property int index
readonly property bool isSecure: Network.isSecure(modelData.security)
required property bool known
required property var modelData
required property Repeater repeater
Layout.fillWidth: true
Layout.preferredHeight: visible ? rowLayout.implicitHeight + Tokens.padding.smaller * 2 : 0
first: index === 0
last: index === (repeater.count - 1)
radius: Tokens.rounding.small
RowLayout {
id: rowLayout
anchors.fill: parent
anchors.leftMargin: networkItem.horizontalPadding
anchors.rightMargin: networkItem.horizontalPadding
Column {
id: column
Layout.fillWidth: true
CustomText {
Layout.fillWidth: true
Layout.leftMargin: Tokens.spacing.extraSmall
Layout.rightMargin: Tokens.spacing.extraSmall
color: networkItem.modelData.active ? Colors.palette.m3primary : Colors.palette.m3onSurface
elide: Text.ElideRight
text: networkItem.modelData.name
}
CustomText {
id: subtext
anchors.left: parent.left
anchors.right: parent.right
color: Colors.palette.m3outline
elide: Text.ElideRight
font.pointSize: Tokens.font.size.small
text: qsTr("%1 strength").arg(Math.round(networkItem.modelData.signalStrength * 100) + "%")
}
}
Item {
Layout.alignment: Qt.AlignRight
implicitHeight: icon.height
implicitWidth: icon.width
MaterialIcon {
id: icon
color: networkItem.modelData.active ? Colors.palette.m3primary : Colors.palette.m3onSurfaceVariant
font.pointSize: Tokens.font.size.large
opacity: networkItem.connecting ? 0 : 1
text: Icons.getNetworkIcon(networkItem.modelData.signalStrength * 100, networkItem.isSecure)
Behavior on opacity {
Anim {
}
}
}
Loader {
active: opacity > 0
anchors.fill: parent
opacity: networkItem.connecting ? 1 : 0
Behavior on opacity {
Anim {
}
}
sourceComponent: CircularIndicator {
bgColor: "transparent"
running: true
}
}
}
}
StateLayer {
color: networkItem.modelData.active ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
onClicked: {
if (networkItem.isSecure && !networkItem.known) {
PopupManager.requestOpen(Qt.resolvedUrl("Network/PSKOverlay.qml"), {
network: networkItem.modelData
});
} else {
Network.requestConnect(networkItem.modelData);
}
}
}
}
component Spacer: Item {
id: spacer
required property Repeater repeater
Layout.preferredHeight: repeater.count > 0 ? Tokens.spacing.extraSmall : 0
}
component Toggle: CustomSwitch {
id: toggleItem
readonly property alias bg: bg
property alias first: bg.first
property alias last: bg.last
property string subtext
Layout.fillWidth: true
cLayer: 2
horizontalPadding: Tokens.padding.largeIncreased
implicitHeight: Math.max(implicitContentHeight, implicitIndicatorHeight) + verticalPadding * 2
implicitWidth: implicitContentWidth + implicitIndicatorWidth + horizontalPadding * 2
indicator.anchors.right: right
indicator.anchors.rightMargin: toggleItem.horizontalPadding
indicator.anchors.verticalCenter: verticalCenter
verticalPadding: Tokens.padding.normal
background: ConnectedRect {
id: bg
StateLayer {
id: stateLayer
manualPressOverride: toggleItem.pressed
}
}
contentItem: Item {
anchors.left: parent.left
anchors.leftMargin: toggleItem.horizontalPadding
anchors.right: toggleItem.indicator.left
anchors.rightMargin: Tokens.spacing.normal
implicitHeight: column.implicitHeight
implicitWidth: column.implicitWidth
Column {
id: column
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: 0
CustomText {
id: label
anchors.left: parent.left
anchors.right: parent.right
elide: Text.ElideRight
font: {
const f = Qt.font(label.font);
f.pointSize = Tokens.font.size.smaller;
return f;
}
text: toggleItem.text
}
CustomText {
id: subtext
anchors.left: parent.left
anchors.right: parent.right
color: Colors.palette.m3outline
elide: Text.ElideRight
font: {
const f = Qt.font(subtext.font);
f.pointSize = Tokens.font.size.small;
return f;
}
text: toggleItem.subtext
visible: toggleItem.subtext
}
}
}
onPressed: stateLayer.press(stateLayer.mouseX, stateLayer.mouseY)
}
}
@@ -8,9 +8,7 @@ PageBase {
readonly property var builtinIcons: ({
audio: qsTr("Speakers"),
microphone: qsTr("Microphone"),
power: qsTr("Battery"),
network: qsTr("Network"),
bluetooth: qsTr("Bluetooth")
power: qsTr("Battery")
})
isSubPage: true
@@ -93,15 +91,5 @@ PageBase {
onToggled: Config.bar.popouts.statusIcons = checked
}
ToggleRow {
checked: Config.bar.popouts.network
last: true
settingAnchor: "bar-status-network-popout"
subtext: qsTr("Show a details popout when hovering the network icon")
text: qsTr("Network popout on hover")
onToggled: Config.bar.popouts.network = checked
}
}
}
@@ -9,6 +9,16 @@ import qs.Modules.Settings.Common
PageBase {
id: root
readonly property var builtinIcons: ({
audio: qsTr("Toggle audio"),
mic: qsTr("Toggle microphone"),
settings: qsTr("Open settings"),
dnd: qsTr("Toggle Do-Not-Disturb"),
gameMode: qsTr("Toggle game mode"),
bluetooth: qsTr("Toggle bluetooth"),
wifi: qsTr("Toggle Wi-Fi")
})
isSubPage: true
title: qsTr("Sidebar")
@@ -44,5 +54,59 @@ PageBase {
onMoved: v => Config.sidebar.dragThreshold = v
}
SectionHeader {
text: qsTr("Quick toggles")
}
ListEditor {
function labelFor(item: var): string {
const prettyName = root.builtinIcons[item.id];
if (prettyName)
return prettyName;
const label = item.id.replace(/([A-Z])/g, " $1");
return label.charAt(0).toUpperCase() + label.slice(1).toLowerCase();
}
function toggledFor(item: var): bool {
return item.enabled;
}
first: true
values: Config.utilities.quickToggles.values
z: 1
onItemMoved: (from, to) => Config.utilities.quickToggles.move(from, to)
onItemRemoved: index => Config.utilities.quickToggles.remove(index)
onItemToggled: (index, checked) => Config.utilities.quickToggles.at(index).enabled = checked
}
DialogSelectButton {
id: addItemContainer
acceptLabel: qsTr("Add")
enabled: Object.keys(model).length > 0
header: qsTr("Add new entry")
icon: "add"
label: qsTr("Add entry")
model: {
const present = new Set(Config.utilities.quickToggles.values.map(item => item.id));
return Object.keys(root.builtinIcons).filter(id => !present.has(id)).map(id => ({
id: id,
label: root.builtinIcons[id]
}));
}
rootParent: root.flickable
onAccepted: {
if (!selectedItem)
return;
Config.utilities.quickToggles.insert({
id: selectedItem,
enabled: true
});
}
}
}
}
+13
View File
@@ -1,5 +1,8 @@
#pragma once
#include "configobject.hpp"
#include "configlist.hpp"
#include <QVariantList>
#include <QStringList>
#include <qqmlintegration.h>
@@ -58,6 +61,16 @@ class Utilities : public ConfigObject {
CONFIG_SUBOBJECT(UtilitiesSizes, sizes)
CONFIG_SUBOBJECT(Toasts, toasts)
CONFIG_SUBOBJECT(Vpn, vpn)
CONFIG_LIST(
EntryList,
quickToggles,
{LIST_ENTRY(dnd, true),
LIST_ENTRY(wifi, true),
LIST_ENTRY(settings, true),
LIST_ENTRY(bluetooth, true),
LIST_ENTRY(mic, true),
LIST_ENTRY(audio, true),
LIST_ENTRY(gameMode, true)})
public:
explicit Utilities(QObject* parent = nullptr)