updated style

This commit is contained in:
2026-08-11 00:22:59 +02:00
parent 47ff5233bd
commit 0a439d951e
16 changed files with 1077 additions and 208 deletions
+23
View File
@@ -31,6 +31,29 @@ Singleton {
];
}
// Vertical counterpart: the *horizontal* edges slant instead, by a fraction
// of width. A tall rail slab has to lean this way — leaning its vertical
// edges by a fraction of its height would shear it clean off the screen.
function parallelogramV(w: real, h: real, lean: real): var {
const off = w * lean;
return [
Qt.point(0, off),
Qt.point(w, 0),
Qt.point(w, h - off),
Qt.point(0, h)
];
}
function parallelogramVL(w: real, h: real, lean: real): var {
const off = w * lean;
return [
Qt.point(0, 0),
Qt.point(w, off),
Qt.point(w, h),
Qt.point(0, h - off)
];
}
function rect(w: real, h: real): var {
return [Qt.point(0, 0), Qt.point(w, 0), Qt.point(w, h), Qt.point(0, h)];
}
+87 -13
View File
@@ -18,6 +18,11 @@ Item {
property real lean: Theme.skew
property bool leanLeft: false
// Tall panels (the right-hand rail) slant their horizontal edges instead,
// by a fraction of width, and inset their content top/bottom rather than
// left/right.
property bool vertical: false
// Corner chop size, and which corners get chopped (indices into the
// polygon, clockwise from the top-left). Default chops the two corners that
// read as "cut" against the lean.
@@ -37,10 +42,24 @@ Item {
// Inner glow along the top edge, for panels that should feel backlit.
property bool sheen: false
readonly property real leanInset: height * Math.abs(lean)
readonly property var polygon: leanLeft
? Geom.parallelogramL(width, height, lean)
: Geom.parallelogram(width, height, lean)
// The screen-print stack. A pure-black copy of the silhouette sits behind
// the fill, offset down-and-right, and the fill carries a black keyline
// outside its coloured border. Together these are what stop the panel from
// reading as a flat clip-path.
property bool drop: true
property real dropOffset: Theme.dropOffset
property color dropColor: Theme.ink
property bool keyline: true
property real keylineWidth: Theme.keylineWidth
readonly property real leanInset:
(vertical ? width : height) * Math.abs(lean)
readonly property var polygon: vertical
? (leanLeft ? Geom.parallelogramVL(width, height, lean)
: Geom.parallelogramV(width, height, lean))
: (leanLeft ? Geom.parallelogramL(width, height, lean)
: Geom.parallelogram(width, height, lean))
// Child content lives inside `contentItem`, inset on both sides far enough
// to clear the slanted edges. Panels do not auto-size: callers set implicit
@@ -52,14 +71,57 @@ Item {
readonly property real contentPad: padding + leanInset
// Offset black copy of the silhouette, behind everything.
Loader {
anchors.fill: parent
active: root.drop && root.dropOffset > 0
sourceComponent: Shape {
preferredRendererType: Shape.CurveRenderer
asynchronous: false
transform: Translate {
x: root.dropOffset * (root.leanLeft ? -1 : 1)
y: root.dropOffset
}
ShapePath {
fillColor: root.dropColor
strokeColor: "transparent"
joinStyle: ShapePath.MiterJoin
PathPolyline {
path: Geom.chamfer(root.polygon, root.chop, root.chopCorners)
}
}
}
}
Shape {
id: body
anchors.fill: parent
preferredRendererType: Shape.CurveRenderer
asynchronous: false
// Fill, carrying the black keyline. The stroke is centred on the path,
// so half of it sits outside the silhouette — which is exactly the
// heavy outer contour the style wants.
ShapePath {
fillColor: Theme.alpha(root.fill, root.fillOpacity)
strokeColor: root.keyline ? root.dropColor : "transparent"
strokeWidth: root.keyline
? root.borderWidth + root.keylineWidth * 2
: 0
joinStyle: ShapePath.MiterJoin
capStyle: ShapePath.FlatCap
PathPolyline {
path: Geom.chamfer(root.polygon, root.chop, root.chopCorners)
}
}
// Coloured border, drawn on top of the keyline so it reads as an inlay.
ShapePath {
fillColor: "transparent"
strokeColor: root.borderWidth > 0 ? root.border : "transparent"
strokeWidth: root.borderWidth
joinStyle: ShapePath.MiterJoin
@@ -141,13 +203,25 @@ Item {
strokeColor: "transparent"
PathPolyline {
path: {
const t = root.slashWidth;
// On a rail slab the slash runs along the slanted top
// edge instead of down a side.
if (root.vertical) {
const voff = root.width * root.lean;
return root.leanLeft
? Geom.closed([Qt.point(0, 0), Qt.point(root.width, voff),
Qt.point(root.width, voff + t), Qt.point(0, t)])
: Geom.closed([Qt.point(0, voff), Qt.point(root.width, 0),
Qt.point(root.width, t), Qt.point(0, voff + t)]);
}
const off = root.height * root.lean;
const w = root.slashWidth;
return root.leanLeft
? Geom.closed([Qt.point(0, 0), Qt.point(w, 0),
Qt.point(off + w, root.height), Qt.point(off, root.height)])
: Geom.closed([Qt.point(off, 0), Qt.point(off + w, 0),
Qt.point(w, root.height), Qt.point(0, root.height)]);
? Geom.closed([Qt.point(0, 0), Qt.point(t, 0),
Qt.point(off + t, root.height), Qt.point(off, root.height)])
: Geom.closed([Qt.point(off, 0), Qt.point(off + t, 0),
Qt.point(t, root.height), Qt.point(0, root.height)]);
}
}
}
@@ -157,9 +231,9 @@ Item {
Item {
id: contentHolder
anchors.fill: parent
anchors.leftMargin: root.contentPad
anchors.rightMargin: root.contentPad
anchors.topMargin: root.padding
anchors.bottomMargin: root.padding
anchors.leftMargin: root.vertical ? root.padding : root.contentPad
anchors.rightMargin: root.vertical ? root.padding : root.contentPad
anchors.topMargin: root.vertical ? root.contentPad : root.padding
anchors.bottomMargin: root.vertical ? root.contentPad : root.padding
}
}
+28 -6
View File
@@ -27,12 +27,25 @@ PopupWindow {
signal dismissedFully()
// Panels hanging off the vertical rail open sideways, so the entry slide has
// to follow whichever axis the popout actually travels along, and start on
// the side nearest its trigger.
readonly property bool horizontal:
fromEdge === Edges.Left || fromEdge === Edges.Right
readonly property string slideProperty: horizontal ? "x" : "y"
readonly property real slideOrigin: bleed + (fromEdge === Edges.Left
|| fromEdge === Edges.Top ? 14 : -14)
anchor.item: anchorItem
anchor.edges: fromEdge
anchor.gravity: fromEdge
anchor.adjustment: PopupAdjustment.SlideX
anchor.adjustment: horizontal
? PopupAdjustment.SlideY
: PopupAdjustment.SlideX
anchor.margins.top: Theme.popoutGap
anchor.margins.bottom: Theme.popoutGap
anchor.margins.left: Theme.popoutGap
anchor.margins.right: Theme.popoutGap
implicitWidth: contentWidth + bleed * 2
implicitHeight: contentHeight + bleed * 2
@@ -83,7 +96,14 @@ PopupWindow {
height: root.contentHeight
opacity: 0
transformOrigin: Item.Top
transformOrigin: {
switch (root.fromEdge) {
case Edges.Left: return Item.Right;
case Edges.Right: return Item.Left;
case Edges.Top: return Item.Bottom;
default: return Item.Top;
}
}
scale: 0.94
// The handler has to live on the content item, not the window: a
@@ -105,8 +125,8 @@ PopupWindow {
to: 1.0; duration: Theme.durSlow; easing.type: Easing.OutBack
}
NumberAnimation {
target: contentHolder; property: "y"
from: root.bleed - 14; to: root.bleed
target: contentHolder; property: root.slideProperty
from: root.slideOrigin; to: root.bleed
duration: Theme.durSlow; easing.type: Easing.OutBack
}
}
@@ -122,12 +142,14 @@ PopupWindow {
to: 0.96; duration: Theme.durFast; easing.type: Easing.InQuad
}
NumberAnimation {
target: contentHolder; property: "y"
to: root.bleed - 8; duration: Theme.durFast; easing.type: Easing.InQuad
target: contentHolder; property: root.slideProperty
to: root.bleed + (root.slideOrigin - root.bleed) * 0.6
duration: Theme.durFast; easing.type: Easing.InQuad
}
onFinished: {
root.shown = false;
root.pinned = false;
contentHolder.x = root.bleed;
contentHolder.y = root.bleed;
root.dismissedFully();
}