diff --git a/Modules/Background/Corner.qml b/Modules/Background/Corner.qml new file mode 100644 index 0000000..dfd5b38 --- /dev/null +++ b/Modules/Background/Corner.qml @@ -0,0 +1,129 @@ +import QtQuick +import QtQuick.Effects +import Quickshell +import Quickshell.Wayland +import qs.Services + +ShellRoot { + id: root + + // Visible ring color + property color ringColor: Colors.backgroundPrimary + // The amount subtracted from full size for the inner cutout + // Inner size = full size - borderWidth (per axis) + property int borderWidth: Style.borderMedium + // Rounded radius for the inner cutout + property int innerRadius: 20 + + Variants { + model: Quickshell.screens + + PanelWindow { + required property ShellScreen modelData + + anchors { + top: true + bottom: true + left: true + right: true + } + margins { + top: Math.round(Style.barHeight * Scaling.scale(screen)) + } + color: "transparent" + screen: modelData + WlrLayershell.exclusionMode: ExclusionMode.Ignore + WlrLayershell.namespace: "quickshell-corner" + // Do not take keyboard focus and make the surface click-through + WlrLayershell.keyboardFocus: WlrKeyboardFocus.None + mask: Region {} + + // Source we want to show only as a ring + Rectangle { + id: overlaySource + anchors.fill: parent + color: root.ringColor + } + + // Texture for overlaySource + ShaderEffectSource { + id: overlayTexture + anchors.fill: parent + sourceItem: overlaySource + hideSource: true + live: true + visible: false + } + + // Mask via Canvas: paint opaque white, then punch rounded inner hole + Canvas { + id: maskSource + anchors.fill: parent + antialiasing: true + renderTarget: Canvas.FramebufferObject + onPaint: function() { + const ctx = getContext("2d"); + ctx.reset(); + ctx.clearRect(0, 0, width, height); + // Solid white base (alpha=1) + ctx.globalCompositeOperation = "source-over"; + ctx.fillStyle = "#ffffffff"; + ctx.fillRect(0, 0, width, height); + + // Punch hole using destination-out with rounded rect path + const x = Math.round(root.borderWidth / 2); + const y = Math.round(root.borderWidth / 2); + const w = Math.max(0, width - root.borderWidth); + const h = Math.max(0, height - root.borderWidth); + const r = Math.max(0, Math.min(root.innerRadius, Math.min(w, h) / 2)); + + ctx.globalCompositeOperation = "destination-out"; + ctx.fillStyle = "#ffffffff"; + ctx.beginPath(); + // rounded rectangle path using arcTo + ctx.moveTo(x + r, y); + ctx.lineTo(x + w - r, y); + ctx.arcTo(x + w, y, x + w, y + r, r); + ctx.lineTo(x + w, y + h - r); + ctx.arcTo(x + w, y + h, x + w - r, y + h, r); + ctx.lineTo(x + r, y + h); + ctx.arcTo(x, y + h, x, y + h - r, r); + ctx.lineTo(x, y + r); + ctx.arcTo(x, y, x + r, y, r); + ctx.closePath(); + ctx.fill(); + } + onWidthChanged: requestPaint() + onHeightChanged: requestPaint() + } + + // Repaint mask when properties change + Connections { + target: root + function onBorderWidthChanged() { maskSource.requestPaint() } + function onRingColorChanged() { /* no-op for mask */ } + function onInnerRadiusChanged() { maskSource.requestPaint() } + } + + // Texture for maskSource; hides the original + ShaderEffectSource { + id: maskTexture + anchors.fill: parent + sourceItem: maskSource + hideSource: true + live: true + visible: false + } + + // Apply mask to show only the ring area + MultiEffect { + anchors.fill: parent + source: overlayTexture + maskEnabled: true + maskSource: maskTexture + maskInverted: false + } + } + } +} + diff --git a/Modules/Bar/Bar.qml b/Modules/Bar/Bar.qml index f4e7223..2161a54 100644 --- a/Modules/Bar/Bar.qml +++ b/Modules/Bar/Bar.qml @@ -85,7 +85,7 @@ PanelWindow { icon: "widgets" anchors.verticalCenter: parent.verticalCenter onClicked: function () { - sidePanel.isLoaded = !demoPanel.isLoaded + sidePanel.isLoaded = !sidePanel.isLoaded } } } diff --git a/Modules/SidePanel/.gitkeep b/Modules/SidePanel/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/shell.qml b/shell.qml index 5a4e30e..adac8ca 100644 --- a/shell.qml +++ b/shell.qml @@ -26,6 +26,8 @@ ShellRoot { Background {} Overview {} + Corner{} + DemoPanel { id: demoPanel }