From d70fb61e4aa39547005f55d12d2e2aec65929489 Mon Sep 17 00:00:00 2001 From: zach Date: Thu, 8 Oct 2026 12:08:30 +0200 Subject: [PATCH] gemini glow fixes + positioning for window view --- .../Sidebar/Chat/ChatContent.qml | 12 +++- .../Notifications/Sidebar/Chat/ChatInput.qml | 6 +- .../Sidebar/Chat/Content/ChatHost.qml | 6 +- .../Sidebar/Chat/SidebarView.qml | 8 +-- Modules/Notifications/Sidebar/Content.qml | 60 +++++++++++++++++- assets/shaders/geminiglow.frag | 28 ++++++-- assets/shaders/geminiglow.frag.qsb | Bin 3721 -> 4278 bytes 7 files changed, 102 insertions(+), 18 deletions(-) diff --git a/Modules/Notifications/Sidebar/Chat/ChatContent.qml b/Modules/Notifications/Sidebar/Chat/ChatContent.qml index 66cbf63..3fdf1db 100644 --- a/Modules/Notifications/Sidebar/Chat/ChatContent.qml +++ b/Modules/Notifications/Sidebar/Chat/ChatContent.qml @@ -541,13 +541,16 @@ Item { bg.border.color: Colors.palette.m3outlineVariant bg.color: Colors.tPalette.m3surfaceContainerLowest fastGlow: Chat.busy + glow.opacity: input.activeFocus && (!Chat.busy || ChatState.isWindow) font: Tokens.font.body.small implicitHeight: Math.min(root.height / 5, contentHeight + topPadding + bottomPadding) focus: true placeholderText: qsTr("Send a message") sendIcon.font: Tokens.font.icon.medium - sendIcon.icon: "arrow_upward" + sendIcon.icon: Chat.busy ? "stop" : "arrow_upward" sendIcon.padding: Tokens.padding.extraSmall + sendIcon.opacity: text || Chat.busy ? 1.0 : 0.5 + sendIcon.enabled: text || Chat.busy Component.onCompleted: { root.focusInput(); @@ -568,6 +571,11 @@ Item { } } } - onSendPressed: root.send(text) + onButtonPressed: { + if (Chat.busy) + Chat.stop(); + else + root.send(text); + } } } diff --git a/Modules/Notifications/Sidebar/Chat/ChatInput.qml b/Modules/Notifications/Sidebar/Chat/ChatInput.qml index 34d2275..ceb4e77 100644 --- a/Modules/Notifications/Sidebar/Chat/ChatInput.qml +++ b/Modules/Notifications/Sidebar/Chat/ChatInput.qml @@ -8,10 +8,11 @@ TextAreaBase { readonly property alias bg: bg readonly property alias sendIcon: sendIcon + readonly property alias glow: glow property bool fastGlow: false - signal sendPressed + signal buttonPressed bottomPadding: Tokens.padding.large leftPadding: Tokens.padding.extraLarge @@ -39,6 +40,7 @@ TextAreaBase { GeminiGlow { id: glow glowSize: 32 + z: 5 opacity: root.activeFocus ? 1 : 0 visible: opacity > 0 @@ -131,7 +133,7 @@ TextAreaBase { } } - onClicked: root.sendPressed() + onClicked: root.buttonPressed() } TapHandler { diff --git a/Modules/Notifications/Sidebar/Chat/Content/ChatHost.qml b/Modules/Notifications/Sidebar/Chat/Content/ChatHost.qml index d708ce5..3ee1c15 100644 --- a/Modules/Notifications/Sidebar/Chat/Content/ChatHost.qml +++ b/Modules/Notifications/Sidebar/Chat/Content/ChatHost.qml @@ -34,8 +34,12 @@ Item { }); const attach = () => { - incubator.object.anchors.fill = container; + incubator.object.anchors.top = container.top; + incubator.object.anchors.bottom = container.bottom; + incubator.object.anchors.margins = Tokens.padding.medium; + incubator.object.anchors.horizontalCenter = container.horizontalCenter; incubator.object.requestClose.connect(root.requestClose); + incubator.object.implicitWidth = Qt.binding(() => Math.max(Math.min(container.width - Tokens.padding.medium * 2, 800), Config.sidebar.sizes.width)); currentItem = incubator.object; root.loading = false; if (!noAnim) diff --git a/Modules/Notifications/Sidebar/Chat/SidebarView.qml b/Modules/Notifications/Sidebar/Chat/SidebarView.qml index afc93d9..748a3ae 100644 --- a/Modules/Notifications/Sidebar/Chat/SidebarView.qml +++ b/Modules/Notifications/Sidebar/Chat/SidebarView.qml @@ -126,9 +126,8 @@ Item { z: 0 anchors.top: parent.top anchors.bottom: parent.bottom - anchors.margins: Tokens.padding.medium anchors.right: parent.right - implicitWidth: root.width - leftMargin - sidebar.anchors.margins - Tokens.spacing.medium * 2 - anchors.margins * 2 + implicitWidth: root.width - leftMargin - sidebar.anchors.margins - Tokens.spacing.medium * 2 states: State { name: "wide" @@ -151,10 +150,7 @@ Item { ChatHost { id: convHost - anchors.top: parent.top - anchors.bottom: parent.bottom - anchors.horizontalCenter: parent.horizontalCenter - implicitWidth: Math.max(Math.min(parent.width, 800), Config.sidebar.sizes.width) + anchors.fill: parent clip: true onLoadingChanged: if (!loading) diff --git a/Modules/Notifications/Sidebar/Content.qml b/Modules/Notifications/Sidebar/Content.qml index 1a6cea8..41a6728 100644 --- a/Modules/Notifications/Sidebar/Content.qml +++ b/Modules/Notifications/Sidebar/Content.qml @@ -1,6 +1,7 @@ pragma ComponentBehavior: Bound import ZShell.Config +import ZShell.Llm import QtQuick import QtQuick.Layouts import qs.Components @@ -35,10 +36,10 @@ Item { visible: height > 0 } - CustomClippingRect { + Item { Layout.fillHeight: true Layout.fillWidth: true - radius: Tokens.rounding.small + clip: true Item { id: pages @@ -58,6 +59,7 @@ Item { } CustomRect { + id: bg anchors.fill: parent color: Colors.tPalette.m3surfaceContainerLow radius: Tokens.rounding.small @@ -96,11 +98,63 @@ Item { CustomRect { anchors.fill: parent color: Colors.tPalette.m3surfaceContainerLow - radius: Tokens.rounding.small + radius: Tokens.rounding.large ChatPanel { anchors.fill: parent } + + GeminiGlow { + innerGlow: 50 + z: 5 + opacity: Chat.busy ? 1.0 : 0.0 + glowSize: 0 + visible: opacity > 0 && !ChatState.isWindow + + layer.enabled: true + layer.effect: OpacityMask { + maskSource: fadeMask + } + + Behavior on opacity { + Anim { + type: Anim.DefaultEffects + } + } + } + + Rectangle { + id: fadeMask + + readonly property real fadeStartPos: { + const h = Math.max(1, height); + const nh = (h - h / 3) / h; + return nh; + } + + anchors.fill: parent + layer.enabled: true + visible: false + + gradient: Gradient { + orientation: Gradient.Vertical + + GradientStop { + color: Qt.rgba(0, 0, 0, 0) + position: 0 + } + + GradientStop { + color: Qt.rgba(0, 0, 0, 0) + position: fadeMask.fadeStartPos + } + + GradientStop { + color: Qt.rgba(0, 0, 0, 1.0) + position: 1.0 + } + } + } } } } diff --git a/assets/shaders/geminiglow.frag b/assets/shaders/geminiglow.frag index ea19795..1edf7f7 100644 --- a/assets/shaders/geminiglow.frag +++ b/assets/shaders/geminiglow.frag @@ -50,6 +50,18 @@ void main() float r = clamp(radius, 0.0, min(rectSize.x, rectSize.y) * 0.5); float d = sdRoundBox(p, rectSize * 0.5, r); + // ~1px antialiasing width + float aa = max(fwidth(d), 0.75); + + // Coverage of the shape: 1 inside, 0 outside, soft across the edge + float inside = 1.0 - smoothstep(-aa, aa, d); + + // Only skip work when clearly outside and there's no outer glow + if (glowSize <= 0.0 && d > aa) { + fragColor = vec4(0.0); + return; + } + float ang = atan(p.y, p.x); float a2 = phase * TAU; float u = ang / TAU + 0.5 + phase; @@ -59,20 +71,28 @@ void main() float g = 0.0; if (d > 0.0) { float t = clamp(d / max(glowSize, 0.001), 0.0, 1.0); - g = pow(1.0 - t, 3.0); + g = (glowSize > 0.0) ? pow(1.0 - t, 3.0) : 0.0; } else if (innerGlow > 0.0) { - g = exp(d / innerGlow * 3.0); + float t = clamp(-d / innerGlow, 0.0, 1.0); + g = pow(1.0 - t, 3.0); } float pulse = 0.9 + 0.1 * sin(a2 * 2.0 + ang); g *= pulse; + // Line: inside only, with a soft inner edge AND a soft outer edge float line = 0.0; - if (lineWidth > 0.0) - line = 1.0 - smoothstep(0.0, lineWidth, abs(d)); + if (lineWidth > 0.0) { + float inner = 1.0 - smoothstep(lineWidth - aa, lineWidth + aa, -d); + line = inner * inside; + } float a = clamp(g * intensity + line * 0.9, 0.0, 1.0); + // Antialias the outer silhouette when there is no outer glow + if (glowSize <= 0.0) + a *= inside; + col = mix(col, vec3(1.0), line * 0.25); a += (hash(gl_FragCoord.xy) - 0.5) / 255.0; diff --git a/assets/shaders/geminiglow.frag.qsb b/assets/shaders/geminiglow.frag.qsb index 233240e6fd7552d50ef537e097b4a5c3c2609215..487934a0a09ce02c73c55682c7e6a8b00b3fd621 100644 GIT binary patch literal 4278 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