From 1a71f2c07b23b150707d2d064ff0ff4b272908d8 Mon Sep 17 00:00:00 2001 From: rope Date: Fri, 12 Jun 2026 09:21:02 +0100 Subject: [PATCH] =?UTF-8?q?quickshell:=20geometric=20melt=20into=20the=20f?= =?UTF-8?q?rame=20column=20=E2=80=94=20one=20shape,=20cubic-morphed=20righ?= =?UTF-8?q?t=20side?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- settings/quickshell.nix | 132 ++++++++++++++++++---------------------- 1 file changed, 60 insertions(+), 72 deletions(-) diff --git a/settings/quickshell.nix b/settings/quickshell.nix index 81f2639..f1bc8b0 100644 --- a/settings/quickshell.nix +++ b/settings/quickshell.nix @@ -514,26 +514,22 @@ in } } - // Frame right-column inner border — starts below a flush-right - // dropdown's bottom curve and follows the morph. The short y - // animation softens the dock/undock jump. + // Frame right-column inner border — always full: when a panel + // is merged, its own right-edge stroke sits exactly on this + // line, so they coincide instead of needing a gap. Rectangle { x: bar.width - Theme.frameWidth - Theme.borderWidth / 2 - y: chrome.mergedRight ? 30 + chrome.height + 8 : 38 + y: 38 width: Theme.borderWidth height: Math.max(0, bar.height - Theme.frameWidth - 8 - y) color: Theme.base03 - Behavior on y { - NumberAnimation { duration: 90; easing.type: Easing.OutCubic } - } } - // Frame top-right inner corner — fades out while a flush-right - // dropdown is merged into the column there. + // Frame top-right inner corner — fades in sync with the + // panel's geometric melt into the column. Shape { - opacity: chrome.mergedRight ? 0 : 1 + opacity: 1 - chrome.mergeP visible: opacity > 0.01 - Behavior on opacity { NumberAnimation { duration: 90 } } preferredRendererType: Shape.CurveRenderer ShapePath { fillColor: "transparent" @@ -1209,15 +1205,29 @@ in } } - // Panel silhouette (caelestia-inspired): fill and border are - // each ONE continuous path — concave ear, side, rounded bottom - // corners, side, ear — so there are no seams or junctions at - // all. The border path is open at the top where the panel - // joins the bar; the bar's own border strips meet it there. + // Panel silhouette: fill and border are each ONE continuous + // path — concave ear, side, rounded bottom corners, side, + // ear — so there are no seams or junctions at all. The border + // path is open at the top where the panel joins the bar. + // + // `merge` (0..1) continuously morphs the RIGHT side between + // floating (concave top ear, convex bottom corner at an 8px + // inset) and merged-into-the-frame-column (edge flush, ear + // collapsed, bottom corner flipped into a concave flare). + // The right side is built from cubics (kappa 0.5523) because + // they degrade gracefully through the zero-radius midpoint, + // where arcs would degenerate. component PanelShape: Shape { id: pshape + property real merge: 0 readonly property real ear: 8 readonly property real rr: Math.min(8, Math.max(1, height / 2)) + // Right-side morph geometry + readonly property real xr: width - ear * (1 - merge) // right edge x + readonly property real re: ear * (1 - merge) // top ear radius + readonly property real rek: re * 0.5523 + readonly property real bey: height - ear + 2 * ear * merge // bottom feature endpoint y + readonly property real bck: ear * 0.5523 * (1 - 2 * merge) // endpoint control offset; sign flips at the melt point preferredRendererType: Shape.CurveRenderer ShapePath { @@ -1227,10 +1237,18 @@ in PathArc { x: pshape.ear; y: Math.min(pshape.ear, pshape.height); radiusX: pshape.ear; radiusY: pshape.ear; direction: PathArc.Clockwise } PathLine { x: pshape.ear; y: pshape.height - pshape.rr } PathArc { x: pshape.ear + pshape.rr; y: pshape.height; radiusX: pshape.rr; radiusY: pshape.rr; direction: PathArc.Counterclockwise } - PathLine { x: pshape.width - pshape.ear - pshape.rr; y: pshape.height } - PathArc { x: pshape.width - pshape.ear; y: pshape.height - pshape.rr; radiusX: pshape.rr; radiusY: pshape.rr; direction: PathArc.Counterclockwise } - PathLine { x: pshape.width - pshape.ear; y: Math.min(pshape.ear, pshape.height) } - PathArc { x: pshape.width; y: 0; radiusX: pshape.ear; radiusY: pshape.ear; direction: PathArc.Clockwise } + PathLine { x: pshape.xr - pshape.ear; y: pshape.height } + PathCubic { + x: pshape.xr; y: pshape.bey + control1X: pshape.xr - pshape.ear + 4.42; control1Y: pshape.height + control2X: pshape.xr; control2Y: pshape.bey + pshape.bck + } + PathLine { x: pshape.xr; y: Math.min(pshape.re, pshape.height) } + PathCubic { + x: pshape.xr + pshape.re; y: 0 + control1X: pshape.xr; control1Y: Math.max(0, Math.min(pshape.re, pshape.height) - pshape.rek) + control2X: pshape.xr + pshape.re - pshape.rek; control2Y: 0 + } PathLine { x: 0; y: 0 } } @@ -1243,45 +1261,18 @@ in PathArc { x: pshape.ear; y: Math.min(pshape.ear, pshape.height); radiusX: pshape.ear; radiusY: pshape.ear; direction: PathArc.Clockwise } PathLine { x: pshape.ear; y: pshape.height - pshape.rr } PathArc { x: pshape.ear + pshape.rr; y: pshape.height; radiusX: pshape.rr; radiusY: pshape.rr; direction: PathArc.Counterclockwise } - PathLine { x: pshape.width - pshape.ear - pshape.rr; y: pshape.height } - PathArc { x: pshape.width - pshape.ear; y: pshape.height - pshape.rr; radiusX: pshape.rr; radiusY: pshape.rr; direction: PathArc.Counterclockwise } - PathLine { x: pshape.width - pshape.ear; y: Math.min(pshape.ear, pshape.height) } - PathArc { x: pshape.width; y: 0; radiusX: pshape.ear; radiusY: pshape.ear; direction: PathArc.Clockwise } - } - } - - // Flush-right variant: no right ear; the bottom-right curve - // merges the panel into the screen frame's right column. - component PanelShapeFlush: Shape { - id: pshapeF - readonly property real ear: 8 - readonly property real rr: Math.min(8, Math.max(1, height / 2)) - preferredRendererType: Shape.CurveRenderer - - ShapePath { - fillColor: Theme.barBg - strokeWidth: -1 - startX: 0; startY: 0 - PathArc { x: pshapeF.ear; y: Math.min(pshapeF.ear, pshapeF.height); radiusX: pshapeF.ear; radiusY: pshapeF.ear; direction: PathArc.Clockwise } - PathLine { x: pshapeF.ear; y: pshapeF.height - pshapeF.rr } - PathArc { x: pshapeF.ear + pshapeF.rr; y: pshapeF.height; radiusX: pshapeF.rr; radiusY: pshapeF.rr; direction: PathArc.Counterclockwise } - PathLine { x: pshapeF.width - pshapeF.ear; y: pshapeF.height } - PathArc { x: pshapeF.width; y: pshapeF.height + pshapeF.ear; radiusX: pshapeF.ear; radiusY: pshapeF.ear; direction: PathArc.Clockwise } - PathLine { x: pshapeF.width; y: 0 } - PathLine { x: 0; y: 0 } - } - - ShapePath { - fillColor: "transparent" - strokeColor: Theme.base03 - strokeWidth: Theme.borderWidth - capStyle: ShapePath.FlatCap - startX: 0; startY: 0 - PathArc { x: pshapeF.ear; y: Math.min(pshapeF.ear, pshapeF.height); radiusX: pshapeF.ear; radiusY: pshapeF.ear; direction: PathArc.Clockwise } - PathLine { x: pshapeF.ear; y: pshapeF.height - pshapeF.rr } - PathArc { x: pshapeF.ear + pshapeF.rr; y: pshapeF.height; radiusX: pshapeF.rr; radiusY: pshapeF.rr; direction: PathArc.Counterclockwise } - PathLine { x: pshapeF.width - pshapeF.ear; y: pshapeF.height } - PathArc { x: pshapeF.width; y: pshapeF.height + pshapeF.ear; radiusX: pshapeF.ear; radiusY: pshapeF.ear; direction: PathArc.Clockwise } + PathLine { x: pshape.xr - pshape.ear; y: pshape.height } + PathCubic { + x: pshape.xr; y: pshape.bey + control1X: pshape.xr - pshape.ear + 4.42; control1Y: pshape.height + control2X: pshape.xr; control2Y: pshape.bey + pshape.bck + } + PathLine { x: pshape.xr; y: Math.min(pshape.re, pshape.height) } + PathCubic { + x: pshape.xr + pshape.re; y: 0 + control1X: pshape.xr; control1Y: Math.max(0, Math.min(pshape.re, pshape.height) - pshape.rek) + control2X: pshape.xr + pshape.re - pshape.rek; control2Y: 0 + } } } @@ -1365,22 +1356,19 @@ in NumberAnimation { duration: 280; easing.type: Easing.OutExpo } } - // Crossfade between the floating and column-merged - // silhouettes at dock/undock; the brief double-draw of - // the (near-identical) bodies is imperceptible over blur. - PanelShape { - opacity: chrome.mergedRight ? 0 : 1 - visible: opacity > 0.01 - Behavior on opacity { NumberAnimation { duration: 90 } } - width: chrome.width - height: chrome.height + // Continuous geometric melt instead of a shape swap: on + // dock the ear collapses, the edge slides the last 8px + // into the column and the bottom corner flips into the + // flare — one shape the whole way. + property real mergeP: mergedRight ? 1 : 0 + Behavior on mergeP { + NumberAnimation { duration: 150; easing.type: Easing.OutCubic } } - PanelShapeFlush { - opacity: chrome.mergedRight ? 1 : 0 - visible: opacity > 0.01 - Behavior on opacity { NumberAnimation { duration: 90 } } + + PanelShape { width: chrome.width height: chrome.height + merge: chrome.mergeP } }