diff --git a/Bar.qml b/Bar.qml index 670ab2a..27782b8 100644 --- a/Bar.qml +++ b/Bar.qml @@ -2,6 +2,7 @@ import QtQuick import QtQuick.Layouts import Quickshell import Quickshell.Io +import Quickshell.Wayland import qs.Modules import qs.Config import qs.Helpers @@ -16,6 +17,20 @@ Scope { property bool trayMenuVisible: false screen: modelData property var root: Quickshell.shellDir + WlrLayershell.exclusionMode: ExclusionMode.Ignore + + PanelWindow { + id: wrapper + screen: bar.screen + WlrLayershell.layer: WlrLayer.Bottom + anchors { + left: true + right: true + top: true + } + color: "transparent" + implicitHeight: 34 + } NotificationCenter { bar: bar @@ -31,14 +46,19 @@ Scope { top: true left: true right: true + bottom: true } - implicitHeight: 34 + mask: Region { item: backgroundRect } + color: "transparent" Rectangle { id: backgroundRect - anchors.fill: parent + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + implicitHeight: 34 color: Config.useDynamicColors ? DynamicColors.tPalette.m3surface : Config.baseBgColor radius: 0 diff --git a/Components/CustomClippingRect.qml b/Components/CustomClippingRect.qml new file mode 100644 index 0000000..120b117 --- /dev/null +++ b/Components/CustomClippingRect.qml @@ -0,0 +1,13 @@ +import Quickshell.Widgets +import QtQuick +import qs.Modules + +ClippingRectangle { + id: root + + color: "transparent" + + Behavior on color { + CAnim {} + } +} diff --git a/Components/CustomMouseArea.qml b/Components/CustomMouseArea.qml new file mode 100644 index 0000000..a17a74e --- /dev/null +++ b/Components/CustomMouseArea.qml @@ -0,0 +1,19 @@ +import QtQuick + +MouseArea { + property int scrollAccumulatedY: 0 + + function onWheel(event: WheelEvent): void { + } + + onWheel: event => { + if (Math.sign(event.angleDelta.y) !== Math.sign(scrollAccumulatedY)) + scrollAccumulatedY = 0; + scrollAccumulatedY += event.angleDelta.y; + + if (Math.abs(scrollAccumulatedY) >= 120) { + onWheel(event); + scrollAccumulatedY = 0; + } + } +} diff --git a/Components/CustomRadioButton.qml b/Components/CustomRadioButton.qml new file mode 100644 index 0000000..56d3927 --- /dev/null +++ b/Components/CustomRadioButton.qml @@ -0,0 +1,56 @@ +import QtQuick +import QtQuick.Templates +import qs.Config +import qs.Modules + +RadioButton { + id: root + + font.pointSize: 12 + + implicitWidth: implicitIndicatorWidth + implicitContentWidth + contentItem.anchors.leftMargin + implicitHeight: Math.max(implicitIndicatorHeight, implicitContentHeight) + + indicator: Rectangle { + id: outerCircle + + implicitWidth: 20 + implicitHeight: 20 + radius: 1000 + color: "transparent" + border.color: root.checked ? DynamicColors.palette.m3primary : DynamicColors.palette.m3onSurfaceVariant + border.width: 2 + anchors.verticalCenter: parent.verticalCenter + + StateLayer { + anchors.margins: -7 + color: root.checked ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3primary + z: -1 + + function onClicked(): void { + root.click(); + } + } + + CustomRect { + anchors.centerIn: parent + implicitWidth: 8 + implicitHeight: 8 + + radius: 1000 + color: Qt.alpha(DynamicColors.palette.m3primary, root.checked ? 1 : 0) + } + + Behavior on border.color { + CAnim {} + } + } + + contentItem: CustomText { + text: root.text + font.pointSize: root.font.pointSize + anchors.verticalCenter: parent.verticalCenter + anchors.left: outerCircle.right + anchors.leftMargin: 10 + } +} diff --git a/Components/CustomRect.qml b/Components/CustomRect.qml new file mode 100644 index 0000000..0aba620 --- /dev/null +++ b/Components/CustomRect.qml @@ -0,0 +1,12 @@ +import QtQuick +import qs.Modules + +Rectangle { + id: root + + color: "transparent" + + Behavior on color { + CAnim {} + } +} diff --git a/Components/CustomSlider.qml b/Components/CustomSlider.qml new file mode 100644 index 0000000..5cb6a38 --- /dev/null +++ b/Components/CustomSlider.qml @@ -0,0 +1,56 @@ +import QtQuick +import QtQuick.Templates +import qs.Config +import qs.Modules + +Slider { + id: root + + background: Item { + CustomRect { + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.topMargin: root.implicitHeight / 3 + anchors.bottomMargin: root.implicitHeight / 3 + + implicitWidth: root.handle.x - root.implicitHeight / 6 + + color: DynamicColors.palette.m3primary + radius: 1000 + topRightRadius: root.implicitHeight / 15 + bottomRightRadius: root.implicitHeight / 15 + } + + CustomRect { + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.topMargin: root.implicitHeight / 3 + anchors.bottomMargin: root.implicitHeight / 3 + + implicitWidth: parent.width - root.handle.x - root.handle.implicitWidth - root.implicitHeight / 6 + + color: DynamicColors.tPalette.m3surfaceContainer + radius: 1000 + topLeftRadius: root.implicitHeight / 15 + bottomLeftRadius: root.implicitHeight / 15 + } + } + + handle: CustomRect { + x: root.visualPosition * root.availableWidth - implicitWidth / 2 + + implicitWidth: root.implicitHeight / 4.5 + implicitHeight: root.implicitHeight + + color: DynamicColors.palette.m3primary + radius: 1000 + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.NoButton + cursorShape: Qt.PointingHandCursor + } + } +} diff --git a/Components/CustomText.qml b/Components/CustomText.qml new file mode 100644 index 0000000..eb72ca5 --- /dev/null +++ b/Components/CustomText.qml @@ -0,0 +1,48 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import qs.Config +import qs.Modules + +Text { + id: root + + property bool animate: false + property string animateProp: "scale" + property real animateFrom: 0 + property real animateTo: 1 + property int animateDuration: 400 + + renderType: Text.NativeRendering + textFormat: Text.PlainText + color: DynamicColors.palette.m3onSurface + font.family: "Rubik" + font.pointSize: 12 + + Behavior on color { + CAnim {} + } + + Behavior on text { + enabled: root.animate + + SequentialAnimation { + Anim { + to: root.animateFrom + easing.bezierCurve: MaterialEasing.standardAccel + } + PropertyAction {} + Anim { + to: root.animateTo + easing.bezierCurve: MaterialEasing.standardDecel + } + } + } + + component Anim: NumberAnimation { + target: root + property: root.animateProp + duration: root.animateDuration / 2 + easing.type: Easing.BezierSpline + } +} diff --git a/Components/StateLayer.qml b/Components/StateLayer.qml new file mode 100644 index 0000000..3f696a7 --- /dev/null +++ b/Components/StateLayer.qml @@ -0,0 +1,94 @@ +import qs.Config +import qs.Modules +import QtQuick + +MouseArea { + id: root + + property bool disabled + property color color: DynamicColors.palette.m3onSurface + property real radius: parent?.radius ?? 0 + property alias rect: hoverLayer + + function onClicked(): void { + } + + anchors.fill: parent + + enabled: !disabled + cursorShape: disabled ? undefined : Qt.PointingHandCursor + hoverEnabled: true + + onPressed: event => { + if (disabled) + return; + + rippleAnim.x = event.x; + rippleAnim.y = event.y; + + const dist = (ox, oy) => ox * ox + oy * oy; + rippleAnim.radius = Math.sqrt(Math.max(dist(event.x, event.y), dist(event.x, height - event.y), dist(width - event.x, event.y), dist(width - event.x, height - event.y))); + + rippleAnim.restart(); + } + + onClicked: event => !disabled && onClicked(event) + + SequentialAnimation { + id: rippleAnim + + property real x + property real y + property real radius + + PropertyAction { + target: ripple + property: "x" + value: rippleAnim.x + } + PropertyAction { + target: ripple + property: "y" + value: rippleAnim.y + } + PropertyAction { + target: ripple + property: "opacity" + value: 0.08 + } + Anim { + target: ripple + properties: "implicitWidth,implicitHeight" + from: 0 + to: rippleAnim.radius * 2 + easing.bezierCurve: MaterialEasing.standardDecel + } + Anim { + target: ripple + property: "opacity" + to: 0 + } + } + + CustomClippingRect { + id: hoverLayer + + anchors.fill: parent + + color: Qt.alpha(root.color, root.disabled ? 0 : root.pressed ? 0.1 : root.containsMouse ? 0.08 : 0) + radius: root.radius + + CustomRect { + id: ripple + + radius: 1000 + color: root.color + opacity: 0 + + transform: Translate { + x: -ripple.width / 2 + y: -ripple.height / 2 + } + } + } +} diff --git a/Modules/AudioPopup.qml b/Modules/AudioPopup.qml new file mode 100644 index 0000000..4c9ffec --- /dev/null +++ b/Modules/AudioPopup.qml @@ -0,0 +1,142 @@ +pragma ComponentBehavior: Bound + +import Quickshell +import Quickshell.Services.Pipewire +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import qs.Config +import qs.Components + +Item { + id: root + + implicitWidth: layout.implicitWidth + 10 * 2 + implicitHeight: layout.implicitHeight + 10 * 2 + + ButtonGroup { + id: sinks + } + + ButtonGroup { + id: sources + } + + ColumnLayout { + id: layout + + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + spacing: 12 + + CustomText { + text: qsTr("Output device") + font.weight: 500 + } + + Repeater { + model: Audio.sinks + + CustomRadioButton { + id: control + + required property PwNode modelData + + ButtonGroup.group: sinks + checked: Audio.sink?.id === modelData.id + onClicked: Audio.setAudioSink(modelData) + text: modelData.description + } + } + + CustomText { + Layout.topMargin: 10 + text: qsTr("Input device") + font.weight: 500 + } + + Repeater { + model: Audio.sources + + StyledRadioButton { + required property PwNode modelData + + ButtonGroup.group: sources + checked: Audio.source?.id === modelData.id + onClicked: Audio.setAudioSource(modelData) + text: modelData.description + } + } + + CustomText { + Layout.topMargin: 10 + Layout.bottomMargin: -7 / 2 + text: qsTr("Volume (%1)").arg(Audio.muted ? qsTr("Muted") : `${Math.round(Audio.volume * 100)}%`) + font.weight: 500 + } + + CustomMouseArea { + Layout.fillWidth: true + implicitHeight: 10 * 3 + + onWheel: event => { + if (event.angleDelta.y > 0) + Audio.incrementVolume(); + else if (event.angleDelta.y < 0) + Audio.decrementVolume(); + } + + CustomSlider { + anchors.left: parent.left + anchors.right: parent.right + implicitHeight: parent.implicitHeight + + value: Audio.volume + onMoved: Audio.setVolume(value) + + Behavior on value { + Anim {} + } + } + } + + CustomRect { + Layout.topMargin: 12 + visible: true + + implicitWidth: expandBtn.implicitWidth + 10 * 2 + implicitHeight: expandBtn.implicitHeight + 5 + + radius: 17 + color: DynamicColors.palette.m3primaryContainer + + StateLayer { + color: DynamicColors.palette.m3onPrimaryContainer + + function onClicked(): void { + root.wrapper.hasCurrent = false; + Quickshell.execDetached(["app2unit", "--", "pavucontrol"]); + } + } + + RowLayout { + id: expandBtn + + anchors.centerIn: parent + spacing: Appearance.spacing.small + + StyledText { + Layout.leftMargin: Appearance.padding.smaller + text: qsTr("Open settings") + color: Colours.palette.m3onPrimaryContainer + } + + MaterialIcon { + text: "chevron_right" + color: Colours.palette.m3onPrimaryContainer + font.pointSize: Appearance.font.size.large + } + } + } + } +} diff --git a/Modules/ResourceDetail.qml b/Modules/ResourceDetail.qml index 4df9d7a..41b3688 100644 --- a/Modules/ResourceDetail.qml +++ b/Modules/ResourceDetail.qml @@ -10,6 +10,9 @@ Item { required property int warningThreshold required property string details required property string iconString + property color barColor: Config.useDynamicColors ? DynamicColors.palette.m3primary : Config.accentColor.accents.primary + property color warningBarColor: Config.useDynamicColors ? DynamicColors.palette.m3error : Config.accentColor.accents.warning + property color textColor: Config.useDynamicColors ? DynamicColors.palette.m3onSurface : "#ffffff" height: columnLayout.childrenRect.height anchors.left: parent.left @@ -29,14 +32,14 @@ Item { font.family: "Material Symbols Rounded" font.pixelSize: 32 text: root.iconString - color: "#ffffff" + color: root.textColor } Text { anchors.verticalCenter: parent.verticalCenter text: root.resourceName font.pixelSize: 14 - color: "#ffffff" + color: root.textColor } } @@ -51,7 +54,7 @@ Item { width: parent.width * Math.min(root.percentage, 1) height: parent.height radius: height / 2 - color: root.percentage * 100 >= root.warningThreshold ? Config.accentColor.accents.warning : Config.accentColor.accents.primary + color: root.percentage * 100 >= root.warningThreshold ? root.warningBarColor : root.barColor Behavior on width { Anim { diff --git a/Modules/Resources.qml b/Modules/Resources.qml index ddf9c78..9c43b6d 100644 --- a/Modules/Resources.qml +++ b/Modules/Resources.qml @@ -1,3 +1,5 @@ +pragma ComponentBehavior: Bound + import QtQuick import Quickshell import QtQuick.Layouts @@ -93,49 +95,24 @@ Item { } } - MouseArea { - id: widgetMouseArea - anchors.fill: parent - hoverEnabled: true - onEntered: { - if (popoutLoader.sourceComponent === null) { - popoutLoader.sourceComponent = resourcePopout; - } - } - } - - Loader { - id: popoutLoader - sourceComponent: null - } - - component ResourcePopout: PanelWindow { + Item { id: popoutWindow + z: 0 property int rectHeight: contentRect.implicitHeight - WlrLayershell.exclusionMode: ExclusionMode.Ignore + anchors.fill: parent + visible: true - anchors { - left: true - top: true - right: true - bottom: true - } - - color: "transparent" - - mask: Region { item: contentRect } - - ShadowRect { - anchors.fill: contentRect - radius: 8 - } + // ShadowRect { + // anchors.fill: contentRect + // radius: 8 + // } ParallelAnimation { id: openAnim Anim { target: contentRect - property: "y" - to: 0 - 1 + property: "implicitHeight" + to: contentColumn.childrenRect.height + 20 duration: MaterialEasing.expressiveEffectsTime easing.bezierCurve: MaterialEasing.expressiveEffects } @@ -145,32 +122,24 @@ Item { id: closeAnim Anim { target: contentRect - property: "y" - to: - contentRect.implicitHeight + property: "implicitHeight" + to: 0 duration: MaterialEasing.expressiveEffectsTime easing.bezierCurve: MaterialEasing.expressiveEffects } - onStopped: { - popoutLoader.sourceComponent = null - } } Rectangle { id: contentRect - x: mapFromItem(root, 0, 0).x - y: - implicitHeight - implicitHeight: contentColumn.childrenRect.height + 20 - implicitWidth: root.implicitWidth - color: Config.baseBgColor - border.color: Config.baseBorderColor + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.bottom + color: Config.useDynamicColors ? DynamicColors.tPalette.m3surface : Config.baseBgColor + border.color: Config.useDynamicColors ? "transparent" : Config.baseBorderColor border.width: 1 bottomLeftRadius: 8 bottomRightRadius: 8 - - - Component.onCompleted: { - openAnim.start(); - } + clip: true Column { id: contentColumn @@ -184,8 +153,8 @@ Item { percentage: ResourceUsage.memoryUsedPercentage warningThreshold: 95 details: qsTr( "%1 of %2 MB used" ) - .arg( Math.round( ResourceUsage.memoryUsed * 0.001 )) - .arg( Math.round( ResourceUsage.memoryTotal * 0.001 )) + .arg( Math.round( ResourceUsage.memoryUsed * 0.001 )) + .arg( Math.round( ResourceUsage.memoryTotal * 0.001 )) } ResourceDetail { @@ -194,7 +163,7 @@ Item { percentage: ResourceUsage.cpuUsage warningThreshold: 95 details: qsTr( "%1% used" ) - .arg( Math.round( ResourceUsage.cpuUsage * 100 )) + .arg( Math.round( ResourceUsage.cpuUsage * 100 )) } ResourceDetail { @@ -203,7 +172,7 @@ Item { percentage: ResourceUsage.gpuUsage warningThreshold: 95 details: qsTr( "%1% used" ) - .arg( Math.round( ResourceUsage.gpuUsage * 100 )) + .arg( Math.round( ResourceUsage.gpuUsage * 100 )) } ResourceDetail { @@ -212,23 +181,24 @@ Item { percentage: ResourceUsage.gpuMemUsage warningThreshold: 95 details: qsTr( "%1% used" ) - .arg( Math.round( ResourceUsage.gpuMemUsage * 100 )) - } - } - MouseArea { - id: mouseArea - anchors.fill: parent - hoverEnabled: true - onExited: { - closeAnim.start(); + .arg( Math.round( ResourceUsage.gpuMemUsage * 100 )) } } } - } - - Component { - id: resourcePopout - - ResourcePopout { } + MouseArea { + id: widgetMouseArea + z: 1 + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: contentRect.bottom + hoverEnabled: true + onEntered: { + openAnim.start(); + } + onExited: { + closeAnim.start(); + } + } } }