diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/arrow-left-filled.svg b/dots/.config/quickshell/ii/assets/icons/fluent/arrow-left-filled.svg new file mode 100644 index 000000000..afb9f5a62 --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/arrow-left-filled.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/arrow-left.svg b/dots/.config/quickshell/ii/assets/icons/fluent/arrow-left.svg new file mode 100644 index 000000000..38709f3e0 --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/arrow-left.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/desktop-speaker-filled.svg b/dots/.config/quickshell/ii/assets/icons/fluent/desktop-speaker-filled.svg new file mode 100644 index 000000000..509aedb83 --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/desktop-speaker-filled.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/desktop-speaker.svg b/dots/.config/quickshell/ii/assets/icons/fluent/desktop-speaker.svg new file mode 100644 index 000000000..32287733b --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/desktop-speaker.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/headphones-filled.svg b/dots/.config/quickshell/ii/assets/icons/fluent/headphones-filled.svg new file mode 100644 index 000000000..256823486 --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/headphones-filled.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/headphones.svg b/dots/.config/quickshell/ii/assets/icons/fluent/headphones.svg new file mode 100644 index 000000000..d4ed6c490 --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/headphones.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/speaker-mute-filled.svg b/dots/.config/quickshell/ii/assets/icons/fluent/speaker-mute-filled.svg new file mode 100644 index 000000000..50c84c316 --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/speaker-mute-filled.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/speaker-mute.svg b/dots/.config/quickshell/ii/assets/icons/fluent/speaker-mute.svg new file mode 100644 index 000000000..891d21e57 --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/speaker-mute.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/modules/common/widgets/FadeLoader.qml b/dots/.config/quickshell/ii/modules/common/widgets/FadeLoader.qml index f5064939b..f822792a3 100644 --- a/dots/.config/quickshell/ii/modules/common/widgets/FadeLoader.qml +++ b/dots/.config/quickshell/ii/modules/common/widgets/FadeLoader.qml @@ -1,12 +1,12 @@ import QtQuick import qs.modules.common -import qs.modules.common.widgets Loader { id: root property bool shown: true property alias fade: opacityBehavior.enabled + property alias animation: opacityBehavior.animation opacity: shown ? 1 : 0 visible: opacity > 0 active: opacity > 0 diff --git a/dots/.config/quickshell/ii/modules/common/widgets/PopupToolTip.qml b/dots/.config/quickshell/ii/modules/common/widgets/PopupToolTip.qml index afc9957a4..437c02a88 100644 --- a/dots/.config/quickshell/ii/modules/common/widgets/PopupToolTip.qml +++ b/dots/.config/quickshell/ii/modules/common/widgets/PopupToolTip.qml @@ -13,7 +13,9 @@ Item { property bool alternativeVisibleCondition: false property real horizontalPadding: 10 property real verticalPadding: 5 - + property real horizontalMargin: horizontalPadding + property real verticalMargin: verticalPadding + function updateAnchor() { tooltipLoader.item?.anchor.updateAnchor(); } @@ -49,8 +51,8 @@ Item { } color: "transparent" - implicitWidth: root.contentItem.implicitWidth + root.horizontalPadding * 2 - implicitHeight: root.contentItem.implicitHeight + root.verticalPadding * 2 + implicitWidth: root.contentItem.implicitWidth + root.horizontalMargin * 2 + implicitHeight: root.contentItem.implicitHeight + root.verticalMargin * 2 data: [root.contentItem] } diff --git a/dots/.config/quickshell/ii/modules/ii/sidebarRight/volumeMixer/VolumeDialogContent.qml b/dots/.config/quickshell/ii/modules/ii/sidebarRight/volumeMixer/VolumeDialogContent.qml index 5eb409ecb..f7c2dc9f8 100644 --- a/dots/.config/quickshell/ii/modules/ii/sidebarRight/volumeMixer/VolumeDialogContent.qml +++ b/dots/.config/quickshell/ii/modules/ii/sidebarRight/volumeMixer/VolumeDialogContent.qml @@ -10,15 +10,8 @@ import Quickshell.Services.Pipewire ColumnLayout { id: root required property bool isSink - function correctType(node) { - return (node.isSink === root.isSink) && node.audio - } - readonly property list appPwNodes: Pipewire.nodes.values.filter((node) => { // Should be list but it breaks ScriptModel - return root.correctType(node) && node.isStream - }) - readonly property list devices: Pipewire.nodes.values.filter(node => { - return root.correctType(node) && !node.isStream - }) + readonly property list appPwNodes: isSink ? Audio.outputAppNodes : Audio.inputAppNodes + readonly property list devices: isSink ? Audio.outputDevices : Audio.inputDevices readonly property bool hasApps: appPwNodes.length > 0 spacing: 16 @@ -44,21 +37,21 @@ ColumnLayout { Layout.fillHeight: false Layout.fillWidth: true Layout.bottomMargin: 6 - model: root.devices.map(node => (node.nickname || node.description || Translation.tr("Unknown"))) + model: root.devices.map(node => Audio.friendlyDeviceName(node)) currentIndex: root.devices.findIndex(item => { if (root.isSink) { - return item.id === Pipewire.preferredDefaultAudioSink?.id + return item.id === Pipewire.defaultAudioSink?.id } else { - return item.id === Pipewire.preferredDefaultAudioSource?.id + return item.id === Pipewire.defaultAudioSource?.id } }) onActivated: (index) => { print(index) const item = root.devices[index] if (root.isSink) { - Pipewire.preferredDefaultAudioSink = item + Audio.setDefaultSink(item) } else { - Pipewire.preferredDefaultAudioSource = item + Audio.setDefaultSource(item) } } } diff --git a/dots/.config/quickshell/ii/modules/ii/sidebarRight/volumeMixer/VolumeMixerEntry.qml b/dots/.config/quickshell/ii/modules/ii/sidebarRight/volumeMixer/VolumeMixerEntry.qml index a00335fb0..a871c05c6 100644 --- a/dots/.config/quickshell/ii/modules/ii/sidebarRight/volumeMixer/VolumeMixerEntry.qml +++ b/dots/.config/quickshell/ii/modules/ii/sidebarRight/volumeMixer/VolumeMixerEntry.qml @@ -10,7 +10,7 @@ Item { id: root required property PwNode node PwObjectTracker { - objects: [node] + objects: [root.node] } implicitHeight: rowLayout.implicitHeight @@ -47,7 +47,7 @@ Item { elide: Text.ElideRight text: { // application.name -> description -> name - const app = root.node?.properties["application.name"] ?? (root.node.description != "" ? root.node.description : root.node.name); + const app = Audio.appNodeDisplayName(root.node); const media = root.node.properties["media.name"]; return media != undefined ? `${app} • ${media}` : app; } diff --git a/dots/.config/quickshell/ii/modules/waffle/actionCenter/ActionCenterContent.qml b/dots/.config/quickshell/ii/modules/waffle/actionCenter/ActionCenterContent.qml index 8519f5351..2b3385ece 100644 --- a/dots/.config/quickshell/ii/modules/waffle/actionCenter/ActionCenterContent.qml +++ b/dots/.config/quickshell/ii/modules/waffle/actionCenter/ActionCenterContent.qml @@ -7,32 +7,34 @@ import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.waffle.looks +import qs.modules.waffle.actionCenter.mainPage WBarAttachedPanelContent { id: root - contentItem: StackView { - implicitWidth: currentItem.implicitWidth - implicitHeight: currentItem.implicitHeight + contentItem: StackView { // TODO: Make this a WStackView with proper anim + id: stackView + anchors.fill: parent + implicitWidth: initItem.implicitWidth + implicitHeight: initItem.implicitHeight - initialItem: ColumnLayout { - anchors.centerIn: parent - spacing: 0 - - ActionCenterBody {} - - Rectangle { - Layout.fillHeight: false - Layout.fillWidth: true - color: Looks.colors.bgPanelSeparator - implicitHeight: 1 - } - - ActionCenterFooter {} + initialItem: PageColumn { + id: initItem + MainPageBody {} + Separator {} + MainPageFooter {} } Component.onCompleted: { ActionCenterContext.stackView = this } + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.BackButton + onClicked: { + ActionCenterContext.back() + } + } } } diff --git a/dots/.config/quickshell/ii/modules/waffle/actionCenter/ActionCenterContext.qml b/dots/.config/quickshell/ii/modules/waffle/actionCenter/ActionCenterContext.qml index 7c75ba4c9..07aa54df9 100644 --- a/dots/.config/quickshell/ii/modules/waffle/actionCenter/ActionCenterContext.qml +++ b/dots/.config/quickshell/ii/modules/waffle/actionCenter/ActionCenterContext.qml @@ -9,4 +9,17 @@ Singleton { property StackView stackView + function push(component) { + if (stackView) { + item = stackView.push(component) + stackView.implicitWidth = item.implicitWidth + stackView.implicitHeight = item.implicitHeight + } + } + + function back() { + if (stackView && stackView.depth > 1) { + stackView.pop() + } + } } diff --git a/dots/.config/quickshell/ii/modules/waffle/actionCenter/BodyRectangle.qml b/dots/.config/quickshell/ii/modules/waffle/actionCenter/BodyRectangle.qml new file mode 100644 index 000000000..98e991359 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/actionCenter/BodyRectangle.qml @@ -0,0 +1,14 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import qs +import qs.services +import qs.modules.common +import qs.modules.common.functions +import qs.modules.waffle.looks + +Rectangle { + Layout.fillHeight: true + Layout.fillWidth: true + color: Looks.colors.bgPanelBody +} diff --git a/dots/.config/quickshell/ii/modules/waffle/actionCenter/FooterRectangle.qml b/dots/.config/quickshell/ii/modules/waffle/actionCenter/FooterRectangle.qml new file mode 100644 index 000000000..f58ea6c9a --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/actionCenter/FooterRectangle.qml @@ -0,0 +1,17 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import qs +import qs.services +import qs.modules.common +import qs.modules.common.functions +import qs.modules.waffle.looks + +Rectangle { + Layout.fillHeight: false + Layout.fillWidth: true + color: Looks.colors.bgPanelFooter + + implicitWidth: 360 + implicitHeight: 47 +} diff --git a/dots/.config/quickshell/ii/modules/waffle/actionCenter/HeaderRow.qml b/dots/.config/quickshell/ii/modules/waffle/actionCenter/HeaderRow.qml new file mode 100644 index 000000000..a6458192b --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/actionCenter/HeaderRow.qml @@ -0,0 +1,25 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import qs.modules.waffle.looks +import qs.modules.waffle.actionCenter + +RowLayout { + id: root + + required property string title + spacing: 4 + + WPanelIconButton { + iconName: "arrow-left" + onClicked: ActionCenterContext.back() + } + + WText { + id: titleText + Layout.fillWidth: true + elide: Text.ElideRight + text: root.title + font.pixelSize: Looks.font.pixelSize.large + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/actionCenter/PageColumn.qml b/dots/.config/quickshell/ii/modules/waffle/actionCenter/PageColumn.qml new file mode 100644 index 000000000..0ecb38f10 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/actionCenter/PageColumn.qml @@ -0,0 +1,6 @@ +import QtQuick +import QtQuick.Layouts + +ColumnLayout { + spacing: 0 +} diff --git a/dots/.config/quickshell/ii/modules/waffle/actionCenter/SectionText.qml b/dots/.config/quickshell/ii/modules/waffle/actionCenter/SectionText.qml new file mode 100644 index 000000000..895ed4649 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/actionCenter/SectionText.qml @@ -0,0 +1,20 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import qs +import qs.services +import qs.modules.common +import qs.modules.common.functions +import qs.modules.waffle.looks + +WText { + Layout.leftMargin: 12 + Layout.rightMargin: 12 + Layout.topMargin: 6 + Layout.bottomMargin: 6 + + font { + weight: Looks.font.weight.stronger + pixelSize: Looks.font.pixelSize.large + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/actionCenter/Separator.qml b/dots/.config/quickshell/ii/modules/waffle/actionCenter/Separator.qml new file mode 100644 index 000000000..17352a814 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/actionCenter/Separator.qml @@ -0,0 +1,10 @@ +import QtQuick +import QtQuick.Layouts +import qs.modules.waffle.looks + +Rectangle { + Layout.fillHeight: false + Layout.fillWidth: true + color: Looks.colors.bgPanelSeparator + implicitHeight: 1 +} diff --git a/dots/.config/quickshell/ii/modules/waffle/actionCenter/WaffleActionCenter.qml b/dots/.config/quickshell/ii/modules/waffle/actionCenter/WaffleActionCenter.qml index 565ac4ba2..9983cdb3b 100644 --- a/dots/.config/quickshell/ii/modules/waffle/actionCenter/WaffleActionCenter.qml +++ b/dots/.config/quickshell/ii/modules/waffle/actionCenter/WaffleActionCenter.qml @@ -54,7 +54,8 @@ Scope { ActionCenterContent { id: content - anchors.centerIn: parent + anchors.fill: parent + anchors.margins: visualMargin focus: true Keys.onPressed: event => { // Esc to close diff --git a/dots/.config/quickshell/ii/modules/waffle/actionCenter/ActionCenterBody.qml b/dots/.config/quickshell/ii/modules/waffle/actionCenter/mainPage/MainPageBody.qml similarity index 77% rename from dots/.config/quickshell/ii/modules/waffle/actionCenter/ActionCenterBody.qml rename to dots/.config/quickshell/ii/modules/waffle/actionCenter/mainPage/MainPageBody.qml index 0d4fd7183..80ec7de51 100644 --- a/dots/.config/quickshell/ii/modules/waffle/actionCenter/ActionCenterBody.qml +++ b/dots/.config/quickshell/ii/modules/waffle/actionCenter/mainPage/MainPageBody.qml @@ -6,15 +6,10 @@ import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.waffle.looks +import qs.modules.waffle.actionCenter -Rectangle { +BodyRectangle { id: root - - Layout.fillHeight: true - Layout.fillWidth: true - color: Looks.colors.bgPanelBody - - implicitWidth: 360 implicitHeight: contentLayout.implicitHeight ColumnLayout { @@ -22,7 +17,7 @@ Rectangle { anchors.fill: parent spacing: 0 - ActionCenterBodyToggles { + MainPageBodyToggles { id: togglesContainer Layout.fillWidth: true } @@ -33,7 +28,7 @@ Rectangle { color: Looks.colors.bg1Border } - ActionCenterBodySliders { + MainPageBodySliders { Layout.margins: 12 Layout.topMargin: 18 Layout.bottomMargin: 14 diff --git a/dots/.config/quickshell/ii/modules/waffle/actionCenter/ActionCenterBodySliders.qml b/dots/.config/quickshell/ii/modules/waffle/actionCenter/mainPage/MainPageBodySliders.qml similarity index 87% rename from dots/.config/quickshell/ii/modules/waffle/actionCenter/ActionCenterBodySliders.qml rename to dots/.config/quickshell/ii/modules/waffle/actionCenter/mainPage/MainPageBodySliders.qml index d42385ee6..506b828a0 100644 --- a/dots/.config/quickshell/ii/modules/waffle/actionCenter/ActionCenterBodySliders.qml +++ b/dots/.config/quickshell/ii/modules/waffle/actionCenter/mainPage/MainPageBodySliders.qml @@ -6,6 +6,8 @@ import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.waffle.looks +import qs.modules.waffle.actionCenter +import qs.modules.waffle.actionCenter.volumeControl ColumnLayout { id: root @@ -58,6 +60,13 @@ ColumnLayout { } WPanelIconButton { + Component { + id: volumeControlComp + VolumeControl {} + } + onClicked: { + ActionCenterContext.push(volumeControlComp) + } contentItem: Item { anchors.centerIn: parent Row { diff --git a/dots/.config/quickshell/ii/modules/waffle/actionCenter/ActionCenterBodyToggles.qml b/dots/.config/quickshell/ii/modules/waffle/actionCenter/mainPage/MainPageBodyToggles.qml similarity index 100% rename from dots/.config/quickshell/ii/modules/waffle/actionCenter/ActionCenterBodyToggles.qml rename to dots/.config/quickshell/ii/modules/waffle/actionCenter/mainPage/MainPageBodyToggles.qml diff --git a/dots/.config/quickshell/ii/modules/waffle/actionCenter/ActionCenterFooter.qml b/dots/.config/quickshell/ii/modules/waffle/actionCenter/mainPage/MainPageFooter.qml similarity index 88% rename from dots/.config/quickshell/ii/modules/waffle/actionCenter/ActionCenterFooter.qml rename to dots/.config/quickshell/ii/modules/waffle/actionCenter/mainPage/MainPageFooter.qml index 2a03daa23..feeb744c3 100644 --- a/dots/.config/quickshell/ii/modules/waffle/actionCenter/ActionCenterFooter.qml +++ b/dots/.config/quickshell/ii/modules/waffle/actionCenter/mainPage/MainPageFooter.qml @@ -6,14 +6,9 @@ import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.waffle.looks +import qs.modules.waffle.actionCenter -Rectangle { - Layout.fillHeight: false - Layout.fillWidth: true - color: Looks.colors.bgPanelFooter - - implicitWidth: 360 - implicitHeight: 47 +FooterRectangle { // Battery button WPanelFooterButton { diff --git a/dots/.config/quickshell/ii/modules/waffle/actionCenter/volumeControl/VolumeControl.qml b/dots/.config/quickshell/ii/modules/waffle/actionCenter/volumeControl/VolumeControl.qml new file mode 100644 index 000000000..d4150ab7a --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/actionCenter/volumeControl/VolumeControl.qml @@ -0,0 +1,160 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs +import qs.services +import qs.modules.common +import qs.modules.common.functions +import qs.modules.common.widgets +import qs.modules.waffle.looks +import qs.modules.waffle.actionCenter + +Rectangle { + id: root + implicitWidth: 360 + implicitHeight: 352 + + PageColumn { + anchors.fill: parent + + BodyRectangle { + implicitHeight: 400 + implicitWidth: 50 + + ColumnLayout { + anchors.fill: parent + anchors.margins: 4 + spacing: 4 + + HeaderRow { + Layout.fillWidth: true + title: qsTr("Sound output") + } + + StyledFlickable { + id: flickable + Layout.fillHeight: true + Layout.fillWidth: true + + contentHeight: contentLayout.implicitHeight + contentWidth: width + clip: true + + AudioChoices { + id: contentLayout + width: flickable.width + } + } + } + } + + Separator {} + + FooterRectangle { + WButton { + id: moreSettingsButton + anchors { + verticalCenter: parent.verticalCenter + left: parent.left + } + inset: 0 + implicitHeight: 40 + implicitWidth: contentItem.implicitWidth + 30 + color: "transparent" + + onClicked: { + Quickshell.execDetached(["qs", "-p", Quickshell.shellPath(""), "ipc", "call", "sidebarLeft", "toggle"]); + Quickshell.execDetached(["bash", "-c", Config.options.apps.volumeMixer]); + } + + contentItem: Item { + anchors.centerIn: parent + implicitWidth: buttonText.implicitWidth + WText { + id: buttonText + anchors.centerIn: parent + text: qsTr("More volume settings") + color: moreSettingsButton.pressed ? Looks.colors.fg : Looks.colors.fg1 + } + } + } + } + } + + component AudioChoices: ColumnLayout { + spacing: 4 + + SectionText { + text: qsTr("Output device") + } + + Repeater { + model: ScriptModel { + values: Audio.outputDevices + } + delegate: WChoiceButton { + required property var modelData + icon.name: WIcons.audioDeviceIcon(modelData) + text: Audio.friendlyDeviceName(modelData) + checked: Audio.sink === modelData + onClicked: { + Audio.setDefaultSink(modelData); + } + } + } + + Separator { + visible: EasyEffects.available + color: Looks.colors.bg2Hover + } + + //////////////////////////////////////////////////////////// + + SectionText { + visible: EasyEffects.available + text: qsTr("Sound effects") + } + + WChoiceButton { + visible: EasyEffects.available + text: Translation.tr("Off") + checked: !EasyEffects.active + onClicked: EasyEffects.disable() + } + + WChoiceButton { + visible: EasyEffects.available + text: "EasyEffects" + checked: EasyEffects.active + onClicked: EasyEffects.enable() + } + + Separator { + color: Looks.colors.bg2Hover + } + + //////////////////////////////////////////////////////////// + + SectionText { + visible: EasyEffects.available + text: qsTr("Volume mixer") + } + + VolumeEntry { + node: Audio.sink + icon: "speaker" + monochrome: true + } + + Repeater { + model: ScriptModel { + values: Audio.outputAppNodes + } + delegate: VolumeEntry { + required property var modelData + node: modelData + } + } + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/actionCenter/volumeControl/VolumeEntry.qml b/dots/.config/quickshell/ii/modules/waffle/actionCenter/volumeControl/VolumeEntry.qml new file mode 100644 index 000000000..000aee2ac --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/actionCenter/volumeControl/VolumeEntry.qml @@ -0,0 +1,54 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Services.Pipewire +import qs +import qs.services +import qs.modules.common +import qs.modules.common.functions +import qs.modules.common.widgets +import qs.modules.waffle.looks +import qs.modules.waffle.actionCenter + +RowLayout { + id: root + required property PwNode node + property alias icon: iconButton.iconName + property alias monochrome: iconButton.monochrome + monochrome: false + + PwObjectTracker { // Necessary for useful info to be present in 'node' + objects: [root.node] + } + + WPanelIconButton { + id: iconButton + iconName: WIcons.audioAppIcon(root.node) + onClicked: root.node.audio.muted = !root.node?.audio.muted + + FluentIcon { + id: muteIcon + visible: root.node?.audio.muted ?? false + anchors { + bottom: parent.bottom + right: parent.right + margins: -1 + } + implicitSize: 14 + icon: "speaker-mute" + } + + WToolTip { + extraVisibleCondition: iconButton.shouldShowTooltip + text: Audio.appNodeDisplayName(root.node) + } + } + + WSlider { + Layout.fillWidth: true + Layout.rightMargin: 10 + value: root.node?.audio.volume ?? 0 + onMoved: root.node.audio.volume = value + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/bar/BarButton.qml b/dots/.config/quickshell/ii/modules/waffle/bar/BarButton.qml index f8192389a..a2e745072 100644 --- a/dots/.config/quickshell/ii/modules/waffle/bar/BarButton.qml +++ b/dots/.config/quickshell/ii/modules/waffle/bar/BarButton.qml @@ -5,41 +5,21 @@ import qs.modules.common import qs.modules.common.functions import qs.modules.waffle.looks -Button { +WButton { id: root property var altAction: () => {} property var middleClickAction: () => {} - property color colBackground: ColorUtils.transparentize(Looks.colors.bg1) - property color colBackgroundHover: Looks.colors.bg1Hover - property color colBackgroundActive: Looks.colors.bg1Active + colBackground: ColorUtils.transparentize(Looks.colors.bg1) + colBackgroundHover: Looks.colors.bg1Hover + colBackgroundActive: Looks.colors.bg1Active property color colBackgroundBorder property color color Layout.fillHeight: true topInset: 4 bottomInset: 4 - signal hoverTimedOut() - property bool shouldShowTooltip: false - property Timer hoverTimer: Timer { - id: hoverTimer - running: root.hovered - interval: 400 - onTriggered: { - root.hoverTimedOut() - } - } - onHoverTimedOut: { - root.shouldShowTooltip = true - } - onHoveredChanged: { - if (!root.hovered) { - root.shouldShowTooltip = false - root.hoverTimer.stop() - } - } - colBackgroundBorder: ColorUtils.transparentize(Looks.colors.bg1Border, (root.checked || root.hovered) ? Looks.contentTransparency : 1) color: { if (root.down) { diff --git a/dots/.config/quickshell/ii/modules/waffle/bar/TimeButton.qml b/dots/.config/quickshell/ii/modules/waffle/bar/TimeButton.qml index 19da1ecdb..bf4c5c614 100644 --- a/dots/.config/quickshell/ii/modules/waffle/bar/TimeButton.qml +++ b/dots/.config/quickshell/ii/modules/waffle/bar/TimeButton.qml @@ -37,6 +37,7 @@ BarButton { } } FluentIcon { + visible: Notifications.silent anchors.verticalCenter: parent.verticalCenter icon: "alert-snooze" implicitSize: 18 diff --git a/dots/.config/quickshell/ii/modules/waffle/bar/tray/Tray.qml b/dots/.config/quickshell/ii/modules/waffle/bar/tray/Tray.qml index f05f2593e..db021b032 100644 --- a/dots/.config/quickshell/ii/modules/waffle/bar/tray/Tray.qml +++ b/dots/.config/quickshell/ii/modules/waffle/bar/tray/Tray.qml @@ -105,8 +105,8 @@ RowLayout { BarToolTip { id: pinTooltip extraVisibleCondition: trayButton.Drag.active && pinDropArea.containsDrag && pinDropArea.willPin - realContentHorizontalPadding: 6 - realContentVerticalPadding: 6 + horizontalPadding: 6 + verticalPadding: 6 realContentItem: FluentIcon { anchors.centerIn: parent icon: "pin-off" diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/Looks.qml b/dots/.config/quickshell/ii/modules/waffle/looks/Looks.qml index c6488ab57..e62657636 100644 --- a/dots/.config/quickshell/ii/modules/waffle/looks/Looks.qml +++ b/dots/.config/quickshell/ii/modules/waffle/looks/Looks.qml @@ -76,7 +76,8 @@ Singleton { } property QtObject pixelSize: QtObject { property real normal: 11 - property real large: 14 + property real large: 13 + property real larger: 15 } } diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/WBarAttachedPanelContent.qml b/dots/.config/quickshell/ii/modules/waffle/looks/WBarAttachedPanelContent.qml index ae984089d..9dfdc43f7 100644 --- a/dots/.config/quickshell/ii/modules/waffle/looks/WBarAttachedPanelContent.qml +++ b/dots/.config/quickshell/ii/modules/waffle/looks/WBarAttachedPanelContent.qml @@ -78,11 +78,14 @@ Item { } } - Item { + Rectangle { id: contentArea + color: "red" z: 0 anchors.fill: borderRect anchors.margins: borderRect.border.width + implicitWidth: contentItem.implicitWidth + implicitHeight: contentItem.implicitHeight layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/WButton.qml b/dots/.config/quickshell/ii/modules/waffle/looks/WButton.qml index b7a068698..d158c5949 100644 --- a/dots/.config/quickshell/ii/modules/waffle/looks/WButton.qml +++ b/dots/.config/quickshell/ii/modules/waffle/looks/WButton.qml @@ -15,6 +15,7 @@ Button { property color colBackgroundToggled: Looks.colors.accent property color colBackgroundToggledHover: Looks.colors.accentHover property color colBackgroundToggledActive: Looks.colors.accentActive + property color colForeground: Looks.colors.fg property alias backgroundOpacity: backgroundRect.opacity property color color: { if (root.checked) { @@ -35,7 +36,29 @@ Button { } } + // Hover stuff + signal hoverTimedOut() + property bool shouldShowTooltip: false + property Timer hoverTimer: Timer { + id: hoverTimer + running: root.hovered + interval: 400 + onTriggered: { + root.hoverTimedOut() + } + } + onHoverTimedOut: { + root.shouldShowTooltip = true + } + onHoveredChanged: { + if (!root.hovered) { + root.shouldShowTooltip = false + root.hoverTimer.stop() + } + } + property alias monochromeIcon: buttonIcon.monochrome + property alias buttonSpacing: contentLayout.spacing property bool forceShowIcon: false property var altAction: () => {} @@ -93,16 +116,16 @@ Button { spacing: 12 FluentIcon { id: buttonIcon - visible: root.icon.name !== "" || root.forceShowIcon monochrome: true implicitSize: 16 - Layout.leftMargin: 6 + Layout.leftMargin: root.iconLeftMargin Layout.fillWidth: false Layout.alignment: Qt.AlignVCenter icon: root.icon.name + color: root.colForeground + visible: root.icon.name !== "" } WText { - Layout.rightMargin: 12 Layout.fillWidth: true Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft text: root.text @@ -110,6 +133,7 @@ Button { font { pixelSize: Looks.font.pixelSize.large } + color: root.colForeground } } } diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/WChoiceButton.qml b/dots/.config/quickshell/ii/modules/waffle/looks/WChoiceButton.qml new file mode 100644 index 000000000..60de3d816 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/looks/WChoiceButton.qml @@ -0,0 +1,67 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import qs +import qs.services +import qs.modules.common +import qs.modules.common.functions +import qs.modules.common.widgets +import qs.modules.waffle.looks + +WButton { + id: root + + Layout.fillWidth: true + implicitWidth: contentItem.implicitWidth + horizontalPadding: 10 + verticalPadding: 11 + inset: 0 + buttonSpacing: 8 + + property color color: { + if (root.checked) { + if (root.down) { + return root.colBackgroundHover; + } else if (root.hovered && !root.down) { + return root.colBackgroundActive; + } else { + return root.colBackgroundHover; + } + } + if (root.down) { + return root.colBackgroundActive; + } else if (root.hovered && !root.down) { + return root.colBackgroundHover; + } else { + return root.colBackground; + } + } + + background: Rectangle { + id: backgroundRect + radius: Looks.radius.medium + color: root.color + Behavior on color { + animation: Looks.transition.color.createObject(this) + } + + WFadeLoader { + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + shown: root.checked + sourceComponent: Rectangle { + implicitWidth: 3 + implicitHeight: 3 + radius: width / 2 + color: Looks.colors.accent + Component.onCompleted: { + implicitHeight = 16; + } + + Behavior on implicitHeight { + animation: Looks.transition.opacity.createObject(this) + } + } + } + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/WFadeLoader.qml b/dots/.config/quickshell/ii/modules/waffle/looks/WFadeLoader.qml new file mode 100644 index 000000000..c815ec0b8 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/looks/WFadeLoader.qml @@ -0,0 +1,19 @@ +import QtQuick +import qs.modules.common + +// Yes, this is (mostly) a copy of FadeLoader. +// The animation of a Behavior cannot be changed... I'd love to be proven wrong. +Loader { + id: root + property bool shown: true + property alias fade: opacityBehavior.enabled + property alias animation: opacityBehavior.animation + opacity: shown ? 1 : 0 + visible: opacity > 0 + active: opacity > 0 + + Behavior on opacity { + id: opacityBehavior + animation: Looks.transition.opacity.createObject(null) + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/WIcons.qml b/dots/.config/quickshell/ii/modules/waffle/looks/WIcons.qml index c0fd7381d..42836fbc7 100644 --- a/dots/.config/quickshell/ii/modules/waffle/looks/WIcons.qml +++ b/dots/.config/quickshell/ii/modules/waffle/looks/WIcons.qml @@ -8,24 +8,34 @@ Singleton { id: root property string internetIcon: { - if (Network.ethernet) return "ethernet"; + if (Network.ethernet) + return "ethernet"; if (Network.wifiEnabled) { const strength = Network.networkStrength; - if (strength > 75) return "wifi-1"; - if (strength > 50) return "wifi-2"; - if (strength > 25) return "wifi-3"; + if (strength > 75) + return "wifi-1"; + if (strength > 50) + return "wifi-2"; + if (strength > 25) + return "wifi-3"; return "wifi-4"; } - if (Network.wifiStatus === "connecting") return "wifi-4"; - if (Network.wifiStatus === "disconnected") return "wifi-off"; - if (Network.wifiStatus === "disabled") return "wifi-off"; + if (Network.wifiStatus === "connecting") + return "wifi-4"; + if (Network.wifiStatus === "disconnected") + return "wifi-off"; + if (Network.wifiStatus === "disabled") + return "wifi-off"; return "wifi-warning"; } property string batteryIcon: { - if (Battery.isCharging) return "battery-charge"; - if (Battery.isCriticalAndNotCharging) return "battery-warning"; - if (Battery.percentage >= 0.9) return "battery-full"; + if (Battery.isCharging) + return "battery-charge"; + if (Battery.isCriticalAndNotCharging) + return "battery-warning"; + if (Battery.percentage >= 0.9) + return "battery-full"; return `battery-${Math.ceil(Battery.percentage * 10)}`; } @@ -33,7 +43,7 @@ Singleton { const muted = Audio.sink?.audio.muted ?? false; const volume = Audio.sink?.audio.volume ?? 0; if (muted) - return volume > 0 ? "speaker-off" : "speaker-none"; + return "speaker-mute"; if (volume == 0) return "speaker-none"; if (volume < 0.5) @@ -53,10 +63,39 @@ Singleton { property string notificationsIcon: Notifications.silent ? "alert-snooze" : "alert" property string powerProfileIcon: { - switch(PowerProfiles.profile) { - case PowerProfile.PowerSaver: return "leaf-two"; - case PowerProfile.Balanced: return "flash-on"; - case PowerProfile.Performance: return "fire"; + switch (PowerProfiles.profile) { + case PowerProfile.PowerSaver: + return "leaf-two"; + case PowerProfile.Balanced: + return "flash-on"; + case PowerProfile.Performance: + return "fire"; } } + + function audioDeviceIcon(node) { + if (!node.isSink) + return "mic-on"; + const monitor = /monitor|hdmi/i; + const headphones = /headset|headphone|bluez|wireless/i; + const speakers = /speaker|output/i; + if (monitor.test(node.nickname) || monitor.test(node.description) || monitor.test(node.name)) { + return "desktop-speaker"; + } + if (headphones.test(node.nickname) || headphones.test(node.description) || headphones.test(node.name)) { + return "headphones"; + } + if (speakers.test(node.nickname) || speakers.test(node.description) || speakers.test(node.name)) { + return "speaker"; + } + return "speaker"; + } + + function audioAppIcon(node) { + let icon; + icon = AppSearch.guessIcon(node?.properties["application.icon-name"] ?? ""); + if (AppSearch.iconExists(icon)) return icon; + icon = AppSearch.guessIcon(node?.properties["node.name"] ?? ""); + return icon; + } } diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/WPanelIconButton.qml b/dots/.config/quickshell/ii/modules/waffle/looks/WPanelIconButton.qml index edaad11a6..8d11ec199 100644 --- a/dots/.config/quickshell/ii/modules/waffle/looks/WPanelIconButton.qml +++ b/dots/.config/quickshell/ii/modules/waffle/looks/WPanelIconButton.qml @@ -10,6 +10,7 @@ WButton { id: root property alias iconName: iconContent.icon + property alias monochrome: iconContent.monochrome inset: 0 implicitWidth: 40 implicitHeight: 40 diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/WPopupToolTip.qml b/dots/.config/quickshell/ii/modules/waffle/looks/WPopupToolTip.qml index feddc3793..d98572261 100644 --- a/dots/.config/quickshell/ii/modules/waffle/looks/WPopupToolTip.qml +++ b/dots/.config/quickshell/ii/modules/waffle/looks/WPopupToolTip.qml @@ -10,37 +10,22 @@ import qs.modules.waffle.looks PopupToolTip { id: root - property Item realContentItem + required property Item realContentItem realContentItem: WText { text: root.text anchors.centerIn: parent } property real visualMargin: 11 - verticalPadding: visualMargin - horizontalPadding: visualMargin - property real realContentVerticalPadding: 8 - property real realContentHorizontalPadding: 10 + verticalPadding: 8 + horizontalPadding: 10 + verticalMargin: visualMargin + horizontalMargin: visualMargin - contentItem: Item { - anchors.centerIn: parent - implicitWidth: realContent.implicitWidth + 2 * 2 - implicitHeight: realContent.implicitHeight + 2 * 2 - - WAmbientShadow { - target: realContent - } - - Rectangle { - id: realContent - z: 1 - anchors.centerIn: parent - implicitWidth: root.realContentItem.implicitWidth + root.realContentHorizontalPadding * 2 - implicitHeight: root.realContentItem.implicitHeight + root.realContentVerticalPadding * 2 - color: Looks.colors.bg1 - radius: Looks.radius.medium - - children: [root.realContentItem] - } + contentItem: WToolTipContent { + id: tooltipContent + realContentItem: root.realContentItem + horizontalPadding: root.horizontalPadding + verticalPadding: root.verticalPadding } } diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/WSlider.qml b/dots/.config/quickshell/ii/modules/waffle/looks/WSlider.qml index 30086f255..8c3c51e03 100644 --- a/dots/.config/quickshell/ii/modules/waffle/looks/WSlider.qml +++ b/dots/.config/quickshell/ii/modules/waffle/looks/WSlider.qml @@ -10,8 +10,7 @@ Slider { id: root property real trackWidth: 4 - // leftPadding: handle.width / 2 - // rightPadding: handle.width / 2 + property string tooltipContent: `${Math.round(value * 100)}` leftPadding: 0 rightPadding: 0 @@ -77,5 +76,14 @@ Slider { animation: Looks.transition.enter.createObject(this) } } + + WToolTip { + id: tooltip + extraVisibleCondition: root.pressed + text: root.tooltipContent + font.pixelSize: Looks.font.pixelSize.larger + verticalPadding: 3 + horizontalPadding: 8 + } } } diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/WToolTip.qml b/dots/.config/quickshell/ii/modules/waffle/looks/WToolTip.qml new file mode 100644 index 000000000..3c8d20d26 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/looks/WToolTip.qml @@ -0,0 +1,34 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.modules.common +import qs.modules.common.functions +import qs.modules.common.widgets +import qs.modules.waffle.looks + +StyledToolTip { + id: root + + required property Item realContentItem + font { + family: Looks.font.family.ui + pixelSize: Looks.font.pixelSize.normal + weight: Looks.font.weight.regular + } + realContentItem: WText { + text: root.text + font: root.font + anchors.centerIn: parent + } + + verticalPadding: 8 + horizontalPadding: 10 + + contentItem: WToolTipContent { + id: tooltipContent + realContentItem: root.realContentItem + horizontalPadding: root.horizontalPadding + verticalPadding: root.verticalPadding + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/WToolTipContent.qml b/dots/.config/quickshell/ii/modules/waffle/looks/WToolTipContent.qml new file mode 100644 index 000000000..57396fef5 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/looks/WToolTipContent.qml @@ -0,0 +1,29 @@ +import QtQuick +import Quickshell +import qs.modules.waffle.looks + +Item { + id: root + anchors.centerIn: parent + required property Item realContentItem + property real verticalPadding: 8 + property real horizontalPadding: 10 + implicitWidth: realContent.implicitWidth + 2 * 2 + implicitHeight: realContent.implicitHeight + 2 * 2 + + WAmbientShadow { + target: realContent + } + + Rectangle { + id: realContent + z: 1 + anchors.centerIn: parent + implicitWidth: root.realContentItem.implicitWidth + root.horizontalPadding * 2 + implicitHeight: root.realContentItem.implicitHeight + root.verticalPadding * 2 + color: Looks.colors.bg1 + radius: Looks.radius.medium + + children: [root.realContentItem] + } +} diff --git a/dots/.config/quickshell/ii/services/Audio.qml b/dots/.config/quickshell/ii/services/Audio.qml index 633890cd0..4b45701a9 100644 --- a/dots/.config/quickshell/ii/services/Audio.qml +++ b/dots/.config/quickshell/ii/services/Audio.qml @@ -11,15 +11,43 @@ import Quickshell.Services.Pipewire Singleton { id: root + // Misc props property bool ready: Pipewire.defaultAudioSink?.ready ?? false property PwNode sink: Pipewire.defaultAudioSink property PwNode source: Pipewire.defaultAudioSource readonly property real hardMaxValue: 2.00 // People keep joking about setting volume to 5172% so... property string audioTheme: Config.options.sounds.theme property real value: sink?.audio.volume ?? 0 - + function friendlyDeviceName(node) { + return (node.nickname || node.description || Translation.tr("Unknown")); + } + function appNodeDisplayName(node) { + return (node.properties["application.name"] || node.description || node.name) + } + + // Lists + function correctType(node, isSink) { + return (node.isSink === isSink) && node.audio + } + function appNodes(isSink) { + return Pipewire.nodes.values.filter((node) => { // Should be list but it breaks ScriptModel + return root.correctType(node, isSink) && node.isStream + }) + } + function devices(isSink) { + return Pipewire.nodes.values.filter(node => { + return root.correctType(node, isSink) && !node.isStream + }) + } + readonly property list outputAppNodes: root.appNodes(true) + readonly property list inputAppNodes: root.appNodes(false) + readonly property list outputDevices: root.devices(true) + readonly property list inputDevices: root.devices(false) + + // Signals signal sinkProtectionTriggered(string reason); + // Controls function toggleMute() { Audio.sink.audio.muted = !Audio.sink.audio.muted } @@ -39,8 +67,16 @@ Singleton { const step = currentVolume < 0.1 ? 0.01 : 0.02 || 0.2; Audio.sink.audio.volume -= step; } - + function setDefaultSink(node) { + Pipewire.preferredDefaultAudioSink = node; + } + + function setDefaultSource(node) { + Pipewire.preferredDefaultAudioSource = node; + } + + // Internals PwObjectTracker { objects: [sink, source] }