diff --git a/Modules/Notifications/Sidebar/Utils/Cards/Toggles.qml b/Modules/Notifications/Sidebar/Utils/Cards/Toggles.qml index f3ee35e..22971fb 100644 --- a/Modules/Notifications/Sidebar/Utils/Cards/Toggles.qml +++ b/Modules/Notifications/Sidebar/Utils/Cards/Toggles.qml @@ -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 diff --git a/Modules/Notifications/Sidebar/Utils/Content.qml b/Modules/Notifications/Sidebar/Utils/Content.qml index ef15ec8..922eff9 100644 --- a/Modules/Notifications/Sidebar/Utils/Content.qml +++ b/Modules/Notifications/Sidebar/Utils/Content.qml @@ -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 } diff --git a/Modules/Notifications/Sidebar/Utils/Wrapper.qml b/Modules/Notifications/Sidebar/Utils/Wrapper.qml index a829f89..d6303e4 100644 --- a/Modules/Notifications/Sidebar/Utils/Wrapper.qml +++ b/Modules/Notifications/Sidebar/Utils/Wrapper.qml @@ -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 diff --git a/Modules/Settings/Pages/Panels/SidebarPanel.qml b/Modules/Settings/Pages/Panels/SidebarPanel.qml index 9e899fa..e99a36e 100644 --- a/Modules/Settings/Pages/Panels/SidebarPanel.qml +++ b/Modules/Settings/Pages/Panels/SidebarPanel.qml @@ -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 + }); + } + } } } diff --git a/Plugins/ZShell/Config/utilities.hpp b/Plugins/ZShell/Config/utilities.hpp index ae97ed7..4d3fc21 100644 --- a/Plugins/ZShell/Config/utilities.hpp +++ b/Plugins/ZShell/Config/utilities.hpp @@ -1,5 +1,8 @@ #pragma once + #include "configobject.hpp" +#include "configlist.hpp" + #include #include #include @@ -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)