diff --git a/settings/quickshell.nix b/settings/quickshell.nix index 7b0698e..83a167f 100644 --- a/settings/quickshell.nix +++ b/settings/quickshell.nix @@ -1474,22 +1474,31 @@ in // the grab — it self-clears and instantly closes the panel. WlrLayershell.keyboardFocus: (launcherPanel.open || bar.activeDropdown !== null) && !bar.screenshotPinned ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None - // Vertical bar on the left edge. The surface still covers the - // whole screen (the frame band and every panel are drawn into - // it); only the reserved strip is the bar itself. + // Vertical bar on the left edge. anchors { top: true left: true bottom: true } - // DIAGNOSTIC — normally bar.screen.width, because the frame - // band and every panel are drawn into this one surface. - // Shrinking it to the bar column clips all of that, which - // looks broken on purpose: the point is to measure whether - // blur and the SDF shader are expensive because of this - // surface's AREA. Revert this line to restore the shell. - implicitWidth: Theme.barWidth + // True while something is drawn outside the bar column and the + // surface therefore has to span the screen. + readonly property bool surfaceWide: bar.activeDropdown !== null + || launcherPanel.visible || toastItem.visible + + // THE performance knob for this whole shell. Hyprland blurs + // this namespace, and blur behind a surface is recomputed + // whenever what is BEHIND it changes — which, with a game + // running underneath, is every pixel of the screen every + // frame. At 3440x1440 that measured ~a third of the card and + // halved framerates; at 46px it is free. So the surface is the + // bar column at rest and only grows while a panel, the toast + // or the launcher is actually on screen, where a momentary + // cost doesn't matter. + // + // exclusiveZone stays fixed at barWidth, so windows never + // reflow when this resizes. + implicitWidth: bar.surfaceWide ? bar.screen.width : Theme.barWidth exclusiveZone: Theme.barWidth color: "transparent" @@ -1977,9 +1986,9 @@ in // buries a full corner radius inside the bar column. readonly property real surfLeftX: Theme.barWidth - meltInto / 2 property vector4d cutout: Qt.vector4d( - (Theme.barWidth + bar.width - Theme.frameWidth) / 2, + (Theme.barWidth + bar.screen.width - Theme.frameWidth) / 2, bar.height / 2, - (bar.width - Theme.frameWidth - Theme.barWidth) / 2, + (bar.screen.width - Theme.frameWidth - Theme.barWidth) / 2, bar.height / 2 - Theme.frameWidth) property vector4d panel: chrome.visible ? Qt.vector4d(surfLeftX + chrome.width / 2, (panelTop + panelBottom) / 2, @@ -2044,7 +2053,7 @@ in NumberAnimation { duration: Theme.animMorph; easing.type: Easing.OutExpo } } - x: Math.round((bar.width - openW) / 2) + x: Math.round((bar.screen.width - openW) / 2) y: bar.height - Theme.frameWidth - openH width: openW height: openH @@ -6802,6 +6811,64 @@ in } } + // ── Screen frame band, on its OWN background-layer surface. + // + // This used to be drawn into the bar's surface, and it is the + // reason that surface had to be screen-sized — which is what + // made blur behind it cost a third of the GPU. The frame is + // static decoration that never takes input, so it belongs + // below windows: no blur rule matches this namespace, nothing + // ever repaints it, and gaps_out keeps it visible. + // + // Hidden (the shader, not the window — mapping and unmapping a + // layer surface on every panel open would flicker) while the + // bar's own surface is wide, because chromeSdf then draws the + // frame itself and melts panels into it. Two translucent + // copies of the same band would read as a darker strip. + PanelWindow { + id: frameWin + screen: bar.screen + WlrLayershell.namespace: "quickshell-frame" + WlrLayershell.layer: WlrLayer.Background + WlrLayershell.keyboardFocus: WlrKeyboardFocus.None + exclusionMode: ExclusionMode.Ignore + anchors { top: true; left: true; right: true; bottom: true } + color: "transparent" + mask: Region {} // decoration only, never takes a click + + ShaderEffect { + // Offset past the bar column so this and the bar never + // composite on top of each other. + x: Theme.barWidth + width: frameWin.width - Theme.barWidth + height: frameWin.height + visible: !bar.surfaceWide + + // Push the cutout's left edge well off-item so only + // straight edges reach the bar column — a rounded + // corner landing here would leave a nub beside the bar. + readonly property real cutLeft: -2 * Theme.radius + readonly property real cutRight: width - Theme.frameWidth + property vector4d cutout: Qt.vector4d( + (cutLeft + cutRight) / 2, + height / 2, + (cutRight - cutLeft) / 2, + height / 2 - Theme.frameWidth) + // No panels down here — the bar's surface owns those. + property vector4d panel: Qt.vector4d(0, 0, 0, 0) + property vector4d toast: Qt.vector4d(0, 0, 0, 0) + property vector4d launcher: Qt.vector4d(0, 0, 0, 0) + property vector4d fillColor: Qt.vector4d(Theme.barBg.r, Theme.barBg.g, Theme.barBg.b, Theme.barBg.a) + property vector4d borderColor: Qt.vector4d(Theme.base03.r, Theme.base03.g, Theme.base03.b, 1) + property vector2d res: Qt.vector2d(width, height) + property real cutoutR: Theme.radius + property real panelR: Theme.radius + property real meltK: Theme.radius / 2 + property real borderW: Theme.borderWidth + fragmentShader: "file://${chromeShader}" + } + } + // ── OSD: volume / brightness level, shown inline in the bar's // empty middle — the gap between the two clusters exists for // this. Volume is reactive: Pipewire reports changes from any