feat: swing the pill's padlock open when it unlocks
This commit is contained in:
1 parent
2ab4122c05
commit
aeb9aa89d5
3 files changed
+70
-35
No files matched your search
@@ -231,6 +231,7 @@ Item {
|
|||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
x: 16
|
x: 16
|
||||||
open: root.shownPhase === "success"
|
open: root.shownPhase === "success"
|
||||||
|
pace: root.pace
|
||||||
color: root.shownPhase === "failure" || root.shownPhase === "lockout" ? Theme.failure : Theme.textPrimary
|
color: root.shownPhase === "failure" || root.shownPhase === "lockout" ? Theme.failure : Theme.textPrimary
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -411,6 +411,7 @@ Item {
|
|||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
width: parent.width * 0.42
|
width: parent.width * 0.42
|
||||||
height: width
|
height: width
|
||||||
|
pace: root.pace
|
||||||
color: root.color
|
color: root.color
|
||||||
opacity: root.mode === "lockout" ? 1 : 0
|
opacity: root.mode === "lockout" ? 1 : 0
|
||||||
scale: root.mode === "lockout" ? 1 : 0.7
|
scale: root.mode === "lockout" ? 1 : 0.7
|
||||||
|
|||||||
+68
-35
@@ -1,6 +1,8 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
//
|
//
|
||||||
// A padlock whose shackle lifts and swings open.
|
// A padlock that opens the way a real one does: the shackle jumps up, its
|
||||||
|
// left leg coming out of the body, and swings round its right leg to the
|
||||||
|
// other side.
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Shapes
|
import QtQuick.Shapes
|
||||||
@@ -10,9 +12,64 @@ Item {
|
|||||||
|
|
||||||
property color color: Theme.textPrimary
|
property color color: Theme.textPrimary
|
||||||
property bool open: false
|
property bool open: false
|
||||||
|
property real pace: 1
|
||||||
|
|
||||||
readonly property real u: width / 100
|
readonly property real u: width / 100
|
||||||
|
|
||||||
|
// 0 shut, 1 open. Everything below follows from it. Only opening is
|
||||||
|
// animated: it is shut again for the next scan, and the screen was
|
||||||
|
// locked all along, so showing it lock would make no sense.
|
||||||
|
property real openness: 0
|
||||||
|
onOpenChanged: {
|
||||||
|
if (open) {
|
||||||
|
opening.restart()
|
||||||
|
} else {
|
||||||
|
opening.stop()
|
||||||
|
openness = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NumberAnimation {
|
||||||
|
id: opening
|
||||||
|
target: root
|
||||||
|
property: "openness"
|
||||||
|
to: 1
|
||||||
|
duration: 560 * root.pace
|
||||||
|
}
|
||||||
|
|
||||||
|
function smooth(from, to, p) {
|
||||||
|
const x = Math.max(0, Math.min(1, (p - from) / (to - from)))
|
||||||
|
return x * x * (3 - 2 * x)
|
||||||
|
}
|
||||||
|
// Up first, then round, a little past the other side and back.
|
||||||
|
readonly property real lift: 12 * smooth(0, 0.3, openness)
|
||||||
|
readonly property real swing: {
|
||||||
|
const p = Math.max(0, Math.min(1, (openness - 0.22) / 0.78))
|
||||||
|
const s = 1.4
|
||||||
|
return 180 * (1 + (s + 1) * Math.pow(p - 1, 3) + s * Math.pow(p - 1, 2))
|
||||||
|
}
|
||||||
|
// A small beat as it comes open.
|
||||||
|
scale: 1 + 0.14 * Math.sin(Math.PI * smooth(0.55, 1, openness))
|
||||||
|
|
||||||
|
// The shackle as points: up the left leg, over the top, down the right
|
||||||
|
// leg, which stays in the body. Swung about that leg, everything else
|
||||||
|
// narrows towards it and comes out on the other side.
|
||||||
|
function shackle() {
|
||||||
|
const right = 68.5, radius = 18.5
|
||||||
|
const top = 28 - lift
|
||||||
|
const narrow = Math.cos(swing * Math.PI / 180)
|
||||||
|
const points = []
|
||||||
|
const add = (x, y) => points.push(Qt.point((right + (x - right) * narrow) * u, y * u))
|
||||||
|
add(31.5, 56 - 18 * smooth(0, 0.3, openness))
|
||||||
|
add(31.5, top)
|
||||||
|
for (let i = 1; i < 24; ++i) {
|
||||||
|
const a = Math.PI + Math.PI * i / 24
|
||||||
|
add(50 + radius * Math.cos(a), top + radius * Math.sin(a))
|
||||||
|
}
|
||||||
|
add(right, top)
|
||||||
|
add(right, 56)
|
||||||
|
return points
|
||||||
|
}
|
||||||
|
|
||||||
// Body
|
// Body
|
||||||
Rectangle {
|
Rectangle {
|
||||||
x: 14 * root.u
|
x: 14 * root.u
|
||||||
@@ -23,41 +80,17 @@ Item {
|
|||||||
color: root.color
|
color: root.color
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shackle: a U standing on the body. Opening lifts it and swings it about
|
Shape {
|
||||||
// its right leg.
|
anchors.fill: parent
|
||||||
Item {
|
preferredRendererType: Shape.CurveRenderer
|
||||||
id: shackle
|
|
||||||
x: 26 * root.u
|
|
||||||
y: 4 * root.u
|
|
||||||
width: 48 * root.u
|
|
||||||
height: 52 * root.u
|
|
||||||
transformOrigin: Item.BottomRight
|
|
||||||
|
|
||||||
property real lift: root.open ? 7 * root.u : 0
|
ShapePath {
|
||||||
Behavior on lift { NumberAnimation { duration: 260; easing.type: Easing.OutBack } }
|
strokeColor: root.color
|
||||||
rotation: root.open ? -28 : 0
|
strokeWidth: 11 * root.u
|
||||||
Behavior on rotation { NumberAnimation { duration: 320; easing.type: Easing.OutBack } }
|
fillColor: "transparent"
|
||||||
transform: Translate { y: -shackle.lift }
|
capStyle: ShapePath.RoundCap
|
||||||
|
joinStyle: ShapePath.RoundJoin
|
||||||
Shape {
|
PathPolyline { path: root.shackle() }
|
||||||
anchors.fill: parent
|
|
||||||
preferredRendererType: Shape.CurveRenderer
|
|
||||||
ShapePath {
|
|
||||||
strokeColor: root.color
|
|
||||||
strokeWidth: 11 * root.u
|
|
||||||
fillColor: "transparent"
|
|
||||||
capStyle: ShapePath.FlatCap
|
|
||||||
startX: 5.5 * root.u
|
|
||||||
startY: shackle.height
|
|
||||||
PathLine { x: 5.5 * root.u; y: 24 * root.u }
|
|
||||||
PathArc {
|
|
||||||
x: shackle.width - 5.5 * root.u
|
|
||||||
y: 24 * root.u
|
|
||||||
radiusX: (shackle.width - 11 * root.u) / 2
|
|
||||||
radiusY: radiusX
|
|
||||||
}
|
|
||||||
PathLine { x: shackle.width - 5.5 * root.u; y: shackle.height }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user