modular quick toggles

This commit is contained in:
2026-08-19 00:34:49 +02:00
parent eb65e44ac8
commit 8a83181f11
5 changed files with 239 additions and 78 deletions
@@ -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
@@ -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)