quickshell: geometric melt into the frame column — one shape, cubic-morphed right side
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
dcf31fbe63
commit
1a71f2c07b
1 changed files with 60 additions and 72 deletions
|
|
@ -514,26 +514,22 @@ in
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Frame right-column inner border — starts below a flush-right
|
// Frame right-column inner border — always full: when a panel
|
||||||
// dropdown's bottom curve and follows the morph. The short y
|
// is merged, its own right-edge stroke sits exactly on this
|
||||||
// animation softens the dock/undock jump.
|
// line, so they coincide instead of needing a gap.
|
||||||
Rectangle {
|
Rectangle {
|
||||||
x: bar.width - Theme.frameWidth - Theme.borderWidth / 2
|
x: bar.width - Theme.frameWidth - Theme.borderWidth / 2
|
||||||
y: chrome.mergedRight ? 30 + chrome.height + 8 : 38
|
y: 38
|
||||||
width: Theme.borderWidth
|
width: Theme.borderWidth
|
||||||
height: Math.max(0, bar.height - Theme.frameWidth - 8 - y)
|
height: Math.max(0, bar.height - Theme.frameWidth - 8 - y)
|
||||||
color: Theme.base03
|
color: Theme.base03
|
||||||
Behavior on y {
|
|
||||||
NumberAnimation { duration: 90; easing.type: Easing.OutCubic }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Frame top-right inner corner — fades out while a flush-right
|
// Frame top-right inner corner — fades in sync with the
|
||||||
// dropdown is merged into the column there.
|
// panel's geometric melt into the column.
|
||||||
Shape {
|
Shape {
|
||||||
opacity: chrome.mergedRight ? 0 : 1
|
opacity: 1 - chrome.mergeP
|
||||||
visible: opacity > 0.01
|
visible: opacity > 0.01
|
||||||
Behavior on opacity { NumberAnimation { duration: 90 } }
|
|
||||||
preferredRendererType: Shape.CurveRenderer
|
preferredRendererType: Shape.CurveRenderer
|
||||||
ShapePath {
|
ShapePath {
|
||||||
fillColor: "transparent"
|
fillColor: "transparent"
|
||||||
|
|
@ -1209,15 +1205,29 @@ in
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Panel silhouette (caelestia-inspired): fill and border are
|
// Panel silhouette: fill and border are each ONE continuous
|
||||||
// each ONE continuous path — concave ear, side, rounded bottom
|
// path — concave ear, side, rounded bottom corners, side,
|
||||||
// corners, side, ear — so there are no seams or junctions at
|
// ear — so there are no seams or junctions at all. The border
|
||||||
// all. The border path is open at the top where the panel
|
// path is open at the top where the panel joins the bar.
|
||||||
// joins the bar; the bar's own border strips meet it there.
|
//
|
||||||
|
// `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 {
|
component PanelShape: Shape {
|
||||||
id: pshape
|
id: pshape
|
||||||
|
property real merge: 0
|
||||||
readonly property real ear: 8
|
readonly property real ear: 8
|
||||||
readonly property real rr: Math.min(8, Math.max(1, height / 2))
|
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
|
preferredRendererType: Shape.CurveRenderer
|
||||||
|
|
||||||
ShapePath {
|
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 }
|
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 }
|
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 }
|
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 }
|
PathLine { x: pshape.xr - pshape.ear; y: pshape.height }
|
||||||
PathArc { x: pshape.width - pshape.ear; y: pshape.height - pshape.rr; radiusX: pshape.rr; radiusY: pshape.rr; direction: PathArc.Counterclockwise }
|
PathCubic {
|
||||||
PathLine { x: pshape.width - pshape.ear; y: Math.min(pshape.ear, pshape.height) }
|
x: pshape.xr; y: pshape.bey
|
||||||
PathArc { x: pshape.width; y: 0; radiusX: pshape.ear; radiusY: pshape.ear; direction: PathArc.Clockwise }
|
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 }
|
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 }
|
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 }
|
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 }
|
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 }
|
PathLine { x: pshape.xr - pshape.ear; y: pshape.height }
|
||||||
PathArc { x: pshape.width - pshape.ear; y: pshape.height - pshape.rr; radiusX: pshape.rr; radiusY: pshape.rr; direction: PathArc.Counterclockwise }
|
PathCubic {
|
||||||
PathLine { x: pshape.width - pshape.ear; y: Math.min(pshape.ear, pshape.height) }
|
x: pshape.xr; y: pshape.bey
|
||||||
PathArc { x: pshape.width; y: 0; radiusX: pshape.ear; radiusY: pshape.ear; direction: PathArc.Clockwise }
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1365,22 +1356,19 @@ in
|
||||||
NumberAnimation { duration: 280; easing.type: Easing.OutExpo }
|
NumberAnimation { duration: 280; easing.type: Easing.OutExpo }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Crossfade between the floating and column-merged
|
// Continuous geometric melt instead of a shape swap: on
|
||||||
// silhouettes at dock/undock; the brief double-draw of
|
// dock the ear collapses, the edge slides the last 8px
|
||||||
// the (near-identical) bodies is imperceptible over blur.
|
// into the column and the bottom corner flips into the
|
||||||
PanelShape {
|
// flare — one shape the whole way.
|
||||||
opacity: chrome.mergedRight ? 0 : 1
|
property real mergeP: mergedRight ? 1 : 0
|
||||||
visible: opacity > 0.01
|
Behavior on mergeP {
|
||||||
Behavior on opacity { NumberAnimation { duration: 90 } }
|
NumberAnimation { duration: 150; easing.type: Easing.OutCubic }
|
||||||
width: chrome.width
|
|
||||||
height: chrome.height
|
|
||||||
}
|
}
|
||||||
PanelShapeFlush {
|
|
||||||
opacity: chrome.mergedRight ? 1 : 0
|
PanelShape {
|
||||||
visible: opacity > 0.01
|
|
||||||
Behavior on opacity { NumberAnimation { duration: 90 } }
|
|
||||||
width: chrome.width
|
width: chrome.width
|
||||||
height: chrome.height
|
height: chrome.height
|
||||||
|
merge: chrome.mergeP
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue