diff options
Diffstat (limited to 'modules/controlcenter/taskbar')
| -rw-r--r-- | modules/controlcenter/taskbar/ConnectedButtonGroup.qml | 170 | ||||
| -rw-r--r-- | modules/controlcenter/taskbar/TaskbarPane.qml | 643 |
2 files changed, 813 insertions, 0 deletions
diff --git a/modules/controlcenter/taskbar/ConnectedButtonGroup.qml b/modules/controlcenter/taskbar/ConnectedButtonGroup.qml new file mode 100644 index 0000000..e35ccfc --- /dev/null +++ b/modules/controlcenter/taskbar/ConnectedButtonGroup.qml @@ -0,0 +1,170 @@ +import ".." +import qs.components +import qs.components.controls +import qs.components.effects +import qs.services +import qs.config +import QtQuick +import QtQuick.Layouts + +StyledRect { + id: root + + property var options: [] // Array of {label: string, propertyName: string, onToggled: function} + property var rootItem: null // The root item that contains the properties we want to bind to + property string title: "" // Optional title text + + Layout.fillWidth: true + implicitHeight: layout.implicitHeight + Appearance.padding.large * 2 + radius: Appearance.rounding.normal + color: Colours.layer(Colours.palette.m3surfaceContainer, 2) + clip: true + + Behavior on implicitHeight { + Anim {} + } + + ColumnLayout { + id: layout + + anchors.fill: parent + anchors.margins: Appearance.padding.large + spacing: Appearance.spacing.normal + + StyledText { + visible: root.title !== "" + text: root.title + font.pointSize: Appearance.font.size.normal + } + + RowLayout { + id: buttonRow + Layout.alignment: Qt.AlignHCenter + spacing: Appearance.spacing.small + + Repeater { + id: repeater + model: root.options + + delegate: TextButton { + id: button + required property int index + required property var modelData + + Layout.fillWidth: true + text: modelData.label + + property bool isChecked: false + + // Initialize from root property + Component.onCompleted: { + if (root.rootItem && modelData.propertyName) { + isChecked = root.rootItem[modelData.propertyName]; + } + } + + checked: isChecked + toggle: false + type: TextButton.Tonal + + // Listen for property changes on rootItem + Connections { + target: root.rootItem + enabled: root.rootItem !== null && modelData.propertyName !== undefined + + function onShowAudioChanged() { + if (modelData.propertyName === "showAudio") { + button.isChecked = root.rootItem.showAudio; + } + } + + function onShowMicrophoneChanged() { + if (modelData.propertyName === "showMicrophone") { + button.isChecked = root.rootItem.showMicrophone; + } + } + + function onShowKbLayoutChanged() { + if (modelData.propertyName === "showKbLayout") { + button.isChecked = root.rootItem.showKbLayout; + } + } + + function onShowNetworkChanged() { + if (modelData.propertyName === "showNetwork") { + button.isChecked = root.rootItem.showNetwork; + } + } + + function onShowBluetoothChanged() { + if (modelData.propertyName === "showBluetooth") { + button.isChecked = root.rootItem.showBluetooth; + } + } + + function onShowBatteryChanged() { + if (modelData.propertyName === "showBattery") { + button.isChecked = root.rootItem.showBattery; + } + } + + function onShowLockStatusChanged() { + if (modelData.propertyName === "showLockStatus") { + button.isChecked = root.rootItem.showLockStatus; + } + } + + function onTrayBackgroundChanged() { + if (modelData.propertyName === "trayBackground") { + button.isChecked = root.rootItem.trayBackground; + } + } + + function onTrayCompactChanged() { + if (modelData.propertyName === "trayCompact") { + button.isChecked = root.rootItem.trayCompact; + } + } + + function onTrayRecolourChanged() { + if (modelData.propertyName === "trayRecolour") { + button.isChecked = root.rootItem.trayRecolour; + } + } + } + + // Match utilities Toggles radius styling + // Each button has full rounding (not connected) since they have spacing + radius: stateLayer.pressed ? Appearance.rounding.small / 2 : internalChecked ? Appearance.rounding.small : Appearance.rounding.normal + + // Match utilities Toggles inactive color + inactiveColour: Colours.layer(Colours.palette.m3surfaceContainerHighest, 2) + + // Adjust width similar to utilities toggles + Layout.preferredWidth: implicitWidth + (stateLayer.pressed ? Appearance.padding.large : internalChecked ? Appearance.padding.smaller : 0) + + onClicked: { + if (modelData.onToggled) { + modelData.onToggled(!checked); + } + } + + Behavior on Layout.preferredWidth { + Anim { + duration: Appearance.anim.durations.expressiveFastSpatial + easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial + } + } + + Behavior on radius { + Anim { + duration: Appearance.anim.durations.expressiveFastSpatial + easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial + } + } + } + } + } + } +} + diff --git a/modules/controlcenter/taskbar/TaskbarPane.qml b/modules/controlcenter/taskbar/TaskbarPane.qml new file mode 100644 index 0000000..18d5304 --- /dev/null +++ b/modules/controlcenter/taskbar/TaskbarPane.qml @@ -0,0 +1,643 @@ +pragma ComponentBehavior: Bound + +import ".." +import qs.components +import qs.components.controls +import qs.components.effects +import qs.components.containers +import qs.services +import qs.config +import qs.utils +import Quickshell +import Quickshell.Widgets +import QtQuick +import QtQuick.Layouts + +Item { + id: root + + required property Session session + + // Clock + property bool clockShowIcon: Config.bar.clock.showIcon ?? true + + // Bar Behavior + property bool persistent: Config.bar.persistent ?? true + property bool showOnHover: Config.bar.showOnHover ?? true + property int dragThreshold: Config.bar.dragThreshold ?? 20 + + // Status Icons + 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 showBluetooth: Config.bar.status.showBluetooth ?? true + property bool showBattery: Config.bar.status.showBattery ?? true + property bool showLockStatus: Config.bar.status.showLockStatus ?? true + + // Tray Settings + property bool trayBackground: Config.bar.tray.background ?? false + property bool trayCompact: Config.bar.tray.compact ?? false + property bool trayRecolour: Config.bar.tray.recolour ?? false + + // Workspaces + 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 bool workspacesPerMonitor: Config.bar.workspaces.perMonitorWorkspaces ?? true + + anchors.fill: parent + + Component.onCompleted: { + // Update entries + 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 + }); + } + } + } + + function saveConfig(entryIndex, entryEnabled) { + // Update clock setting + Config.bar.clock.showIcon = root.clockShowIcon; + + // Update bar behavior + Config.bar.persistent = root.persistent; + Config.bar.showOnHover = root.showOnHover; + Config.bar.dragThreshold = root.dragThreshold; + + // Update status icons + Config.bar.status.showAudio = root.showAudio; + Config.bar.status.showMicrophone = root.showMicrophone; + Config.bar.status.showKbLayout = root.showKbLayout; + Config.bar.status.showNetwork = root.showNetwork; + Config.bar.status.showBluetooth = root.showBluetooth; + Config.bar.status.showBattery = root.showBattery; + Config.bar.status.showLockStatus = root.showLockStatus; + + // Update tray settings + Config.bar.tray.background = root.trayBackground; + Config.bar.tray.compact = root.trayCompact; + Config.bar.tray.recolour = root.trayRecolour; + + // Update workspaces + Config.bar.workspaces.shown = root.workspacesShown; + Config.bar.workspaces.activeIndicator = root.workspacesActiveIndicator; + Config.bar.workspaces.occupiedBg = root.workspacesOccupiedBg; + Config.bar.workspaces.showWindows = root.workspacesShowWindows; + Config.bar.workspaces.perMonitorWorkspaces = root.workspacesPerMonitor; + + // Update entries from the model (same approach as clock - use provided value if available) + const entries = []; + for (let i = 0; i < entriesModel.count; i++) { + const entry = entriesModel.get(i); + // If this is the entry being updated, use the provided value (same as clock toggle reads from switch) + // Otherwise use the value from the model + let enabled = entry.enabled; + if (entryIndex !== undefined && i === entryIndex) { + enabled = entryEnabled; + } + entries.push({ + id: entry.id, + enabled: enabled + }); + } + Config.bar.entries = entries; + + // Persist changes to disk + Config.save(); + } + + ListModel { + id: entriesModel + } + + ClippingRectangle { + id: taskbarClippingRect + anchors.fill: parent + anchors.margins: Appearance.padding.normal + anchors.leftMargin: 0 + anchors.rightMargin: Appearance.padding.normal / 2 + + radius: taskbarBorder.innerRadius + color: "transparent" + + Loader { + id: taskbarLoader + + anchors.fill: parent + anchors.margins: Appearance.padding.large + Appearance.padding.normal + anchors.leftMargin: Appearance.padding.large + anchors.rightMargin: Appearance.padding.large + Appearance.padding.normal / 2 + + asynchronous: true + sourceComponent: taskbarContentComponent + } + } + + InnerBorder { + id: taskbarBorder + leftThickness: 0 + rightThickness: Appearance.padding.normal / 2 + } + + 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: Appearance.spacing.normal + + RowLayout { + spacing: Appearance.spacing.smaller + + StyledText { + text: qsTr("Taskbar") + font.pointSize: Appearance.font.size.large + font.weight: 500 + } + } + + SectionContainer { + Layout.fillWidth: true + alignTop: true + + StyledText { + text: qsTr("Status Icons") + font.pointSize: Appearance.font.size.normal + } + + 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("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 { + Layout.fillWidth: true + spacing: Appearance.spacing.normal + + ColumnLayout { + Layout.fillWidth: true + Layout.alignment: Qt.AlignTop + spacing: Appearance.spacing.small + + SectionContainer { + Layout.fillWidth: true + alignTop: true + + StyledText { + text: qsTr("Workspaces") + font.pointSize: Appearance.font.size.normal + } + + StyledRect { + Layout.fillWidth: true + implicitHeight: workspacesShownRow.implicitHeight + Appearance.padding.large * 2 + radius: Appearance.rounding.normal + 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: Appearance.padding.large + spacing: Appearance.spacing.normal + + 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 + Appearance.padding.large * 2 + radius: Appearance.rounding.normal + 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: Appearance.padding.large + spacing: Appearance.spacing.normal + + 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 + Appearance.padding.large * 2 + radius: Appearance.rounding.normal + 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: Appearance.padding.large + spacing: Appearance.spacing.normal + + 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 + Appearance.padding.large * 2 + radius: Appearance.rounding.normal + 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: Appearance.padding.large + spacing: Appearance.spacing.normal + + StyledText { + Layout.fillWidth: true + text: qsTr("Show windows") + } + + StyledSwitch { + checked: root.workspacesShowWindows + onToggled: { + root.workspacesShowWindows = checked; + root.saveConfig(); + } + } + } + } + + StyledRect { + Layout.fillWidth: true + implicitHeight: workspacesPerMonitorRow.implicitHeight + Appearance.padding.large * 2 + radius: Appearance.rounding.normal + 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: Appearance.padding.large + spacing: Appearance.spacing.normal + + StyledText { + Layout.fillWidth: true + text: qsTr("Per monitor workspaces") + } + + StyledSwitch { + checked: root.workspacesPerMonitor + onToggled: { + root.workspacesPerMonitor = checked; + root.saveConfig(); + } + } + } + } + } + } + + ColumnLayout { + Layout.fillWidth: true + Layout.alignment: Qt.AlignTop + spacing: Appearance.spacing.normal + + SectionContainer { + Layout.fillWidth: true + alignTop: true + + StyledText { + text: qsTr("Clock") + font.pointSize: Appearance.font.size.normal + } + + 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("Tray Settings") + font.pointSize: Appearance.font.size.normal + } + + 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(); + } + } + ] + } + } + } + + ColumnLayout { + Layout.fillWidth: true + Layout.alignment: Qt.AlignTop + spacing: Appearance.spacing.small + + SectionContainer { + Layout.fillWidth: true + alignTop: true + + StyledText { + text: qsTr("Bar Behavior") + font.pointSize: Appearance.font.size.normal + } + + 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: Appearance.spacing.normal + + ColumnLayout { + Layout.fillWidth: true + spacing: Appearance.spacing.small + + RowLayout { + Layout.fillWidth: true + spacing: Appearance.spacing.normal + + StyledText { + text: qsTr("Drag threshold") + font.pointSize: Appearance.font.size.normal + } + + Item { + Layout.fillWidth: true + } + + StyledRect { + Layout.preferredWidth: 70 + implicitHeight: dragThresholdInput.implicitHeight + Appearance.padding.small * 2 + color: dragThresholdInputHover.containsMouse || dragThresholdInput.activeFocus + ? Colours.layer(Colours.palette.m3surfaceContainer, 3) + : Colours.layer(Colours.palette.m3surfaceContainer, 2) + radius: Appearance.rounding.small + border.width: 1 + border.color: dragThresholdInput.activeFocus + ? Colours.palette.m3primary + : Qt.alpha(Colours.palette.m3outline, 0.3) + + Behavior on color { CAnim {} } + Behavior on border.color { CAnim {} } + + MouseArea { + id: dragThresholdInputHover + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.IBeamCursor + acceptedButtons: Qt.NoButton + } + + StyledTextField { + id: dragThresholdInput + anchors.centerIn: parent + width: parent.width - Appearance.padding.normal + horizontalAlignment: TextInput.AlignHCenter + validator: IntValidator { bottom: 0; top: 100 } + + Component.onCompleted: { + text = root.dragThreshold.toString(); + } + + onTextChanged: { + if (activeFocus) { + const val = parseInt(text); + if (!isNaN(val) && val >= 0 && val <= 100) { + root.dragThreshold = val; + root.saveConfig(); + } + } + } + onEditingFinished: { + const val = parseInt(text); + if (isNaN(val) || val < 0 || val > 100) { + text = root.dragThreshold.toString(); + } + } + } + } + + StyledText { + text: "px" + color: Colours.palette.m3outline + font.pointSize: Appearance.font.size.normal + } + } + + StyledSlider { + id: dragThresholdSlider + + Layout.fillWidth: true + implicitHeight: Appearance.padding.normal * 3 + + from: 0 + to: 100 + value: root.dragThreshold + onMoved: { + root.dragThreshold = Math.round(dragThresholdSlider.value); + if (!dragThresholdInput.activeFocus) { + dragThresholdInput.text = Math.round(dragThresholdSlider.value).toString(); + } + root.saveConfig(); + } + } + } + } + } + } + } + + } + } + } +} |